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)
This commit is contained in:
PSI-Rockin 2024-05-02 21:40:08 -04:00
parent 8e303d8d08
commit f230384444
16 changed files with 411 additions and 251 deletions

View file

@ -175,6 +175,7 @@ set(KERNEL_SOURCE_FILES src/core/kernel/kernel.cpp src/core/kernel/resource_limi
src/core/kernel/address_arbiter.cpp src/core/kernel/error.cpp src/core/kernel/address_arbiter.cpp src/core/kernel/error.cpp
src/core/kernel/file_operations.cpp src/core/kernel/directory_operations.cpp src/core/kernel/file_operations.cpp src/core/kernel/directory_operations.cpp
src/core/kernel/idle_thread.cpp src/core/kernel/timers.cpp src/core/kernel/idle_thread.cpp src/core/kernel/timers.cpp
src/core/kernel/fcram.cpp
) )
set(SERVICE_SOURCE_FILES src/core/services/service_manager.cpp src/core/services/apt.cpp src/core/services/hid.cpp set(SERVICE_SOURCE_FILES src/core/services/service_manager.cpp src/core/services/apt.cpp src/core/services/hid.cpp
src/core/services/fs.cpp src/core/services/gsp_gpu.cpp src/core/services/gsp_lcd.cpp src/core/services/fs.cpp src/core/services/gsp_gpu.cpp src/core/services/gsp_lcd.cpp

47
include/kernel/fcram.hpp Normal file
View file

@ -0,0 +1,47 @@
#pragma once
#include <list>
#include <memory>
#include "kernel_types.hpp"
class Memory;
typedef std::list<FcramBlock> FcramBlockList;
class KFcram {
struct Region {
struct Block {
s32 pages;
s32 pageOffs;
bool used;
Block(s32 pages, u32 pageOffs) : pages(pages), pageOffs(pageOffs), used(false) {}
};
std::list<Block> blocks;
u32 start;
s32 pages;
s32 freePages;
public:
Region() : start(0), pages(0) {}
void reset(u32 start, size_t size);
void alloc(std::list<FcramBlock>& out, s32 pages, bool linear);
u32 getUsedCount();
u32 getFreeCount();
};
Memory& mem;
Region appRegion, sysRegion, baseRegion;
uint8_t* fcram;
std::unique_ptr<u32> refs;
public:
KFcram(Memory& memory);
void reset(size_t ramSize, size_t appSize, size_t sysSize, size_t baseSize);
void alloc(FcramBlockList& out, s32 pages, FcramRegion region, bool linear);
void incRef(FcramBlockList& list);
void decRef(FcramBlockList& list);
u32 getUsedCount(FcramRegion region);
};

View file

@ -7,6 +7,7 @@
#include <vector> #include <vector>
#include "config.hpp" #include "config.hpp"
#include "fcram.hpp"
#include "helpers.hpp" #include "helpers.hpp"
#include "kernel_types.hpp" #include "kernel_types.hpp"
#include "logger.hpp" #include "logger.hpp"
@ -21,6 +22,8 @@ class Kernel {
CPU& cpu; CPU& cpu;
Memory& mem; Memory& mem;
KFcram fcramManager;
// The handle number for the next kernel object to be created // The handle number for the next kernel object to be created
u32 handleCounter; u32 handleCounter;
// A list of our OS threads, the max number of which depends on the resource limit (hardcoded 32 per process on retail it seems). // A list of our OS threads, the max number of which depends on the resource limit (hardcoded 32 per process on retail it seems).
@ -243,6 +246,7 @@ public:
} }
ServiceManager& getServiceManager() { return serviceManager; } ServiceManager& getServiceManager() { return serviceManager; }
KFcram& getFcramManager() { return fcramManager; }
void sendGPUInterrupt(GPUInterrupt type) { serviceManager.sendGPUInterrupt(type); } void sendGPUInterrupt(GPUInterrupt type) { serviceManager.sendGPUInterrupt(type); }
void clearInstructionCache(); void clearInstructionCache();

View file

@ -44,6 +44,12 @@ enum class ProcessorID : s32 {
New3DSExtra2 = 3 New3DSExtra2 = 3
}; };
enum class FcramRegion {
App = 0x100,
Sys = 0x200,
Base = 0x300
};
struct AddressArbiter {}; struct AddressArbiter {};
struct ResourceLimits { struct ResourceLimits {
@ -242,4 +248,11 @@ struct KernelObject {
Helpers::panic("Called GetWaitList on kernel object without a waitlist (Type: %s)", getTypeName()); Helpers::panic("Called GetWaitList on kernel object without a waitlist (Type: %s)", getTypeName());
} }
} }
};
struct FcramBlock {
u32 paddr;
s32 pages;
FcramBlock(u32 paddr, s32 pages) : paddr(paddr), pages(pages) {}
}; };

View file

@ -4,14 +4,17 @@
#include <filesystem> #include <filesystem>
#include <fstream> #include <fstream>
#include <optional> #include <optional>
#include <list>
#include <vector> #include <vector>
#include "config.hpp" #include "config.hpp"
#include "crypto/aes_engine.hpp" #include "crypto/aes_engine.hpp"
#include "handles.hpp" #include "handles.hpp"
#include "helpers.hpp" #include "helpers.hpp"
#include "kernel/kernel_types.hpp"
#include "loader/ncsd.hpp" #include "loader/ncsd.hpp"
#include "loader/3dsx.hpp" #include "loader/3dsx.hpp"
#include "result/result.hpp"
#include "services/region_codes.hpp" #include "services/region_codes.hpp"
namespace PhysicalAddrs { namespace PhysicalAddrs {
@ -45,7 +48,7 @@ namespace VirtualAddrs {
LinearHeapEndNew = 0x40000000, LinearHeapEndNew = 0x40000000,
// Start of TLS for first thread. Next thread's storage will be at TLSBase + 0x1000, and so on // Start of TLS for first thread. Next thread's storage will be at TLSBase + 0x1000, and so on
TLSBase = 0xFF400000, TLSBase = 0x1FF82000,
TLSSize = 0x1000, TLSSize = 0x1000,
VramStart = 0x1F000000, VramStart = 0x1F000000,
@ -77,17 +80,15 @@ namespace KernelMemoryTypes {
PERMISSION_X = 1 << 2 PERMISSION_X = 1 << 2
}; };
// I assume this is referring to a single piece of allocated memory? If it's for pages, it makes no sense.
// If it's for multiple allocations, it also makes no sense
struct MemoryInfo { struct MemoryInfo {
u32 baseAddr; // Base process virtual address. Used as a paddr in lockedMemoryInfo instead u32 baseAddr;
u32 size; // Of what? u32 pages;
u32 perms; // Is this referring to a single page or? u32 perms;
u32 state; u32 state;
u32 end() { return baseAddr + size; } u32 end() { return baseAddr + (pages << 12); }
MemoryInfo(u32 baseAddr, u32 size, u32 perms, u32 state) : baseAddr(baseAddr), size(size) MemoryInfo() : baseAddr(0), pages(0), perms(0), state(0) {}
, perms(perms), state(state) {} MemoryInfo(u32 baseAddr, u32 pages, u32 perms, u32 state) : baseAddr(baseAddr), pages(pages), perms(perms), state(state) {}
}; };
// Shared memory block for HID, GSP:GPU etc // Shared memory block for HID, GSP:GPU etc
@ -101,6 +102,9 @@ namespace KernelMemoryTypes {
}; };
} }
class KFcram;
enum class FcramRegion;
class Memory { class Memory {
u8* fcram; u8* fcram;
u8* dspRam; // Provided to us by Audio u8* dspRam; // Provided to us by Audio
@ -109,11 +113,17 @@ class Memory {
u64& cpuTicks; // Reference to the CPU tick counter u64& cpuTicks; // Reference to the CPU tick counter
using SharedMemoryBlock = KernelMemoryTypes::SharedMemoryBlock; using SharedMemoryBlock = KernelMemoryTypes::SharedMemoryBlock;
// TODO: remove this reference when Peach's excellent page table code is moved to a better home
KFcram& fcramManager;
// Our dynarmic core uses page tables for reads and writes with 4096 byte pages // Our dynarmic core uses page tables for reads and writes with 4096 byte pages
std::vector<uintptr_t> readTable, writeTable; std::vector<uintptr_t> readTable, writeTable;
// vaddr->paddr translation table
std::vector<u32> paddrTable;
// This tracks our OS' memory allocations // This tracks our OS' memory allocations
std::vector<KernelMemoryTypes::MemoryInfo> memoryInfo; std::list<KernelMemoryTypes::MemoryInfo> memoryInfo;
std::array<SharedMemoryBlock, 5> sharedMemBlocks = { std::array<SharedMemoryBlock, 5> sharedMemBlocks = {
SharedMemoryBlock(0, 0, KernelHandles::FontSharedMemHandle), // Shared memory for the system font (size is 0 because we read the size from the cmrc filesystem SharedMemoryBlock(0, 0, KernelHandles::FontSharedMemHandle), // Shared memory for the system font (size is 0 because we read the size from the cmrc filesystem
@ -131,6 +141,9 @@ public:
static constexpr u32 FCRAM_SIZE = u32(128_MB); static constexpr u32 FCRAM_SIZE = u32(128_MB);
static constexpr u32 FCRAM_APPLICATION_SIZE = u32(64_MB); static constexpr u32 FCRAM_APPLICATION_SIZE = u32(64_MB);
static constexpr u32 FCRAM_SYSTEM_SIZE = u32(44_MB);
static constexpr u32 FCRAM_BASE_SIZE = u32(20_MB);
static constexpr u32 FCRAM_PAGE_COUNT = FCRAM_SIZE / pageSize; static constexpr u32 FCRAM_PAGE_COUNT = FCRAM_SIZE / pageSize;
static constexpr u32 FCRAM_APPLICATION_PAGE_COUNT = FCRAM_APPLICATION_SIZE / pageSize; static constexpr u32 FCRAM_APPLICATION_PAGE_COUNT = FCRAM_APPLICATION_SIZE / pageSize;
@ -139,8 +152,8 @@ public:
static constexpr u32 DSP_DATA_MEMORY_OFFSET = u32(256_KB); static constexpr u32 DSP_DATA_MEMORY_OFFSET = u32(256_KB);
private: private:
std::bitset<FCRAM_PAGE_COUNT> usedFCRAMPages; //std::bitset<FCRAM_PAGE_COUNT> usedFCRAMPages;
std::optional<u32> findPaddr(u32 size); //std::optional<u32> findPaddr(u32 size);
u64 timeSince3DSEpoch(); u64 timeSince3DSEpoch();
// https://www.3dbrew.org/wiki/Configuration_Memory#ENVINFO // https://www.3dbrew.org/wiki/Configuration_Memory#ENVINFO
@ -167,10 +180,8 @@ private:
public: public:
u16 kernelVersion = 0; u16 kernelVersion = 0;
u32 usedUserMemory = u32(0_MB); // How much of the APPLICATION FCRAM range is used (allocated to the appcore)
u32 usedSystemMemory = u32(0_MB); // Similar for the SYSTEM range (reserved for the syscore)
Memory(u64& cpuTicks, const EmulatorConfig& config); Memory(KFcram& fcramManager, u64& cpuTicks, const EmulatorConfig& config);
void reset(); void reset();
void* getReadPointer(u32 address); void* getReadPointer(u32 address);
void* getWritePointer(u32 address); void* getWritePointer(u32 address);
@ -196,22 +207,6 @@ private:
u32 getLinearHeapVaddr(); u32 getLinearHeapVaddr();
u8* getFCRAM() { return fcram; } u8* getFCRAM() { return fcram; }
// Total amount of OS-only FCRAM available (Can vary depending on how much FCRAM the app requests via the cart exheader)
u32 totalSysFCRAM() {
return FCRAM_SIZE - FCRAM_APPLICATION_SIZE;
}
// Amount of OS-only FCRAM currently available
u32 remainingSysFCRAM() {
return totalSysFCRAM() - usedSystemMemory;
}
// Physical FCRAM index to the start of OS FCRAM
// We allocate the first part of physical FCRAM for the application, and the rest to the OS. So the index for the OS = application ram size
u32 sysFCRAMIndex() {
return FCRAM_APPLICATION_SIZE;
}
enum class BatteryLevel { enum class BatteryLevel {
Empty = 0, AlmostEmpty, OneBar, TwoBars, ThreeBars, FourBars Empty = 0, AlmostEmpty, OneBar, TwoBars, ThreeBars, FourBars
}; };
@ -253,14 +248,16 @@ private:
// isMap: Shows whether this is a reserve operation, that allocates memory and maps it to the addr space, or if it's a map operation, // isMap: Shows whether this is a reserve operation, that allocates memory and maps it to the addr space, or if it's a map operation,
// which just maps memory from paddr to vaddr without hassle. The latter is useful for shared memory mapping, the "map" ControlMemory, op, etc // which just maps memory from paddr to vaddr without hassle. The latter is useful for shared memory mapping, the "map" ControlMemory, op, etc
// Returns the vaddr the FCRAM was mapped to or nullopt if allocation failed // Returns the vaddr the FCRAM was mapped to or nullopt if allocation failed
std::optional<u32> allocateMemory(u32 vaddr, u32 paddr, u32 size, bool linear, bool r = true, bool w = true, bool x = true, //std::optional<u32> allocateMemory(u32 vaddr, u32 paddr, u32 size, bool linear, bool r = true, bool w = true, bool x = true,
bool adjustsAddrs = false, bool isMap = false); //bool adjustsAddrs = false, bool isMap = false);
KernelMemoryTypes::MemoryInfo queryMemory(u32 vaddr);
// For internal use bool allocMemory(u32 vaddr, s32 pages, FcramRegion region, bool r, bool w, bool x);
// Allocates a "size"-sized chunk of system FCRAM and returns the index of physical FCRAM used for the allocation bool allocMemoryLinear(u32& outVaddr, u32 inVaddr, s32 pages, FcramRegion region, bool r, bool w, bool x);
// Used for allocating things like shared memory and the like bool mapPhysicalMemory(u32 vaddr, u32 paddr, s32 pages, bool r, bool w, bool x);
u32 allocateSysMemory(u32 size); bool mapVirtualMemory(u32 dstVaddr, u32 srcVaddr, s32 pages, bool r, bool w, bool x);
Result::HorizonResult queryMemory(KernelMemoryTypes::MemoryInfo& out, u32 vaddr);
void copyToVaddr(u32 dstVaddr, const u8* srcHost, s32 size);
// Map a shared memory block to virtual address vaddr with permissions "myPerms" // Map a shared memory block to virtual address vaddr with permissions "myPerms"
// The kernel has a second permission parameter in MapMemoryBlock but not sure what's used for // The kernel has a second permission parameter in MapMemoryBlock but not sure what's used for
@ -270,7 +267,7 @@ private:
// Mirrors the page mapping for "size" bytes starting from sourceAddress, to "size" bytes in destAddress // Mirrors the page mapping for "size" bytes starting from sourceAddress, to "size" bytes in destAddress
// All of the above must be page-aligned. // All of the above must be page-aligned.
void mirrorMapping(u32 destAddress, u32 sourceAddress, u32 size); // void mirrorMapping(u32 destAddress, u32 sourceAddress, u32 size);
// Backup of the game's CXI partition info, if any // Backup of the game's CXI partition info, if any
std::optional<NCCH> loadedCXI = std::nullopt; std::optional<NCCH> loadedCXI = std::nullopt;
@ -283,7 +280,6 @@ private:
u8* getDSPMem() { return dspRam; } u8* getDSPMem() { return dspRam; }
u8* getDSPDataMem() { return &dspRam[DSP_DATA_MEMORY_OFFSET]; } u8* getDSPDataMem() { return &dspRam[DSP_DATA_MEMORY_OFFSET]; }
u8* getDSPCodeMem() { return &dspRam[DSP_CODE_MEMORY_OFFSET]; } u8* getDSPCodeMem() { return &dspRam[DSP_CODE_MEMORY_OFFSET]; }
u32 getUsedUserMem() { return usedUserMemory; }
void setVRAM(u8* pointer) { vram = pointer; } void setVRAM(u8* pointer) { vram = pointer; }
void setDSPMem(u8* pointer) { dspRam = pointer; } void setDSPMem(u8* pointer) { dspRam = pointer; }

106
src/core/kernel/fcram.cpp Normal file
View file

@ -0,0 +1,106 @@
#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.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->pageOffs + 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->pageOffs << 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]);
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

@ -26,18 +26,14 @@ static constexpr u8 idleThreadCode[] = {
// Set up an idle thread to run when no thread is able to run // Set up an idle thread to run when no thread is able to run
void Kernel::setupIdleThread() { void Kernel::setupIdleThread() {
Thread& t = threads[idleThreadIndex]; 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 // We only allocate 4KB (1 page) because our idle code is pretty small
const u32 fcramIndex = mem.allocateSysMemory(Memory::pageSize); constexpr u32 codeAddress = 0x3FC00000;
auto vaddr = mem.allocateMemory(codeAddress, fcramIndex, Memory::pageSize, true, true, false, true, false, true); if (!mem.allocMemory(codeAddress, 1, FcramRegion::Base, true, true, false)) Helpers::panic("Failed to setup idle thread");
if (!vaddr.has_value() || vaddr.value() != codeAddress) {
Helpers::panic("Failed to setup idle thread");
}
// Copy idle thread code to the allocated FCRAM // 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.entrypoint = codeAddress;
t.tlsBase = 0; t.tlsBase = 0;

View file

@ -4,7 +4,8 @@
#include "cpu.hpp" #include "cpu.hpp"
Kernel::Kernel(CPU& cpu, Memory& mem, GPU& gpu, const EmulatorConfig& config) Kernel::Kernel(CPU& cpu, Memory& mem, GPU& gpu, const EmulatorConfig& config)
: cpu(cpu), regs(cpu.regs()), mem(mem), handleCounter(0), serviceManager(regs, mem, gpu, currentProcess, *this, config) { : cpu(cpu), regs(cpu.regs()), mem(mem), handleCounter(0), serviceManager(regs, mem, gpu, currentProcess, *this, config),
fcramManager(mem) {
objects.reserve(512); // Make room for a few objects to avoid further memory allocs later objects.reserve(512); // Make room for a few objects to avoid further memory allocs later
mutexHandles.reserve(8); mutexHandles.reserve(8);
portHandles.reserve(32); portHandles.reserve(32);
@ -139,6 +140,9 @@ void Kernel::reset() {
threadCount = 0; threadCount = 0;
aliveThreadCount = 0; aliveThreadCount = 0;
// TODO: These values should be derived from the memory type in an app's exheader
fcramManager.reset(Memory::FCRAM_SIZE, Memory::FCRAM_APPLICATION_SIZE, Memory::FCRAM_SYSTEM_SIZE, Memory::FCRAM_BASE_SIZE);
for (auto& t : threads) { for (auto& t : threads) {
t.status = ThreadStatus::Dead; t.status = ThreadStatus::Dead;
t.waitList.clear(); t.waitList.clear();
@ -265,7 +269,8 @@ void Kernel::getProcessInfo() {
// According to 3DBrew: Amount of private (code, data, heap) memory used by the process + total supervisor-mode // 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 // stack size + page-rounded size of the external handle table
case 2: case 2:
regs[1] = mem.getUsedUserMem(); // TODO
regs[1] = 0;
regs[2] = 0; regs[2] = 0;
break; break;
@ -342,7 +347,7 @@ void Kernel::getSystemInfo() {
switch (subtype) { switch (subtype) {
// Total used memory size in the APPLICATION memory region // Total used memory size in the APPLICATION memory region
case 1: case 1:
regs[1] = mem.getUsedUserMem(); regs[1] = fcramManager.getUsedCount(FcramRegion::App);
regs[2] = 0; regs[2] = 0;
break; break;

View file

@ -44,6 +44,7 @@ void Kernel::controlMemory() {
u32 addr0 = regs[1]; u32 addr0 = regs[1];
u32 addr1 = regs[2]; u32 addr1 = regs[2];
u32 size = regs[3]; u32 size = regs[3];
u32 pages = size >> 12; // Official kernel truncates nonaligned sizes
u32 perms = regs[4]; u32 perms = regs[4];
if (perms == MemoryPermissions::DontCare) { if (perms == MemoryPermissions::DontCare) {
@ -60,7 +61,7 @@ void Kernel::controlMemory() {
if (x) if (x)
Helpers::panic("ControlMemory: attempted to allocate executable memory"); 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); Helpers::panic("ControlMemory: Unaligned parameters\nAddr0: %08X\nAddr1: %08X\nSize: %08X", addr0, addr1, size);
} }
@ -70,16 +71,23 @@ void Kernel::controlMemory() {
switch (operation & 0xFF) { switch (operation & 0xFF) {
case Operation::Commit: { case Operation::Commit: {
std::optional<u32> address = mem.allocateMemory(addr0, 0, size, linear, r, w, x, true); // TODO: base this from the exheader
if (!address.has_value()) auto region = FcramRegion::App;
Helpers::panic("ControlMemory: Failed to allocate memory");
regs[1] = address.value(); 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)) Helpers::panic("ControlMemory: Failed to allocate memory");
outAddr = addr0;
}
regs[1] = outAddr;
break; break;
} }
case Operation::Map: case Operation::Map:
mem.mirrorMapping(addr0, addr1, size); if (!mem.mapVirtualMemory(addr0, addr1, pages, r, w, false)) Helpers::panic("ControlMemory: Failed to map memory");
break; break;
case Operation::Protect: case Operation::Protect:
@ -100,10 +108,11 @@ void Kernel::queryMemory() {
logSVC("QueryMemory(mem info pointer = %08X, page info pointer = %08X, addr = %08X)\n", memInfo, pageInfo, addr); logSVC("QueryMemory(mem info pointer = %08X, page info pointer = %08X, addr = %08X)\n", memInfo, pageInfo, addr);
const auto info = mem.queryMemory(addr); KernelMemoryTypes::MemoryInfo info;
regs[0] = Result::Success; const auto result = mem.queryMemory(info, addr);
regs[0] = result;
regs[1] = info.baseAddr; regs[1] = info.baseAddr;
regs[2] = info.size; regs[2] = info.pages << 12;
regs[3] = info.perms; regs[3] = info.perms;
regs[4] = info.state; regs[4] = info.state;
regs[5] = 0; // page flags regs[5] = 0; // page flags

View file

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

View file

@ -26,7 +26,7 @@ namespace {
} // namespace } // namespace
bool Memory::map3DSX(HB3DSX& hb3dsx, const HB3DSX::Header& header) { bool Memory::map3DSX(HB3DSX& hb3dsx, const HB3DSX::Header& header) {
const LoadInfo hbInfo = { /* const LoadInfo hbInfo = {
.codeSegSizeAligned = (header.codeSegSize + 0xFFF) & ~0xFFF, .codeSegSizeAligned = (header.codeSegSize + 0xFFF) & ~0xFFF,
.rodataSegSizeAligned = (header.rodataSegSize + 0xFFF) & ~0xFFF, .rodataSegSizeAligned = (header.rodataSegSize + 0xFFF) & ~0xFFF,
.dataSegSizeAligned = (header.dataSegSize + 0xFFF) & ~0xFFF, .dataSegSizeAligned = (header.dataSegSize + 0xFFF) & ~0xFFF,
@ -186,10 +186,10 @@ bool Memory::map3DSX(HB3DSX& hb3dsx, const HB3DSX::Header& header) {
return false; return false;
} }
} }
} }*/
// Detect and fill _prm structure // Detect and fill _prm structure
HB3DSX::PrmStruct pst; /*HB3DSX::PrmStruct pst;
std::memcpy(&pst, &code[4], sizeof(pst)); std::memcpy(&pst, &code[4], sizeof(pst));
if (pst.magic[0] == '_' && pst.magic[1] == 'p' && pst.magic[2] == 'r' && pst.magic[3] == 'm') { if (pst.magic[0] == '_' && pst.magic[1] == 'p' && pst.magic[2] == 'r' && pst.magic[3] == 'm') {
// if there was any argv to put, it would go there // if there was any argv to put, it would go there
@ -205,7 +205,7 @@ bool Memory::map3DSX(HB3DSX& hb3dsx, const HB3DSX::Header& header) {
// RUNFLAG_APTREINIT: Reinitialize APT. // RUNFLAG_APTREINIT: Reinitialize APT.
// From libctru. Because there's no previously running software here // From libctru. Because there's no previously running software here
pst.runFlags |= 1 << 1; pst.runFlags |= 1 << 1;*/
/* s64 dummy; /* s64 dummy;
bool isN3DS = svcGetSystemInfo(&dummy, 0x10001, 0) == 0; bool isN3DS = svcGetSystemInfo(&dummy, 0x10001, 0) == 0;
@ -213,7 +213,7 @@ bool Memory::map3DSX(HB3DSX& hb3dsx, const HB3DSX::Header& header) {
{ {
pst->heapSize = u32(48_MB); pst->heapSize = u32(48_MB);
pst->linearHeapSize = u32(64_MB); pst->linearHeapSize = u32(64_MB);
} else */ { } else *//* {
pst.heapSize = u32(24_MB); pst.heapSize = u32(24_MB);
pst.linearHeapSize = u32(32_MB); pst.linearHeapSize = u32(32_MB);
} }
@ -228,7 +228,8 @@ bool Memory::map3DSX(HB3DSX& hb3dsx, const HB3DSX::Header& header) {
allocateMemory(rodataSegAddr, paddr + rodataOffset, hbInfo.rodataSegSizeAligned, true, true, false, false); // Rodata is R-- allocateMemory(rodataSegAddr, paddr + rodataOffset, hbInfo.rodataSegSizeAligned, true, true, false, false); // Rodata is R--
allocateMemory(dataSegAddr, paddr + dataOffset, hbInfo.dataSegSizeAligned + 0x1000, true, true, true, false); // Data+BSS+Extra is RW- allocateMemory(dataSegAddr, paddr + dataOffset, hbInfo.dataSegSizeAligned + 0x1000, true, true, true, false); // Data+BSS+Extra is RW-
return true; return true;*/
return false;
} }
std::optional<u32> Memory::load3DSX(const std::filesystem::path& path) { std::optional<u32> Memory::load3DSX(const std::filesystem::path& path) {

View file

@ -6,7 +6,7 @@
using namespace ELFIO; using namespace ELFIO;
std::optional<u32> Memory::loadELF(std::ifstream& file) { std::optional<u32> Memory::loadELF(std::ifstream& file) {
loadedCXI = std::nullopt; // ELF files don't have a CXI, so set this to null /* loadedCXI = std::nullopt; // ELF files don't have a CXI, so set this to null
elfio reader; elfio reader;
if (!file.good() || !reader.load(file)) { if (!file.good() || !reader.load(file)) {
@ -65,5 +65,6 @@ std::optional<u32> Memory::loadELF(std::ifstream& file) {
// ELF can't specify a region, make it default to USA // ELF can't specify a region, make it default to USA
region = Regions::USA; region = Regions::USA;
return static_cast<u32>(reader.get_entry()); return static_cast<u32>(reader.get_entry());*/
return std::nullopt;
} }

View file

@ -4,6 +4,7 @@
#include <optional> #include <optional>
#include "memory.hpp" #include "memory.hpp"
#include "kernel/fcram.hpp"
bool Memory::mapCXI(NCSD& ncsd, NCCH& cxi) { bool Memory::mapCXI(NCSD& ncsd, NCCH& cxi) {
printf("Text address = %08X, size = %08X\n", cxi.text.address, cxi.text.size); printf("Text address = %08X, size = %08X\n", cxi.text.address, cxi.text.size);
@ -49,15 +50,6 @@ bool Memory::mapCXI(NCSD& ncsd, NCCH& cxi) {
return false; return false;
} }
const auto opt = findPaddr(totalSize);
if (!opt.has_value()) {
Helpers::panic("Failed to find paddr to map CXI file's code to");
return false;
}
const auto paddr = opt.value();
std::memcpy(&fcram[paddr], &code[0], totalSize); // Copy the 3 segments + BSS to FCRAM
// Map the ROM on the kernel side // Map the ROM on the kernel side
u32 textOffset = 0; u32 textOffset = 0;
u32 textAddr = cxi.text.address; u32 textAddr = cxi.text.address;
@ -71,11 +63,19 @@ bool Memory::mapCXI(NCSD& ncsd, NCCH& cxi) {
u32 dataAddr = cxi.data.address; u32 dataAddr = cxi.data.address;
u32 dataSize = cxi.data.pageCount * pageSize + bssSize; // We're merging the data and BSS segments, as BSS is just pre-initted .data u32 dataSize = cxi.data.pageCount * pageSize + bssSize; // We're merging the data and BSS segments, as BSS is just pre-initted .data
allocateMemory(textAddr, paddr + textOffset, textSize, true, true, false, true); // Text is R-X // TODO: base this off the exheader
allocateMemory(rodataAddr, paddr + rodataOffset, rodataSize, true, true, false, false); // Rodata is R-- auto region = FcramRegion::App;
allocateMemory(dataAddr, paddr + dataOffset, dataSize, true, true, true, false); // Data+BSS is RW-
ncsd.entrypoint = textAddr; allocMemory(textAddr, cxi.text.pageCount, region, true, false, true);
allocMemory(rodataAddr, cxi.rodata.pageCount, region, true, false, false);
allocMemory(dataAddr, cxi.data.pageCount + (bssSize >> 12), region, true, true, false); // Merge data and BSS segments
// Copy .code file to FCRAM
copyToVaddr(textAddr, code.data(), textSize);
copyToVaddr(rodataAddr, code.data() + textSize, rodataSize);
copyToVaddr(dataAddr, code.data() + textSize + rodataSize, cxi.data.pageCount << 12);
ncsd.entrypoint = cxi.text.address;
// Back the IOFile for accessing the ROM, as well as the ROM's CXI partition, in the memory class. // Back the IOFile for accessing the ROM, as well as the ROM's CXI partition, in the memory class.
CXIFile = ncsd.file; CXIFile = ncsd.file;

View file

@ -6,6 +6,7 @@
#include <ctime> #include <ctime>
#include "config_mem.hpp" #include "config_mem.hpp"
#include "kernel/fcram.hpp"
#include "resource_limits.hpp" #include "resource_limits.hpp"
#include "services/ptm.hpp" #include "services/ptm.hpp"
@ -13,38 +14,33 @@ CMRC_DECLARE(ConsoleFonts);
using namespace KernelMemoryTypes; using namespace KernelMemoryTypes;
Memory::Memory(u64& cpuTicks, const EmulatorConfig& config) : cpuTicks(cpuTicks), config(config) { Memory::Memory(KFcram& fcramManager, u64& cpuTicks, const EmulatorConfig& config) : fcramManager(fcramManager), cpuTicks(cpuTicks), config(config) {
fcram = new uint8_t[FCRAM_SIZE](); fcram = new uint8_t[FCRAM_SIZE]();
readTable.resize(totalPageCount, 0); readTable.resize(totalPageCount, 0);
writeTable.resize(totalPageCount, 0); writeTable.resize(totalPageCount, 0);
memoryInfo.reserve(32); // Pre-allocate some room for memory allocation info to avoid dynamic allocs paddrTable.resize(totalPageCount, 0);
} }
void Memory::reset() { void Memory::reset() {
// Unallocate all memory // Mark the entire process address space as free
constexpr static int MAX_USER_PAGES = 0x40000000 >> 12;
memoryInfo.clear(); memoryInfo.clear();
usedFCRAMPages.reset(); memoryInfo.push_back(MemoryInfo(0, MAX_USER_PAGES, 0, KernelMemoryTypes::Free));
usedUserMemory = u32(0_MB);
usedSystemMemory = u32(0_MB); // TODO: remove this, only needed to make the subsequent allocations work for now
fcramManager.reset(FCRAM_SIZE, FCRAM_APPLICATION_SIZE, FCRAM_SYSTEM_SIZE, FCRAM_BASE_SIZE);
for (u32 i = 0; i < totalPageCount; i++) { for (u32 i = 0; i < totalPageCount; i++) {
readTable[i] = 0; readTable[i] = 0;
writeTable[i] = 0; writeTable[i] = 0;
paddrTable[i] = 0;
} }
// Map (32 * 4) KB of FCRAM before the stack for the TLS of each thread // Map 4 KB of FCRAM for each thread
std::optional<u32> tlsBaseOpt = findPaddr(32 * 4_KB); // TODO: the region should be taken from the exheader
if (!tlsBaseOpt.has_value()) { // Should be unreachable but still good to have // TODO: each thread should only have 512 bytes - an FCRAM page should account for 8 threads
Helpers::panic("Failed to allocate memory for thread-local storage"); assert(allocMemory(VirtualAddrs::TLSBase, appResourceLimits.maxThreads, FcramRegion::App, true, true, false));
}
u32 basePaddrForTLS = tlsBaseOpt.value();
for (u32 i = 0; i < appResourceLimits.maxThreads; i++) {
u32 vaddr = VirtualAddrs::TLSBase + i * VirtualAddrs::TLSSize;
allocateMemory(vaddr, basePaddrForTLS, VirtualAddrs::TLSSize, true);
basePaddrForTLS += VirtualAddrs::TLSSize;
}
// Initialize shared memory blocks and reserve memory for them // Initialize shared memory blocks and reserve memory for them
for (auto& e : sharedMemBlocks) { for (auto& e : sharedMemBlocks) {
@ -55,19 +51,18 @@ void Memory::reset() {
} }
e.mapped = false; e.mapped = false;
e.paddr = allocateSysMemory(e.size); FcramBlockList memBlock;
fcramManager.alloc(memBlock, e.size >> 12, FcramRegion::Sys, false);
e.paddr = memBlock.begin()->paddr;
} }
// Map DSP RAM as R/W at [0x1FF00000, 0x1FF7FFFF] // Map DSP RAM as R/W at [0x1FF00000, 0x1FF7FFFF]
constexpr u32 dspRamPages = DSP_RAM_SIZE / pageSize; // Number of DSP RAM pages constexpr u32 dspRamPages = DSP_RAM_SIZE / pageSize; // Number of DSP RAM pages
constexpr u32 initialPage = VirtualAddrs::DSPMemStart / pageSize; // First page of DSP RAM in the virtual address space constexpr u32 initialPage = VirtualAddrs::DSPMemStart / pageSize; // First page of DSP RAM in the virtual address space
for (u32 i = 0; i < dspRamPages; i++) { u32 vaddr = VirtualAddrs::DSPMemStart;
auto pointer = uintptr_t(&dspRam[i * pageSize]); u32 paddr = vaddr;
mapPhysicalMemory(vaddr, paddr, dspRamPages, true, true, false);
readTable[i + initialPage] = pointer;
writeTable[i + initialPage] = pointer;
}
// Later adjusted based on ROM header when possible // Later adjusted based on ROM header when possible
region = Regions::USA; region = Regions::USA;
@ -75,14 +70,9 @@ void Memory::reset() {
bool Memory::allocateMainThreadStack(u32 size) { bool Memory::allocateMainThreadStack(u32 size) {
// Map stack pages as R/W // Map stack pages as R/W
std::optional<u32> basePaddr = findPaddr(size); // TODO: get the region from the exheader
if (!basePaddr.has_value()) { // Should also be unreachable but still good to have
return false;
}
const u32 stackBottom = VirtualAddrs::StackTop - size; const u32 stackBottom = VirtualAddrs::StackTop - size;
std::optional<u32> result = allocateMemory(stackBottom, basePaddr.value(), size, true); // Should never be nullopt return allocMemory(stackBottom, size >> 12, FcramRegion::App, true, true, false);
return result.has_value();
} }
u8 Memory::read8(u32 vaddr) { u8 Memory::read8(u32 vaddr) {
@ -295,149 +285,155 @@ std::string Memory::readString(u32 address, u32 maxSize) {
// thanks to the New 3DS having more FCRAM // thanks to the New 3DS having more FCRAM
u32 Memory::getLinearHeapVaddr() { return (kernelVersion < 0x22C) ? VirtualAddrs::LinearHeapStartOld : VirtualAddrs::LinearHeapStartNew; } u32 Memory::getLinearHeapVaddr() { return (kernelVersion < 0x22C) ? VirtualAddrs::LinearHeapStartOld : VirtualAddrs::LinearHeapStartNew; }
std::optional<u32> Memory::allocateMemory(u32 vaddr, u32 paddr, u32 size, bool linear, bool r, bool w, bool x, bool adjustAddrs, bool isMap) { bool Memory::allocMemory(u32 vaddr, s32 pages, FcramRegion region, bool r, bool w, bool x)
// Kernel-allocated memory & size must always be aligned to a page boundary {
// Additionally assert we don't OoM and that we don't try to allocate physical FCRAM past what's available to userland FcramBlockList memList;
// If we're mapping there's no fear of OoM, because we're not really allocating memory, just binding vaddrs to specific paddrs fcramManager.alloc(memList, pages, region, false);
assert(isAligned(vaddr) && isAligned(paddr) && isAligned(size));
assert(size <= FCRAM_APPLICATION_SIZE || isMap);
assert(usedUserMemory + size <= FCRAM_APPLICATION_SIZE || isMap);
assert(paddr + size <= FCRAM_APPLICATION_SIZE || isMap);
// Amount of available user FCRAM pages and FCRAM pages to allocate respectively bool succeeded = true;
const u32 availablePageCount = (FCRAM_APPLICATION_SIZE - usedUserMemory) / pageSize;
const u32 neededPageCount = size / pageSize;
assert(availablePageCount >= neededPageCount || isMap); for (auto it = memList.begin(); it != memList.end(); it++) {
succeeded = mapPhysicalMemory(vaddr, it->paddr, it->pages, r, w, x);
if (!succeeded) break;
vaddr += it->pages << 12;
}
// If the paddr is 0, that means we need to select our own assert(succeeded);
// TODO: Fix. This method always tries to allocate blocks linearly. return succeeded;
// However, if the allocation is non-linear, the panic will trigger when it shouldn't. }
// Non-linear allocation needs special handling
if (paddr == 0 && adjustAddrs) { bool Memory::allocMemoryLinear(u32& outVaddr, u32 inVaddr, s32 pages, FcramRegion region, bool r, bool w, bool x)
std::optional<u32> newPaddr = findPaddr(size); {
if (!newPaddr.has_value()) { if (inVaddr) Helpers::panic("inVaddr specified for linear allocation!");
Helpers::panic("Failed to find paddr");
FcramBlockList memList;
fcramManager.alloc(memList, pages, region, true);
u32 paddr = memList.begin()->paddr;
u32 vaddr = getLinearHeapVaddr() + paddr;
if (!mapPhysicalMemory(vaddr, paddr, pages, r, w, x)) Helpers::panic("Failed to map linear memory!");
outVaddr = vaddr;
return true;
}
bool Memory::mapPhysicalMemory(u32 vaddr, u32 paddr, s32 pages, bool r, bool w, bool x)
{
assert(!(vaddr & 0xFFF));
assert(!(paddr & 0xFFF));
for (auto it = memoryInfo.begin(); it != memoryInfo.end(); it++) {
// Look for a free block that the requested memory region fits directly inside
u32 blockStart = it->baseAddr;
u32 blockEnd = it->end();
u32 reqStart = vaddr;
u32 reqEnd = vaddr + (pages << 12);
if (!(reqStart >= blockStart && reqEnd <= blockEnd)) continue;
if (it->state != MemoryState::Free) continue;
// Now that a candidate block has been selected, fill it with the necessary info
it->baseAddr = reqStart;
it->pages = pages;
it->perms = (r ? PERMISSION_R : 0) | (w ? PERMISSION_W : 0) | (x ? PERMISSION_X : 0);
it->state = MemoryState::Private; // TODO: make this a function parameter
// If the requested memory region is smaller than the block found, the block must be split
if (blockStart < reqStart) {
MemoryInfo startBlock(blockStart, (reqStart - blockStart) >> 12, 0, MemoryState::Free);
memoryInfo.insert(it, startBlock);
} }
paddr = newPaddr.value(); if (reqEnd < blockEnd) {
assert(paddr + size <= FCRAM_APPLICATION_SIZE || isMap); auto itAfter = std::next(it);
} MemoryInfo endBlock(reqEnd, (blockEnd - reqEnd) >> 12, 0, MemoryState::Free);
memoryInfo.insert(itAfter, endBlock);
// If the vaddr is 0 that means we need to select our own
// Depending on whether our mapping should be linear or not we allocate from one of the 2 typical heap spaces
// We don't plan on implementing freeing any time soon, so we can pick added userUserMemory to the vaddr base to
// Get the full vaddr.
// TODO: Fix this
if (vaddr == 0 && adjustAddrs) {
// Linear memory needs to be allocated in a way where you can easily get the paddr by subtracting the linear heap base
// In order to be able to easily send data to hardware like the GPU
if (linear) {
vaddr = getLinearHeapVaddr() + paddr;
} else {
vaddr = usedUserMemory + VirtualAddrs::NormalHeapStart;
} }
}
if (!isMap) { // Fill the paddr table as well as the host pointer tables
usedUserMemory += size; // TODO: make this a separate function
} u8* hostPtr = nullptr;
if (paddr < FCRAM_SIZE) {
hostPtr = fcram + paddr; // FIXME
}
else if (paddr >= VirtualAddrs::DSPMemStart && paddr < VirtualAddrs::DSPMemStart + DSP_RAM_SIZE) {
hostPtr = dspRam + (paddr - VirtualAddrs::DSPMemStart);
}
for (int i = 0; i < pages; i++) paddrTable[(vaddr >> 12) + i] = paddr + (i << 12);
// Do linear mapping
u32 virtualPage = vaddr >> pageShift;
u32 physPage = paddr >> pageShift; // TODO: Special handle when non-linear mapping is necessary
for (u32 i = 0; i < neededPageCount; i++) {
if (r) { if (r) {
readTable[virtualPage] = uintptr_t(&fcram[physPage * pageSize]); for (int i = 0; i < pages; i++) readTable[(vaddr >> 12) + i] = (uintptr_t)(hostPtr + (i << 12));
} }
if (w) { if (w) {
writeTable[virtualPage] = uintptr_t(&fcram[physPage * pageSize]); for (int i = 0; i < pages; i++) writeTable[(vaddr >> 12) + i] = (uintptr_t)(hostPtr + (i << 12));
} }
// Mark FCRAM page as allocated and go on return true;
usedFCRAMPages[physPage] = true;
virtualPage++;
physPage++;
} }
// Back up the info for this allocation in our memoryInfo vector Helpers::panic("Can't map physical memory!");
u32 perms = (r ? PERMISSION_R : 0) | (w ? PERMISSION_W : 0) | (x ? PERMISSION_X : 0); return false;
memoryInfo.push_back(std::move(MemoryInfo(vaddr, size, perms, KernelMemoryTypes::Reserved)));
return vaddr;
} }
// Find a paddr which we can use for allocating "size" bytes bool Memory::mapVirtualMemory(u32 dstVaddr, u32 srcVaddr, s32 pages, bool r, bool w, bool x)
std::optional<u32> Memory::findPaddr(u32 size) { {
assert(isAligned(size)); // Get a list of physical blocks in the source region
const u32 neededPages = size / pageSize; FcramBlockList physicalList;
// The FCRAM page we're testing to see if it's appropriate to use s32 srcPages = pages;
u32 candidatePage = 0; for (auto& alloc : memoryInfo) {
// The number of linear available pages we could find starting from this candidate page. u32 blockStart = alloc.baseAddr;
// If this ends up >= than neededPages then the paddr is good (ie we can use the candidate page as a base address) u32 blockEnd = alloc.end();
u32 counter = 0;
for (u32 i = 0; i < FCRAM_APPLICATION_PAGE_COUNT; i++) { if (!(srcVaddr >= blockStart && srcVaddr < blockEnd)) continue;
if (usedFCRAMPages[i]) { // Page is occupied already, go to new candidate if (alloc.state == MemoryState::Free) Helpers::panic("Found free block when mapping virtual memory!");
candidatePage = i + 1;
counter = 0; s32 blockPaddr = paddrTable[srcVaddr >> 12];
} else { // The paddr we're testing has 1 more free page s32 blockPages = alloc.pages - ((srcVaddr - blockStart) >> 12);
counter++; blockPages = std::min(pages, blockPages);
// Check if there's enough free memory to use this page FcramBlock physicalBlock(blockPaddr, blockPages);
// We use == instead of >= because some software does 0-byte allocations physicalList.push_back(physicalBlock);
if (counter >= neededPages) {
return candidatePage * pageSize; srcVaddr += blockPages << 12;
} srcPages -= blockPages;
} if (srcPages == 0) break;
} }
// Couldn't find any page :( if (srcPages != 0) Helpers::panic("Unable to find virtual pages to map!");
return std::nullopt;
// Map each physical block
// FIXME: this is O(n^2)...
for (auto& block : physicalList) {
if (!mapPhysicalMemory(dstVaddr, block.paddr, block.pages, r, w, x)) Helpers::panic("Failed to map virtual memory!");
dstVaddr += block.pages << 12;
}
return true;
} }
u32 Memory::allocateSysMemory(u32 size) { Result::HorizonResult Memory::queryMemory(MemoryInfo& out, u32 vaddr) {
// Should never be triggered, only here as a sanity check
if (!isAligned(size)) {
Helpers::panic("Memory::allocateSysMemory: Size is not page aligned (val = %08X)", size);
}
// We use a pretty dumb allocator for OS memory since this is not really accessible to the app and is only used internally
// It works by just allocating memory linearly, starting from index 0 of OS memory and going up
// This should also be unreachable in practice and exists as a sanity check
if (size > remainingSysFCRAM()) {
Helpers::panic("Memory::allocateSysMemory: Overflowed OS FCRAM");
}
const u32 pageCount = size / pageSize; // Number of pages that will be used up
const u32 startIndex = sysFCRAMIndex() + usedSystemMemory; // Starting FCRAM index
const u32 startingPage = startIndex / pageSize;
for (u32 i = 0; i < pageCount; i++) {
if (usedFCRAMPages[startingPage + i]) // Also a theoretically unreachable panic for safety
Helpers::panic("Memory::reserveMemory: Trying to reserve already reserved memory");
usedFCRAMPages[startingPage + i] = true;
}
usedSystemMemory += size;
return startIndex;
}
// The way I understand how the kernel's QueryMemory is supposed to work is that you give it a vaddr
// And the kernel looks up the memory allocations it's performed, finds which one it belongs in and returns its info?
// TODO: Verify this
MemoryInfo Memory::queryMemory(u32 vaddr) {
// Check each allocation // Check each allocation
for (auto& alloc : memoryInfo) { for (auto& alloc : memoryInfo) {
// Check if the memory address belongs in this allocation and return the info if so // Check if the memory address belongs in this allocation and return the info if so
if (vaddr >= alloc.baseAddr && vaddr < alloc.end()) { if (vaddr >= alloc.baseAddr && vaddr < alloc.end()) {
return alloc; out = alloc;
return Result::Success;
} }
} }
// Otherwise, if this vaddr was never allocated // Official kernel just returns an error here
// TODO: I think this is meant to return how much memory starting here is free as the size? Helpers::panic("Failed to find block in QueryMemory!");
return MemoryInfo(vaddr, pageSize, 0, KernelMemoryTypes::Free); return Result::FailurePlaceholder;
}
void Memory::copyToVaddr(u32 dstVaddr, const u8* srcHost, s32 size) {
// TODO: check for noncontiguous allocations
u8* dstHost = (u8*)readTable[dstVaddr >> 12] + (dstVaddr & 0xFFF);
memcpy(dstHost, srcHost, size);
} }
u8* Memory::mapSharedMemory(Handle handle, u32 vaddr, u32 myPerms, u32 otherPerms) { u8* Memory::mapSharedMemory(Handle handle, u32 vaddr, u32 myPerms, u32 otherPerms) {
@ -458,13 +454,12 @@ u8* Memory::mapSharedMemory(Handle handle, u32 vaddr, u32 myPerms, u32 otherPerm
bool w = myPerms & 0b010; bool w = myPerms & 0b010;
bool x = myPerms & 0b100; bool x = myPerms & 0b100;
const auto result = allocateMemory(vaddr, paddr, size, true, r, w, x, false, true); if (!mapPhysicalMemory(vaddr, paddr, size >> 12, true, true, false)) {
e.mapped = true;
if (!result.has_value()) {
Helpers::panic("Memory::mapSharedMemory: Failed to map shared memory block"); Helpers::panic("Memory::mapSharedMemory: Failed to map shared memory block");
return nullptr; return nullptr;
} }
e.mapped = true;
return &fcram[paddr]; return &fcram[paddr];
} }
} }
@ -474,24 +469,6 @@ u8* Memory::mapSharedMemory(Handle handle, u32 vaddr, u32 myPerms, u32 otherPerm
return nullptr; return nullptr;
} }
void Memory::mirrorMapping(u32 destAddress, u32 sourceAddress, u32 size) {
// Should theoretically be unreachable, only here for safety purposes
assert(isAligned(destAddress) && isAligned(sourceAddress) && isAligned(size));
const u32 pageCount = size / pageSize; // How many pages we need to mirror
for (u32 i = 0; i < pageCount; i++) {
// Redo the shift here to "properly" handle wrapping around the address space instead of reading OoB
const u32 sourcePage = sourceAddress / pageSize;
const u32 destPage = destAddress / pageSize;
readTable[destPage] = readTable[sourcePage];
writeTable[destPage] = writeTable[sourcePage];
sourceAddress += pageSize;
destAddress += pageSize;
}
}
// Get the number of ms since Jan 1 1900 // Get the number of ms since Jan 1 1900
u64 Memory::timeSince3DSEpoch() { u64 Memory::timeSince3DSEpoch() {
using namespace std::chrono; using namespace std::chrono;

View file

@ -1236,7 +1236,8 @@ void LDRService::initialize(u32 messagePointer) {
} }
// Map CRO to output address // Map CRO to output address
mem.mirrorMapping(mapVaddr, crsPointer, size); // TODO: how to handle permissions?
mem.mapVirtualMemory(mapVaddr, crsPointer, size >> 12, true, true, true);
CRO crs(mem, mapVaddr, false); CRO crs(mem, mapVaddr, false);
@ -1326,7 +1327,8 @@ void LDRService::loadCRO(u32 messagePointer, bool isNew) {
} }
// Map CRO to output address // Map CRO to output address
mem.mirrorMapping(mapVaddr, croPointer, size); // TODO: how to handle permissions?
mem.mapVirtualMemory(mapVaddr, croPointer, size >> 12, true, true, true);
CRO cro(mem, mapVaddr, true); CRO cro(mem, mapVaddr, true);
@ -1388,6 +1390,8 @@ void LDRService::unloadCRO(u32 messagePointer) {
Helpers::panic("Failed to unrebase CRO"); Helpers::panic("Failed to unrebase CRO");
} }
// TODO: unmap the CRO from the pagetable
kernel.clearInstructionCache(); kernel.clearInstructionCache();
mem.write32(messagePointer, IPC::responseHeader(0x5, 1, 0)); mem.write32(messagePointer, IPC::responseHeader(0x5, 1, 0));

View file

@ -17,7 +17,7 @@ __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 1;
#endif #endif
Emulator::Emulator() Emulator::Emulator()
: config(getConfigPath()), kernel(cpu, memory, gpu, config), cpu(memory, kernel, *this), gpu(memory, config), memory(cpu.getTicksRef(), config), : config(getConfigPath()), kernel(cpu, memory, gpu, config), cpu(memory, kernel, *this), gpu(memory, config), memory(kernel.getFcramManager(), cpu.getTicksRef(), config),
cheats(memory, kernel.getServiceManager().getHID()), lua(*this), running(false) cheats(memory, kernel.getServiceManager().getHID()), lua(*this), running(false)
#ifdef PANDA3DS_ENABLE_HTTP_SERVER #ifdef PANDA3DS_ENABLE_HTTP_SERVER
, ,