From 30f121d1ac2305f2ed633433c3b0afc5823e46f1 Mon Sep 17 00:00:00 2001 From: bunnei Date: Fri, 3 Aug 2018 23:05:02 -0400 Subject: [PATCH] Revert "core/memory: Get rid of 3DS leftovers" --- src/core/CMakeLists.txt | 2 + src/core/core.cpp | 6 +- src/core/core.h | 3 +- src/core/hle/kernel/kernel.cpp | 6 +- src/core/hle/kernel/kernel.h | 2 +- src/core/hle/kernel/memory.cpp | 90 ++++++++++++++++++++++ src/core/hle/kernel/memory.h | 34 +++++++++ src/core/hle/kernel/process.cpp | 78 ++++++++++++++++++- src/core/hle/kernel/process.h | 17 ++++- src/core/hle/kernel/shared_memory.cpp | 59 +++++++++++--- src/core/hle/kernel/shared_memory.h | 3 + src/core/hle/kernel/thread.cpp | 33 +++++++- src/core/memory.cpp | 106 ++++++++++++++++++++++++++ src/core/memory.h | 88 ++++++++++++++++++++- src/tests/CMakeLists.txt | 1 + src/tests/core/memory/memory.cpp | 56 ++++++++++++++ 16 files changed, 557 insertions(+), 27 deletions(-) create mode 100644 src/core/hle/kernel/memory.cpp create mode 100644 src/core/hle/kernel/memory.h create mode 100644 src/tests/core/memory/memory.cpp diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 884c28e203..780a3affe1 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -63,6 +63,8 @@ add_library(core STATIC hle/kernel/hle_ipc.h hle/kernel/kernel.cpp hle/kernel/kernel.h + hle/kernel/memory.cpp + hle/kernel/memory.h hle/kernel/mutex.cpp hle/kernel/mutex.h hle/kernel/object.cpp diff --git a/src/core/core.cpp b/src/core/core.cpp index 9824769cfa..54fc4170ff 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -110,7 +110,7 @@ System::ResultStatus System::Load(EmuWindow& emu_window, const std::string& file } } - ResultStatus init_result{Init(emu_window)}; + ResultStatus init_result{Init(emu_window, system_mode.first.get())}; if (init_result != ResultStatus::Success) { LOG_CRITICAL(Core, "Failed to initialize system (Error {})!", static_cast(init_result)); @@ -161,7 +161,7 @@ Cpu& System::CpuCore(size_t core_index) { return *cpu_cores[core_index]; } -System::ResultStatus System::Init(EmuWindow& emu_window) { +System::ResultStatus System::Init(EmuWindow& emu_window, u32 system_mode) { LOG_DEBUG(HW_Memory, "initialized OK"); CoreTiming::Init(); @@ -178,7 +178,7 @@ System::ResultStatus System::Init(EmuWindow& emu_window) { telemetry_session = std::make_unique(); service_manager = std::make_shared(); - Kernel::Init(); + Kernel::Init(system_mode); Service::Init(service_manager); GDBStub::Init(); diff --git a/src/core/core.h b/src/core/core.h index ed475ac4ef..f8b6644bb9 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -189,9 +189,10 @@ private: * Initialize the emulated system. * @param emu_window Reference to the host-system window used for video output and keyboard * input. + * @param system_mode The system mode. * @return ResultStatus code, indicating if the operation succeeded. */ - ResultStatus Init(EmuWindow& emu_window); + ResultStatus Init(EmuWindow& emu_window, u32 system_mode); /// AppLoader used to load the current executing application std::unique_ptr app_loader; diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index 1b0cd0abf9..3eb4f465ca 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -4,6 +4,7 @@ #include "core/hle/kernel/handle_table.h" #include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/memory.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/resource_limit.h" #include "core/hle/kernel/thread.h" @@ -14,7 +15,9 @@ namespace Kernel { unsigned int Object::next_object_id; /// Initialize the kernel -void Init() { +void Init(u32 system_mode) { + Kernel::MemoryInit(system_mode); + Kernel::ResourceLimitsInit(); Kernel::ThreadingInit(); Kernel::TimersInit(); @@ -34,6 +37,7 @@ void Shutdown() { Kernel::TimersShutdown(); Kernel::ResourceLimitsShutdown(); + Kernel::MemoryShutdown(); } } // namespace Kernel diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index 1313114720..2bc45d7db0 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -9,7 +9,7 @@ namespace Kernel { /// Initialize the kernel with the specified system mode. -void Init(); +void Init(u32 system_mode); /// Shutdown the kernel void Shutdown(); diff --git a/src/core/hle/kernel/memory.cpp b/src/core/hle/kernel/memory.cpp new file mode 100644 index 0000000000..a7f3c3c5a7 --- /dev/null +++ b/src/core/hle/kernel/memory.cpp @@ -0,0 +1,90 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include +#include "common/assert.h" +#include "common/common_types.h" +#include "common/logging/log.h" +#include "core/hle/kernel/memory.h" +#include "core/hle/kernel/process.h" +#include "core/hle/kernel/vm_manager.h" +#include "core/memory.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +namespace Kernel { + +MemoryRegionInfo memory_regions[3]; + +/// Size of the APPLICATION, SYSTEM and BASE memory regions (respectively) for each system +/// memory configuration type. +static const u32 memory_region_sizes[8][3] = { + // Old 3DS layouts + {0x04000000, 0x02C00000, 0x01400000}, // 0 + {/* This appears to be unused. */}, // 1 + {0x06000000, 0x00C00000, 0x01400000}, // 2 + {0x05000000, 0x01C00000, 0x01400000}, // 3 + {0x04800000, 0x02400000, 0x01400000}, // 4 + {0x02000000, 0x04C00000, 0x01400000}, // 5 + + // New 3DS layouts + {0x07C00000, 0x06400000, 0x02000000}, // 6 + {0x0B200000, 0x02E00000, 0x02000000}, // 7 +}; + +void MemoryInit(u32 mem_type) { + // TODO(yuriks): On the n3DS, all o3DS configurations (<=5) are forced to 6 instead. + ASSERT_MSG(mem_type <= 5, "New 3DS memory configuration aren't supported yet!"); + ASSERT(mem_type != 1); + + // The kernel allocation regions (APPLICATION, SYSTEM and BASE) are laid out in sequence, with + // the sizes specified in the memory_region_sizes table. + VAddr base = 0; + for (int i = 0; i < 3; ++i) { + memory_regions[i].base = base; + memory_regions[i].size = memory_region_sizes[mem_type][i]; + memory_regions[i].used = 0; + memory_regions[i].linear_heap_memory = std::make_shared>(); + // Reserve enough space for this region of FCRAM. + // We do not want this block of memory to be relocated when allocating from it. + memory_regions[i].linear_heap_memory->reserve(memory_regions[i].size); + + base += memory_regions[i].size; + } + + // We must've allocated the entire FCRAM by the end + ASSERT(base == Memory::FCRAM_SIZE); +} + +void MemoryShutdown() { + for (auto& region : memory_regions) { + region.base = 0; + region.size = 0; + region.used = 0; + region.linear_heap_memory = nullptr; + } +} + +MemoryRegionInfo* GetMemoryRegion(MemoryRegion region) { + switch (region) { + case MemoryRegion::APPLICATION: + return &memory_regions[0]; + case MemoryRegion::SYSTEM: + return &memory_regions[1]; + case MemoryRegion::BASE: + return &memory_regions[2]; + default: + UNREACHABLE(); + } +} + +void HandleSpecialMapping(VMManager& address_space, const AddressMapping& mapping) {} + +void MapSharedPages(VMManager& address_space) {} + +} // namespace Kernel diff --git a/src/core/hle/kernel/memory.h b/src/core/hle/kernel/memory.h new file mode 100644 index 0000000000..1d05b88711 --- /dev/null +++ b/src/core/hle/kernel/memory.h @@ -0,0 +1,34 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +#include "common/common_types.h" + +namespace Kernel { + +class VMManager; +enum class MemoryRegion : u16; +struct AddressMapping; + +struct MemoryRegionInfo { + u64 base; // Not an address, but offset from start of FCRAM + u64 size; + u64 used; + + std::shared_ptr> linear_heap_memory; +}; + +void MemoryInit(u32 mem_type); +void MemoryShutdown(); +MemoryRegionInfo* GetMemoryRegion(MemoryRegion region); + +void HandleSpecialMapping(VMManager& address_space, const AddressMapping& mapping); +void MapSharedPages(VMManager& address_space); + +extern MemoryRegionInfo memory_regions[3]; +} // namespace Kernel diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp index 5403ceef55..0c05060854 100644 --- a/src/core/hle/kernel/process.cpp +++ b/src/core/hle/kernel/process.cpp @@ -8,6 +8,7 @@ #include "common/common_funcs.h" #include "common/logging/log.h" #include "core/hle/kernel/errors.h" +#include "core/hle/kernel/memory.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/resource_limit.h" #include "core/hle/kernel/thread.h" @@ -124,6 +125,14 @@ void Process::Run(VAddr entry_point, s32 main_thread_priority, u32 stack_size) { std::make_shared>(stack_size, 0), 0, stack_size, MemoryState::Mapped) .Unwrap(); + misc_memory_used += stack_size; + memory_region->used += stack_size; + + // Map special address mappings + MapSharedPages(vm_manager); + for (const auto& mapping : address_mappings) { + HandleSpecialMapping(vm_manager, mapping); + } vm_manager.LogLayout(); status = ProcessStatus::Running; @@ -132,13 +141,17 @@ void Process::Run(VAddr entry_point, s32 main_thread_priority, u32 stack_size) { } void Process::LoadModule(SharedPtr module_, VAddr base_addr) { - const auto MapSegment = [&](CodeSet::Segment& segment, VMAPermission permissions, - MemoryState memory_state) { + memory_region = GetMemoryRegion(flags.memory_region); + + auto MapSegment = [&](CodeSet::Segment& segment, VMAPermission permissions, + MemoryState memory_state) { auto vma = vm_manager .MapMemoryBlock(segment.addr + base_addr, module_->memory, segment.offset, segment.size, memory_state) .Unwrap(); vm_manager.Reprotect(vma, permissions); + misc_memory_used += segment.size; + memory_region->used += segment.size; }; // Map CodeSet segments @@ -147,6 +160,20 @@ void Process::LoadModule(SharedPtr module_, VAddr base_addr) { MapSegment(module_->data, VMAPermission::ReadWrite, MemoryState::CodeMutable); } +VAddr Process::GetLinearHeapAreaAddress() const { + // Starting from system version 8.0.0 a new linear heap layout is supported to allow usage of + // the extra RAM in the n3DS. + return kernel_version < 0x22C ? Memory::LINEAR_HEAP_VADDR : Memory::NEW_LINEAR_HEAP_VADDR; +} + +VAddr Process::GetLinearHeapBase() const { + return GetLinearHeapAreaAddress() + memory_region->base; +} + +VAddr Process::GetLinearHeapLimit() const { + return GetLinearHeapBase() + memory_region->size; +} + ResultVal Process::HeapAllocate(VAddr target, u64 size, VMAPermission perms) { if (target < Memory::HEAP_VADDR || target + size > Memory::HEAP_VADDR_END || target + size < target) { @@ -179,6 +206,7 @@ ResultVal Process::HeapAllocate(VAddr target, u64 size, VMAPermission per vm_manager.Reprotect(vma, perms); heap_used = size; + memory_region->used += size; return MakeResult(heap_end - size); } @@ -198,6 +226,52 @@ ResultCode Process::HeapFree(VAddr target, u32 size) { return result; heap_used -= size; + memory_region->used -= size; + + return RESULT_SUCCESS; +} + +ResultVal Process::LinearAllocate(VAddr target, u32 size, VMAPermission perms) { + UNIMPLEMENTED(); + return {}; +} + +ResultCode Process::LinearFree(VAddr target, u32 size) { + auto& linheap_memory = memory_region->linear_heap_memory; + + if (target < GetLinearHeapBase() || target + size > GetLinearHeapLimit() || + target + size < target) { + + return ERR_INVALID_ADDRESS; + } + + if (size == 0) { + return RESULT_SUCCESS; + } + + VAddr heap_end = GetLinearHeapBase() + (u32)linheap_memory->size(); + if (target + size > heap_end) { + return ERR_INVALID_ADDRESS_STATE; + } + + ResultCode result = vm_manager.UnmapRange(target, size); + if (result.IsError()) + return result; + + linear_heap_used -= size; + memory_region->used -= size; + + if (target + size == heap_end) { + // End of linear heap has been freed, so check what's the last allocated block in it and + // reduce the size. + auto vma = vm_manager.FindVMA(target); + ASSERT(vma != vm_manager.vma_map.end()); + ASSERT(vma->second.type == VMAType::Free); + VAddr new_end = vma->second.base; + if (new_end >= GetLinearHeapBase()) { + linheap_memory->resize(new_end - GetLinearHeapBase()); + } + } return RESULT_SUCCESS; } diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h index 98d8da35e1..1204026bed 100644 --- a/src/core/hle/kernel/process.h +++ b/src/core/hle/kernel/process.h @@ -53,6 +53,7 @@ union ProcessFlags { enum class ProcessStatus { Created, Running, Exited }; class ResourceLimit; +struct MemoryRegionInfo; struct CodeSet final : public Object { static SharedPtr Create(std::string name); @@ -162,11 +163,12 @@ public: // This makes deallocation and reallocation of holes fast and keeps process memory contiguous // in the emulator address space, allowing Memory::GetPointer to be reasonably safe. std::shared_ptr> heap_memory; - // The left/right bounds of the address space covered by heap_memory. - VAddr heap_start = 0; - VAddr heap_end = 0; - u64 heap_used = 0; + VAddr heap_start = 0, heap_end = 0; + + u64 heap_used = 0, linear_heap_used = 0, misc_memory_used = 0; + + MemoryRegionInfo* memory_region = nullptr; /// The Thread Local Storage area is allocated as processes create threads, /// each TLS area is 0x200 bytes, so one page (0x1000) is split up in 8 parts, and each part @@ -177,9 +179,16 @@ public: std::string name; + VAddr GetLinearHeapAreaAddress() const; + VAddr GetLinearHeapBase() const; + VAddr GetLinearHeapLimit() const; + ResultVal HeapAllocate(VAddr target, u64 size, VMAPermission perms); ResultCode HeapFree(VAddr target, u32 size); + ResultVal LinearAllocate(VAddr target, u32 size, VMAPermission perms); + ResultCode LinearFree(VAddr target, u32 size); + ResultCode MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size); ResultCode UnmapMemory(VAddr dst_addr, VAddr src_addr, u64 size); diff --git a/src/core/hle/kernel/shared_memory.cpp b/src/core/hle/kernel/shared_memory.cpp index b3ddebb3d4..a5b11bd873 100644 --- a/src/core/hle/kernel/shared_memory.cpp +++ b/src/core/hle/kernel/shared_memory.cpp @@ -8,6 +8,7 @@ #include "common/logging/log.h" #include "core/core.h" #include "core/hle/kernel/errors.h" +#include "core/hle/kernel/memory.h" #include "core/hle/kernel/shared_memory.h" #include "core/memory.h" @@ -28,22 +29,51 @@ SharedPtr SharedMemory::Create(SharedPtr owner_process, u shared_memory->permissions = permissions; shared_memory->other_permissions = other_permissions; - auto& vm_manager = shared_memory->owner_process->vm_manager; + if (address == 0) { + // We need to allocate a block from the Linear Heap ourselves. + // We'll manually allocate some memory from the linear heap in the specified region. + MemoryRegionInfo* memory_region = GetMemoryRegion(region); + auto& linheap_memory = memory_region->linear_heap_memory; - // The memory is already available and mapped in the owner process. - auto vma = vm_manager.FindVMA(address); - ASSERT_MSG(vma != vm_manager.vma_map.end(), "Invalid memory address"); - ASSERT_MSG(vma->second.backing_block, "Backing block doesn't exist for address"); + ASSERT_MSG(linheap_memory->size() + size <= memory_region->size, + "Not enough space in region to allocate shared memory!"); - // The returned VMA might be a bigger one encompassing the desired address. - auto vma_offset = address - vma->first; - ASSERT_MSG(vma_offset + size <= vma->second.size, - "Shared memory exceeds bounds of mapped block"); + shared_memory->backing_block = linheap_memory; + shared_memory->backing_block_offset = linheap_memory->size(); + // Allocate some memory from the end of the linear heap for this region. + linheap_memory->insert(linheap_memory->end(), size, 0); + memory_region->used += size; + + shared_memory->linear_heap_phys_address = + Memory::FCRAM_PADDR + memory_region->base + + static_cast(shared_memory->backing_block_offset); + + // Increase the amount of used linear heap memory for the owner process. + if (shared_memory->owner_process != nullptr) { + shared_memory->owner_process->linear_heap_used += size; + } + + // Refresh the address mappings for the current process. + if (Core::CurrentProcess() != nullptr) { + Core::CurrentProcess()->vm_manager.RefreshMemoryBlockMappings(linheap_memory.get()); + } + } else { + auto& vm_manager = shared_memory->owner_process->vm_manager; + // The memory is already available and mapped in the owner process. + auto vma = vm_manager.FindVMA(address); + ASSERT_MSG(vma != vm_manager.vma_map.end(), "Invalid memory address"); + ASSERT_MSG(vma->second.backing_block, "Backing block doesn't exist for address"); + + // The returned VMA might be a bigger one encompassing the desired address. + auto vma_offset = address - vma->first; + ASSERT_MSG(vma_offset + size <= vma->second.size, + "Shared memory exceeds bounds of mapped block"); + + shared_memory->backing_block = vma->second.backing_block; + shared_memory->backing_block_offset = vma->second.offset + vma_offset; + } - shared_memory->backing_block = vma->second.backing_block; - shared_memory->backing_block_offset = vma->second.offset + vma_offset; shared_memory->base_address = address; - return shared_memory; } @@ -94,6 +124,11 @@ ResultCode SharedMemory::Map(Process* target_process, VAddr address, MemoryPermi VAddr target_address = address; + if (base_address == 0 && target_address == 0) { + // Calculate the address at which to map the memory block. + target_address = Memory::PhysicalToVirtualAddress(linear_heap_phys_address).value(); + } + // Map the memory block into the target process auto result = target_process->vm_manager.MapMemoryBlock( target_address, backing_block, backing_block_offset, size, MemoryState::Shared); diff --git a/src/core/hle/kernel/shared_memory.h b/src/core/hle/kernel/shared_memory.h index c50fee615f..8a6f685296 100644 --- a/src/core/hle/kernel/shared_memory.h +++ b/src/core/hle/kernel/shared_memory.h @@ -111,6 +111,9 @@ public: SharedPtr owner_process; /// Address of shared memory block in the owner process if specified. VAddr base_address; + /// Physical address of the shared memory block in the linear heap if no address was specified + /// during creation. + PAddr linear_heap_phys_address; /// Backing memory for this shared memory block. std::shared_ptr> backing_block; /// Offset into the backing block for this shared memory. diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index ea9554cbb5..cdb8120f29 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -20,6 +20,7 @@ #include "core/core_timing_util.h" #include "core/hle/kernel/errors.h" #include "core/hle/kernel/handle_table.h" +#include "core/hle/kernel/memory.h" #include "core/hle/kernel/object.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/thread.h" @@ -80,8 +81,8 @@ void Thread::Stop() { wait_objects.clear(); // Mark the TLS slot in the thread's page as free. - const u64 tls_page = (tls_address - Memory::TLS_AREA_VADDR) / Memory::PAGE_SIZE; - const u64 tls_slot = + u64 tls_page = (tls_address - Memory::TLS_AREA_VADDR) / Memory::PAGE_SIZE; + u64 tls_slot = ((tls_address - Memory::TLS_AREA_VADDR) % Memory::PAGE_SIZE) / Memory::TLS_ENTRY_SIZE; Core::CurrentProcess()->tls_slots[tls_page].reset(tls_slot); } @@ -335,10 +336,38 @@ ResultVal> Thread::Create(std::string name, VAddr entry_point, auto& tls_slots = owner_process->tls_slots; auto [available_page, available_slot, needs_allocation] = GetFreeThreadLocalSlot(tls_slots); + if (needs_allocation) { + // There are no already-allocated pages with free slots, lets allocate a new one. + // TLS pages are allocated from the BASE region in the linear heap. + MemoryRegionInfo* memory_region = GetMemoryRegion(MemoryRegion::BASE); + auto& linheap_memory = memory_region->linear_heap_memory; + + if (linheap_memory->size() + Memory::PAGE_SIZE > memory_region->size) { + LOG_ERROR(Kernel_SVC, + "Not enough space in region to allocate a new TLS page for thread"); + return ERR_OUT_OF_MEMORY; + } + + size_t offset = linheap_memory->size(); + + // Allocate some memory from the end of the linear heap for this region. + linheap_memory->insert(linheap_memory->end(), Memory::PAGE_SIZE, 0); + memory_region->used += Memory::PAGE_SIZE; + owner_process->linear_heap_used += Memory::PAGE_SIZE; + tls_slots.emplace_back(0); // The page is completely available at the start available_page = tls_slots.size() - 1; available_slot = 0; // Use the first slot in the new page + + auto& vm_manager = owner_process->vm_manager; + vm_manager.RefreshMemoryBlockMappings(linheap_memory.get()); + + // Map the page to the current process' address space. + // TODO(Subv): Find the correct MemoryState for this region. + vm_manager.MapMemoryBlock(Memory::TLS_AREA_VADDR + available_page * Memory::PAGE_SIZE, + linheap_memory, offset, Memory::PAGE_SIZE, + MemoryState::ThreadLocal); } // Mark the slot as used diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 0d41b25275..e753e34361 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -14,6 +14,7 @@ #include "common/swap.h" #include "core/arm/arm_interface.h" #include "core/core.h" +#include "core/hle/kernel/memory.h" #include "core/hle/kernel/process.h" #include "core/hle/lock.h" #include "core/memory.h" @@ -23,6 +24,8 @@ namespace Memory { +static std::array vram; + static PageTable* current_page_table = nullptr; void SetCurrentPageTable(PageTable* page_table) { @@ -239,6 +242,10 @@ bool IsKernelVirtualAddress(const VAddr vaddr) { return KERNEL_REGION_VADDR <= vaddr && vaddr < KERNEL_REGION_END; } +bool IsValidPhysicalAddress(const PAddr paddr) { + return GetPhysicalPointer(paddr) != nullptr; +} + u8* GetPointer(const VAddr vaddr) { u8* page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS]; if (page_pointer) { @@ -267,6 +274,61 @@ std::string ReadCString(VAddr vaddr, std::size_t max_length) { return string; } +u8* GetPhysicalPointer(PAddr address) { + struct MemoryArea { + PAddr paddr_base; + u32 size; + }; + + static constexpr MemoryArea memory_areas[] = { + {VRAM_PADDR, VRAM_SIZE}, + {IO_AREA_PADDR, IO_AREA_SIZE}, + {DSP_RAM_PADDR, DSP_RAM_SIZE}, + {FCRAM_PADDR, FCRAM_N3DS_SIZE}, + }; + + const auto area = + std::find_if(std::begin(memory_areas), std::end(memory_areas), [&](const auto& area) { + return address >= area.paddr_base && address < area.paddr_base + area.size; + }); + + if (area == std::end(memory_areas)) { + LOG_ERROR(HW_Memory, "Unknown GetPhysicalPointer @ 0x{:016X}", address); + return nullptr; + } + + if (area->paddr_base == IO_AREA_PADDR) { + LOG_ERROR(HW_Memory, "MMIO mappings are not supported yet. phys_addr={:016X}", address); + return nullptr; + } + + u64 offset_into_region = address - area->paddr_base; + + u8* target_pointer = nullptr; + switch (area->paddr_base) { + case VRAM_PADDR: + target_pointer = vram.data() + offset_into_region; + break; + case DSP_RAM_PADDR: + break; + case FCRAM_PADDR: + for (const auto& region : Kernel::memory_regions) { + if (offset_into_region >= region.base && + offset_into_region < region.base + region.size) { + target_pointer = + region.linear_heap_memory->data() + offset_into_region - region.base; + break; + } + } + ASSERT_MSG(target_pointer != nullptr, "Invalid FCRAM address"); + break; + default: + UNREACHABLE(); + } + + return target_pointer; +} + void RasterizerMarkRegionCached(Tegra::GPUVAddr gpu_addr, u64 size, bool cached) { if (gpu_addr == 0) { return; @@ -604,4 +666,48 @@ void CopyBlock(VAddr dest_addr, VAddr src_addr, size_t size) { CopyBlock(*Core::CurrentProcess(), dest_addr, src_addr, size); } +boost::optional TryVirtualToPhysicalAddress(const VAddr addr) { + if (addr == 0) { + return 0; + } else if (addr >= VRAM_VADDR && addr < VRAM_VADDR_END) { + return addr - VRAM_VADDR + VRAM_PADDR; + } else if (addr >= LINEAR_HEAP_VADDR && addr < LINEAR_HEAP_VADDR_END) { + return addr - LINEAR_HEAP_VADDR + FCRAM_PADDR; + } else if (addr >= NEW_LINEAR_HEAP_VADDR && addr < NEW_LINEAR_HEAP_VADDR_END) { + return addr - NEW_LINEAR_HEAP_VADDR + FCRAM_PADDR; + } else if (addr >= DSP_RAM_VADDR && addr < DSP_RAM_VADDR_END) { + return addr - DSP_RAM_VADDR + DSP_RAM_PADDR; + } else if (addr >= IO_AREA_VADDR && addr < IO_AREA_VADDR_END) { + return addr - IO_AREA_VADDR + IO_AREA_PADDR; + } + + return boost::none; +} + +PAddr VirtualToPhysicalAddress(const VAddr addr) { + auto paddr = TryVirtualToPhysicalAddress(addr); + if (!paddr) { + LOG_ERROR(HW_Memory, "Unknown virtual address @ 0x{:016X}", addr); + // To help with debugging, set bit on address so that it's obviously invalid. + return addr | 0x80000000; + } + return *paddr; +} + +boost::optional PhysicalToVirtualAddress(const PAddr addr) { + if (addr == 0) { + return 0; + } else if (addr >= VRAM_PADDR && addr < VRAM_PADDR_END) { + return addr - VRAM_PADDR + VRAM_VADDR; + } else if (addr >= FCRAM_PADDR && addr < FCRAM_PADDR_END) { + return addr - FCRAM_PADDR + Core::CurrentProcess()->GetLinearHeapAreaAddress(); + } else if (addr >= DSP_RAM_PADDR && addr < DSP_RAM_PADDR_END) { + return addr - DSP_RAM_PADDR + DSP_RAM_VADDR; + } else if (addr >= IO_AREA_PADDR && addr < IO_AREA_PADDR_END) { + return addr - IO_AREA_PADDR + IO_AREA_VADDR; + } + + return boost::none; +} + } // namespace Memory diff --git a/src/core/memory.h b/src/core/memory.h index b5d885b8ad..8d5d017a46 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -6,9 +6,12 @@ #include #include +#include #include #include +#include #include +#include #include "common/common_types.h" #include "core/memory_hook.h" #include "video_core/memory_manager.h" @@ -82,6 +85,40 @@ struct PageTable { std::array attributes; }; +/// Physical memory regions as seen from the ARM11 +enum : PAddr { + /// IO register area + IO_AREA_PADDR = 0x10100000, + IO_AREA_SIZE = 0x01000000, ///< IO area size (16MB) + IO_AREA_PADDR_END = IO_AREA_PADDR + IO_AREA_SIZE, + + /// MPCore internal memory region + MPCORE_RAM_PADDR = 0x17E00000, + MPCORE_RAM_SIZE = 0x00002000, ///< MPCore internal memory size (8KB) + MPCORE_RAM_PADDR_END = MPCORE_RAM_PADDR + MPCORE_RAM_SIZE, + + /// Video memory + VRAM_PADDR = 0x18000000, + VRAM_SIZE = 0x00600000, ///< VRAM size (6MB) + VRAM_PADDR_END = VRAM_PADDR + VRAM_SIZE, + + /// DSP memory + DSP_RAM_PADDR = 0x1FF00000, + DSP_RAM_SIZE = 0x00080000, ///< DSP memory size (512KB) + DSP_RAM_PADDR_END = DSP_RAM_PADDR + DSP_RAM_SIZE, + + /// AXI WRAM + AXI_WRAM_PADDR = 0x1FF80000, + AXI_WRAM_SIZE = 0x00080000, ///< AXI WRAM size (512KB) + AXI_WRAM_PADDR_END = AXI_WRAM_PADDR + AXI_WRAM_SIZE, + + /// Main FCRAM + FCRAM_PADDR = 0x20000000, + FCRAM_SIZE = 0x08000000, ///< FCRAM size on the Old 3DS (128MB) + FCRAM_N3DS_SIZE = 0x10000000, ///< FCRAM size on the New 3DS (256MB) + FCRAM_PADDR_END = FCRAM_PADDR + FCRAM_SIZE, +}; + /// Virtual user-space memory regions enum : VAddr { /// Where the application text, data and bss reside. @@ -89,6 +126,24 @@ enum : VAddr { PROCESS_IMAGE_MAX_SIZE = 0x08000000, PROCESS_IMAGE_VADDR_END = PROCESS_IMAGE_VADDR + PROCESS_IMAGE_MAX_SIZE, + /// Maps 1:1 to an offset in FCRAM. Used for HW allocations that need to be linear in physical + /// memory. + LINEAR_HEAP_VADDR = 0x14000000, + LINEAR_HEAP_SIZE = 0x08000000, + LINEAR_HEAP_VADDR_END = LINEAR_HEAP_VADDR + LINEAR_HEAP_SIZE, + + /// Maps 1:1 to the IO register area. + IO_AREA_VADDR = 0x1EC00000, + IO_AREA_VADDR_END = IO_AREA_VADDR + IO_AREA_SIZE, + + /// Maps 1:1 to VRAM. + VRAM_VADDR = 0x1F000000, + VRAM_VADDR_END = VRAM_VADDR + VRAM_SIZE, + + /// Maps 1:1 to DSP memory. + DSP_RAM_VADDR = 0x1FF00000, + DSP_RAM_VADDR_END = DSP_RAM_VADDR + DSP_RAM_SIZE, + /// Read-only page containing kernel and system configuration values. CONFIG_MEMORY_VADDR = 0x1FF80000, CONFIG_MEMORY_SIZE = 0x00001000, @@ -99,8 +154,13 @@ enum : VAddr { SHARED_PAGE_SIZE = 0x00001000, SHARED_PAGE_VADDR_END = SHARED_PAGE_VADDR + SHARED_PAGE_SIZE, + /// Equivalent to LINEAR_HEAP_VADDR, but expanded to cover the extra memory in the New 3DS. + NEW_LINEAR_HEAP_VADDR = 0x30000000, + NEW_LINEAR_HEAP_SIZE = 0x10000000, + NEW_LINEAR_HEAP_VADDR_END = NEW_LINEAR_HEAP_VADDR + NEW_LINEAR_HEAP_SIZE, + /// Area where TLS (Thread-Local Storage) buffers are allocated. - TLS_AREA_VADDR = 0x40000000, + TLS_AREA_VADDR = NEW_LINEAR_HEAP_VADDR_END, TLS_ENTRY_SIZE = 0x200, TLS_AREA_SIZE = 0x10000000, TLS_AREA_VADDR_END = TLS_AREA_VADDR + TLS_AREA_SIZE, @@ -145,6 +205,8 @@ bool IsValidVirtualAddress(const VAddr addr); /// Determines if the given VAddr is a kernel address bool IsKernelVirtualAddress(const VAddr addr); +bool IsValidPhysicalAddress(const PAddr addr); + u8 Read8(VAddr addr); u16 Read16(VAddr addr); u32 Read32(VAddr addr); @@ -168,6 +230,30 @@ u8* GetPointer(VAddr virtual_address); std::string ReadCString(VAddr virtual_address, std::size_t max_length); +/** + * Converts a virtual address inside a region with 1:1 mapping to physical memory to a physical + * address. This should be used by services to translate addresses for use by the hardware. + */ +boost::optional TryVirtualToPhysicalAddress(VAddr addr); + +/** + * Converts a virtual address inside a region with 1:1 mapping to physical memory to a physical + * address. This should be used by services to translate addresses for use by the hardware. + * + * @deprecated Use TryVirtualToPhysicalAddress(), which reports failure. + */ +PAddr VirtualToPhysicalAddress(VAddr addr); + +/** + * Undoes a mapping performed by VirtualToPhysicalAddress(). + */ +boost::optional PhysicalToVirtualAddress(PAddr addr); + +/** + * Gets a pointer to the memory region beginning at the specified physical address. + */ +u8* GetPhysicalPointer(PAddr address); + enum class FlushMode { /// Write back modified surfaces to RAM Flush, diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 4d74bb3955..6a0a62ecc8 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -3,6 +3,7 @@ add_executable(tests core/arm/arm_test_common.cpp core/arm/arm_test_common.h core/core_timing.cpp + core/memory/memory.cpp glad.cpp tests.cpp ) diff --git a/src/tests/core/memory/memory.cpp b/src/tests/core/memory/memory.cpp new file mode 100644 index 0000000000..165496a548 --- /dev/null +++ b/src/tests/core/memory/memory.cpp @@ -0,0 +1,56 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include "core/hle/kernel/memory.h" +#include "core/hle/kernel/process.h" +#include "core/memory.h" + +TEST_CASE("Memory::IsValidVirtualAddress", "[core][memory][!hide]") { + SECTION("these regions should not be mapped on an empty process") { + auto process = Kernel::Process::Create(""); + CHECK(Memory::IsValidVirtualAddress(*process, Memory::PROCESS_IMAGE_VADDR) == false); + CHECK(Memory::IsValidVirtualAddress(*process, Memory::HEAP_VADDR) == false); + CHECK(Memory::IsValidVirtualAddress(*process, Memory::LINEAR_HEAP_VADDR) == false); + CHECK(Memory::IsValidVirtualAddress(*process, Memory::VRAM_VADDR) == false); + CHECK(Memory::IsValidVirtualAddress(*process, Memory::CONFIG_MEMORY_VADDR) == false); + CHECK(Memory::IsValidVirtualAddress(*process, Memory::SHARED_PAGE_VADDR) == false); + CHECK(Memory::IsValidVirtualAddress(*process, Memory::TLS_AREA_VADDR) == false); + } + + SECTION("CONFIG_MEMORY_VADDR and SHARED_PAGE_VADDR should be valid after mapping them") { + auto process = Kernel::Process::Create(""); + Kernel::MapSharedPages(process->vm_manager); + CHECK(Memory::IsValidVirtualAddress(*process, Memory::CONFIG_MEMORY_VADDR) == true); + CHECK(Memory::IsValidVirtualAddress(*process, Memory::SHARED_PAGE_VADDR) == true); + } + + SECTION("special regions should be valid after mapping them") { + auto process = Kernel::Process::Create(""); + SECTION("VRAM") { + Kernel::HandleSpecialMapping(process->vm_manager, + {Memory::VRAM_VADDR, Memory::VRAM_SIZE, false, false}); + CHECK(Memory::IsValidVirtualAddress(*process, Memory::VRAM_VADDR) == true); + } + + SECTION("IO (Not yet implemented)") { + Kernel::HandleSpecialMapping( + process->vm_manager, {Memory::IO_AREA_VADDR, Memory::IO_AREA_SIZE, false, false}); + CHECK_FALSE(Memory::IsValidVirtualAddress(*process, Memory::IO_AREA_VADDR) == true); + } + + SECTION("DSP") { + Kernel::HandleSpecialMapping( + process->vm_manager, {Memory::DSP_RAM_VADDR, Memory::DSP_RAM_SIZE, false, false}); + CHECK(Memory::IsValidVirtualAddress(*process, Memory::DSP_RAM_VADDR) == true); + } + } + + SECTION("Unmapping a VAddr should make it invalid") { + auto process = Kernel::Process::Create(""); + Kernel::MapSharedPages(process->vm_manager); + process->vm_manager.UnmapRange(Memory::CONFIG_MEMORY_VADDR, Memory::CONFIG_MEMORY_SIZE); + CHECK(Memory::IsValidVirtualAddress(*process, Memory::CONFIG_MEMORY_VADDR) == false); + } +}