mirror of
https://github.com/wheremyfoodat/Panda3DS.git
synced 2025-04-09 15:45:40 +12:00
Accurately handle MemoryState for virtual memory
Previously all non-free blocks were marked as Reserved
This commit is contained in:
parent
f230384444
commit
9b20801dde
6 changed files with 108 additions and 63 deletions
|
@ -239,23 +239,14 @@ private:
|
|||
return (addr & pageMask) == 0;
|
||||
}
|
||||
|
||||
// Allocate "size" bytes of RAM starting from FCRAM index "paddr" (We pick it ourself if paddr == 0)
|
||||
// And map them to virtual address "vaddr" (We also pick it ourself if vaddr == 0).
|
||||
// If the "linear" flag is on, the paddr pages must be adjacent in FCRAM
|
||||
// This function is for interacting with the *user* portion of FCRAM mainly. For OS RAM, we use other internal functions below
|
||||
// r, w, x: Permissions for the allocated memory
|
||||
// adjustAddrs: If it's true paddr == 0 or vaddr == 0 tell the allocator to pick its own addresses. Used for eg svc ControlMemory
|
||||
// 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
|
||||
// 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,
|
||||
//bool adjustsAddrs = false, bool isMap = false);
|
||||
|
||||
bool allocMemory(u32 vaddr, s32 pages, FcramRegion region, bool r, bool w, bool x);
|
||||
bool allocMemory(u32 vaddr, s32 pages, FcramRegion region, bool r, bool w, bool x, KernelMemoryTypes::MemoryState state);
|
||||
bool allocMemoryLinear(u32& outVaddr, u32 inVaddr, s32 pages, FcramRegion region, bool r, bool w, bool x);
|
||||
bool mapPhysicalMemory(u32 vaddr, u32 paddr, s32 pages, bool r, bool w, bool x);
|
||||
bool mapVirtualMemory(u32 dstVaddr, u32 srcVaddr, s32 pages, bool r, bool w, bool x);
|
||||
bool mapPhysicalMemory(u32 vaddr, u32 paddr, s32 pages, bool r, bool w, bool x, KernelMemoryTypes::MemoryState state);
|
||||
bool mapVirtualMemory(u32 dstVaddr, u32 srcVaddr, s32 pages, bool r, bool w, bool x,
|
||||
KernelMemoryTypes::MemoryState oldDstState, KernelMemoryTypes::MemoryState oldSrcState,
|
||||
KernelMemoryTypes::MemoryState newDstState, KernelMemoryTypes::MemoryState newSrcState);
|
||||
Result::HorizonResult queryMemory(KernelMemoryTypes::MemoryInfo& out, u32 vaddr);
|
||||
Result::HorizonResult testMemoryState(u32 vaddr, s32 pages, KernelMemoryTypes::MemoryState desiredState);
|
||||
|
||||
void copyToVaddr(u32 dstVaddr, const u8* srcHost, s32 size);
|
||||
|
||||
|
@ -265,10 +256,6 @@ private:
|
|||
// Returns a pointer to the FCRAM block used for the memory if allocation succeeded
|
||||
u8* mapSharedMemory(Handle handle, u32 vaddr, u32 myPerms, u32 otherPerms);
|
||||
|
||||
// Mirrors the page mapping for "size" bytes starting from sourceAddress, to "size" bytes in destAddress
|
||||
// All of the above must be page-aligned.
|
||||
// void mirrorMapping(u32 destAddress, u32 sourceAddress, u32 size);
|
||||
|
||||
// Backup of the game's CXI partition info, if any
|
||||
std::optional<NCCH> loadedCXI = std::nullopt;
|
||||
std::optional<HB3DSX> loaded3DSX = std::nullopt;
|
||||
|
|
|
@ -16,6 +16,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
|
||||
|
@ -30,7 +32,7 @@ void Kernel::setupIdleThread() {
|
|||
// 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
|
||||
constexpr u32 codeAddress = 0x3FC00000;
|
||||
if (!mem.allocMemory(codeAddress, 1, FcramRegion::Base, true, true, false)) Helpers::panic("Failed to setup idle thread");
|
||||
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
|
||||
mem.copyToVaddr(codeAddress, idleThreadCode, sizeof(idleThreadCode));
|
||||
|
|
|
@ -30,6 +30,8 @@ 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;
|
||||
|
@ -78,7 +80,7 @@ void Kernel::controlMemory() {
|
|||
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");
|
||||
if (!mem.allocMemory(addr0, pages, region, r, w, false, MemoryState::Private)) Helpers::panic("ControlMemory: Failed to allocate memory");
|
||||
outAddr = addr0;
|
||||
}
|
||||
|
||||
|
@ -87,7 +89,8 @@ void Kernel::controlMemory() {
|
|||
}
|
||||
|
||||
case Operation::Map:
|
||||
if (!mem.mapVirtualMemory(addr0, addr1, pages, r, w, false)) Helpers::panic("ControlMemory: Failed to map memory");
|
||||
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::Protect:
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
#include "memory.hpp"
|
||||
#include "kernel/fcram.hpp"
|
||||
|
||||
using namespace KernelMemoryTypes;
|
||||
|
||||
bool Memory::mapCXI(NCSD& ncsd, NCCH& cxi) {
|
||||
printf("Text address = %08X, size = %08X\n", cxi.text.address, cxi.text.size);
|
||||
printf("Rodata address = %08X, size = %08X\n", cxi.rodata.address, cxi.rodata.size);
|
||||
|
@ -66,9 +68,9 @@ bool Memory::mapCXI(NCSD& ncsd, NCCH& cxi) {
|
|||
// TODO: base this off the exheader
|
||||
auto region = FcramRegion::App;
|
||||
|
||||
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
|
||||
allocMemory(textAddr, cxi.text.pageCount, region, true, false, true, MemoryState::Code);
|
||||
allocMemory(rodataAddr, cxi.rodata.pageCount, region, true, false, false, MemoryState::Code);
|
||||
allocMemory(dataAddr, cxi.data.pageCount + (bssSize >> 12), region, true, true, false, MemoryState::Private); // Merge data and BSS segments
|
||||
|
||||
// Copy .code file to FCRAM
|
||||
copyToVaddr(textAddr, code.data(), textSize);
|
||||
|
|
|
@ -40,7 +40,7 @@ void Memory::reset() {
|
|||
// Map 4 KB of FCRAM for each thread
|
||||
// TODO: the region should be taken from the exheader
|
||||
// TODO: each thread should only have 512 bytes - an FCRAM page should account for 8 threads
|
||||
assert(allocMemory(VirtualAddrs::TLSBase, appResourceLimits.maxThreads, FcramRegion::App, true, true, false));
|
||||
assert(allocMemory(VirtualAddrs::TLSBase, appResourceLimits.maxThreads, FcramRegion::App, true, true, false, MemoryState::Locked));
|
||||
|
||||
// Initialize shared memory blocks and reserve memory for them
|
||||
for (auto& e : sharedMemBlocks) {
|
||||
|
@ -62,7 +62,7 @@ void Memory::reset() {
|
|||
|
||||
u32 vaddr = VirtualAddrs::DSPMemStart;
|
||||
u32 paddr = vaddr;
|
||||
mapPhysicalMemory(vaddr, paddr, dspRamPages, true, true, false);
|
||||
mapPhysicalMemory(vaddr, paddr, dspRamPages, true, true, false, MemoryState::Static);
|
||||
|
||||
// Later adjusted based on ROM header when possible
|
||||
region = Regions::USA;
|
||||
|
@ -72,7 +72,7 @@ bool Memory::allocateMainThreadStack(u32 size) {
|
|||
// Map stack pages as R/W
|
||||
// TODO: get the region from the exheader
|
||||
const u32 stackBottom = VirtualAddrs::StackTop - size;
|
||||
return allocMemory(stackBottom, size >> 12, FcramRegion::App, true, true, false);
|
||||
return allocMemory(stackBottom, size >> 12, FcramRegion::App, true, true, false, MemoryState::Locked);
|
||||
}
|
||||
|
||||
u8 Memory::read8(u32 vaddr) {
|
||||
|
@ -285,15 +285,17 @@ std::string Memory::readString(u32 address, u32 maxSize) {
|
|||
// thanks to the New 3DS having more FCRAM
|
||||
u32 Memory::getLinearHeapVaddr() { return (kernelVersion < 0x22C) ? VirtualAddrs::LinearHeapStartOld : VirtualAddrs::LinearHeapStartNew; }
|
||||
|
||||
bool Memory::allocMemory(u32 vaddr, s32 pages, FcramRegion region, bool r, bool w, bool x)
|
||||
{
|
||||
bool Memory::allocMemory(u32 vaddr, s32 pages, FcramRegion region, bool r, bool w, bool x, MemoryState state) {
|
||||
auto res = testMemoryState(vaddr, pages, MemoryState::Free);
|
||||
if (res.isFailure()) return false;
|
||||
|
||||
FcramBlockList memList;
|
||||
fcramManager.alloc(memList, pages, region, false);
|
||||
|
||||
bool succeeded = true;
|
||||
|
||||
for (auto it = memList.begin(); it != memList.end(); it++) {
|
||||
succeeded = mapPhysicalMemory(vaddr, it->paddr, it->pages, r, w, x);
|
||||
succeeded = mapPhysicalMemory(vaddr, it->paddr, it->pages, r, w, x, state);
|
||||
if (!succeeded) break;
|
||||
vaddr += it->pages << 12;
|
||||
}
|
||||
|
@ -302,8 +304,7 @@ bool Memory::allocMemory(u32 vaddr, s32 pages, FcramRegion region, bool r, bool
|
|||
return succeeded;
|
||||
}
|
||||
|
||||
bool Memory::allocMemoryLinear(u32& outVaddr, u32 inVaddr, s32 pages, FcramRegion region, bool r, bool w, bool x)
|
||||
{
|
||||
bool Memory::allocMemoryLinear(u32& outVaddr, u32 inVaddr, s32 pages, FcramRegion region, bool r, bool w, bool x) {
|
||||
if (inVaddr) Helpers::panic("inVaddr specified for linear allocation!");
|
||||
|
||||
FcramBlockList memList;
|
||||
|
@ -311,19 +312,20 @@ bool Memory::allocMemoryLinear(u32& outVaddr, u32 inVaddr, s32 pages, FcramRegio
|
|||
|
||||
u32 paddr = memList.begin()->paddr;
|
||||
u32 vaddr = getLinearHeapVaddr() + paddr;
|
||||
if (!mapPhysicalMemory(vaddr, paddr, pages, r, w, x)) Helpers::panic("Failed to map linear memory!");
|
||||
if (!mapPhysicalMemory(vaddr, paddr, pages, r, w, x, MemoryState::Continuous)) 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)
|
||||
{
|
||||
bool Memory::mapPhysicalMemory(u32 vaddr, u32 paddr, s32 pages, bool r, bool w, bool x, MemoryState state) {
|
||||
assert(!(vaddr & 0xFFF));
|
||||
assert(!(paddr & 0xFFF));
|
||||
|
||||
bool blockFound = false;
|
||||
|
||||
for (auto it = memoryInfo.begin(); it != memoryInfo.end(); it++) {
|
||||
// Look for a free block that the requested memory region fits directly inside
|
||||
// Find the block that the memory region is located in
|
||||
u32 blockStart = it->baseAddr;
|
||||
u32 blockEnd = it->end();
|
||||
|
||||
|
@ -331,27 +333,46 @@ bool Memory::mapPhysicalMemory(u32 vaddr, u32 paddr, s32 pages, bool r, bool w,
|
|||
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
|
||||
auto oldState = it->state;
|
||||
|
||||
// Now that the block has been found, 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
|
||||
it->state = state; // 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 startBlock(blockStart, (reqStart - blockStart) >> 12, 0, oldState);
|
||||
memoryInfo.insert(it, startBlock);
|
||||
}
|
||||
|
||||
if (reqEnd < blockEnd) {
|
||||
auto itAfter = std::next(it);
|
||||
MemoryInfo endBlock(reqEnd, (blockEnd - reqEnd) >> 12, 0, MemoryState::Free);
|
||||
MemoryInfo endBlock(reqEnd, (blockEnd - reqEnd) >> 12, 0, oldState);
|
||||
memoryInfo.insert(itAfter, endBlock);
|
||||
}
|
||||
|
||||
// Fill the paddr table as well as the host pointer tables
|
||||
// TODO: if the current block is adjacent to blocks with the same state, merge them
|
||||
|
||||
blockFound = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!blockFound) Helpers::panic("Can't map physical memory!");
|
||||
|
||||
// Fill the paddr table as well as the host pointer tables
|
||||
// If the memory region is free, ignore the paddr, otherwise use it
|
||||
if (state == MemoryState::Free) {
|
||||
for (int i = 0; i < pages; i++) {
|
||||
u32 index = (vaddr >> 12) + i;
|
||||
paddrTable[index] = 0;
|
||||
readTable[index] = 0;
|
||||
writeTable[index] = 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// TODO: make this a separate function
|
||||
u8* hostPtr = nullptr;
|
||||
if (paddr < FCRAM_SIZE) {
|
||||
|
@ -361,39 +382,40 @@ bool Memory::mapPhysicalMemory(u32 vaddr, u32 paddr, s32 pages, bool r, bool w,
|
|||
hostPtr = dspRam + (paddr - VirtualAddrs::DSPMemStart);
|
||||
}
|
||||
|
||||
for (int i = 0; i < pages; i++) paddrTable[(vaddr >> 12) + i] = paddr + (i << 12);
|
||||
|
||||
if (r) {
|
||||
for (int i = 0; i < pages; i++) readTable[(vaddr >> 12) + i] = (uintptr_t)(hostPtr + (i << 12));
|
||||
for (int i = 0; i < pages; i++) {
|
||||
u32 index = (vaddr >> 12) + i;
|
||||
paddrTable[index] = paddr + (i << 12);
|
||||
if (r) readTable[index] = (uintptr_t)(hostPtr + (i << 12));
|
||||
if (w) writeTable[index] = (uintptr_t)(hostPtr + (i << 12));
|
||||
}
|
||||
|
||||
if (w) {
|
||||
for (int i = 0; i < pages; i++) writeTable[(vaddr >> 12) + i] = (uintptr_t)(hostPtr + (i << 12));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Helpers::panic("Can't map physical memory!");
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Memory::mapVirtualMemory(u32 dstVaddr, u32 srcVaddr, s32 pages, bool r, bool w, bool x)
|
||||
{
|
||||
bool Memory::mapVirtualMemory(u32 dstVaddr, u32 srcVaddr, s32 pages, bool r, bool w, bool x, MemoryState oldDstState, MemoryState oldSrcState,
|
||||
MemoryState newDstState, MemoryState newSrcState) {
|
||||
// The regions must have the specified state
|
||||
auto res = testMemoryState(srcVaddr, pages, oldSrcState);
|
||||
if (res.isFailure()) return false;
|
||||
|
||||
res = testMemoryState(dstVaddr, pages, oldDstState);
|
||||
if (res.isFailure()) return false;
|
||||
|
||||
// Get a list of physical blocks in the source region
|
||||
FcramBlockList physicalList;
|
||||
|
||||
u32 oldSrcVaddr = srcVaddr;
|
||||
s32 srcPages = pages;
|
||||
for (auto& alloc : memoryInfo) {
|
||||
u32 blockStart = alloc.baseAddr;
|
||||
u32 blockEnd = alloc.end();
|
||||
|
||||
if (!(srcVaddr >= blockStart && srcVaddr < blockEnd)) continue;
|
||||
if (alloc.state == MemoryState::Free) Helpers::panic("Found free block when mapping virtual memory!");
|
||||
|
||||
s32 blockPaddr = paddrTable[srcVaddr >> 12];
|
||||
s32 blockPages = alloc.pages - ((srcVaddr - blockStart) >> 12);
|
||||
blockPages = std::min(pages, blockPages);
|
||||
blockPages = std::min(srcPages, blockPages);
|
||||
FcramBlock physicalBlock(blockPaddr, blockPages);
|
||||
physicalList.push_back(physicalBlock);
|
||||
|
||||
|
@ -406,9 +428,13 @@ bool Memory::mapVirtualMemory(u32 dstVaddr, u32 srcVaddr, s32 pages, bool r, boo
|
|||
|
||||
// Map each physical block
|
||||
// FIXME: this is O(n^2)...
|
||||
srcVaddr = oldSrcVaddr;
|
||||
for (auto& block : physicalList) {
|
||||
if (!mapPhysicalMemory(dstVaddr, block.paddr, block.pages, r, w, x)) Helpers::panic("Failed to map virtual memory!");
|
||||
// TODO: how do permissions on the source side work?
|
||||
if (!mapPhysicalMemory(srcVaddr, block.paddr, block.pages, true, true, false, newSrcState)) Helpers::panic("Failed to map src virtual memory!");
|
||||
if (!mapPhysicalMemory(dstVaddr, block.paddr, block.pages, r, w, x, newDstState)) Helpers::panic("Failed to map dst virtual memory!");
|
||||
|
||||
srcVaddr += block.pages << 12;
|
||||
dstVaddr += block.pages << 12;
|
||||
}
|
||||
|
||||
|
@ -430,6 +456,21 @@ Result::HorizonResult Memory::queryMemory(MemoryInfo& out, u32 vaddr) {
|
|||
return Result::FailurePlaceholder;
|
||||
}
|
||||
|
||||
Result::HorizonResult Memory::testMemoryState(u32 vaddr, s32 pages, MemoryState desiredState) {
|
||||
for (auto& alloc : memoryInfo) {
|
||||
// Don't bother checking if we're to the left of the requested region
|
||||
if (vaddr >= alloc.end()) continue;
|
||||
|
||||
if (alloc.state != desiredState) return Result::FailurePlaceholder; // TODO: error for state mismatch
|
||||
|
||||
// If the end of this block comes after the end of the requested range with no errors, it's a success
|
||||
if (alloc.end() >= vaddr + (pages << 12)) return Result::Success;
|
||||
}
|
||||
|
||||
// TODO: error for when address is outside of userland
|
||||
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);
|
||||
|
@ -454,7 +495,7 @@ u8* Memory::mapSharedMemory(Handle handle, u32 vaddr, u32 myPerms, u32 otherPerm
|
|||
bool w = myPerms & 0b010;
|
||||
bool x = myPerms & 0b100;
|
||||
|
||||
if (!mapPhysicalMemory(vaddr, paddr, size >> 12, true, true, false)) {
|
||||
if (!mapPhysicalMemory(vaddr, paddr, size >> 12, true, true, false, MemoryState::Shared)) {
|
||||
Helpers::panic("Memory::mapSharedMemory: Failed to map shared memory block");
|
||||
return nullptr;
|
||||
}
|
||||
|
|
|
@ -141,6 +141,8 @@ static const std::string CRO_MAGIC("CRO0");
|
|||
static const std::string CRO_MAGIC_FIXED("FIXD");
|
||||
static const std::string CRR_MAGIC("CRR0");
|
||||
|
||||
using namespace KernelMemoryTypes;
|
||||
|
||||
class CRO {
|
||||
Memory &mem;
|
||||
|
||||
|
@ -1237,7 +1239,11 @@ void LDRService::initialize(u32 messagePointer) {
|
|||
|
||||
// Map CRO to output address
|
||||
// TODO: how to handle permissions?
|
||||
mem.mapVirtualMemory(mapVaddr, crsPointer, size >> 12, true, true, true);
|
||||
bool succeeded = mem.mapVirtualMemory(mapVaddr, crsPointer, size >> 12, true, true, true,
|
||||
MemoryState::Free, MemoryState::Private, MemoryState::Locked, MemoryState::AliasCode);
|
||||
if (!succeeded) {
|
||||
Helpers::panic("Failed to map CRS");
|
||||
}
|
||||
|
||||
CRO crs(mem, mapVaddr, false);
|
||||
|
||||
|
@ -1328,7 +1334,11 @@ void LDRService::loadCRO(u32 messagePointer, bool isNew) {
|
|||
|
||||
// Map CRO to output address
|
||||
// TODO: how to handle permissions?
|
||||
mem.mapVirtualMemory(mapVaddr, croPointer, size >> 12, true, true, true);
|
||||
bool succeeded = mem.mapVirtualMemory(mapVaddr, croPointer, size >> 12, true, true, true,
|
||||
MemoryState::Free, MemoryState::Private, MemoryState::Locked, MemoryState::AliasCode);
|
||||
if (!succeeded) {
|
||||
Helpers::panic("Failed to map CRO");
|
||||
}
|
||||
|
||||
CRO cro(mem, mapVaddr, true);
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue