diff --git a/src/core/hle/config_mem.cpp b/src/core/hle/config_mem.cpp index 94bca0378..b1a72dc0c 100644 --- a/src/core/hle/config_mem.cpp +++ b/src/core/hle/config_mem.cpp @@ -25,10 +25,6 @@ void Init() { config_mem.sys_core_ver = 0x2; config_mem.unit_info = 0x1; // Bit 0 set for Retail config_mem.prev_firm = 0; - config_mem.app_mem_type = 0x2; // Default app mem type is 0 - config_mem.app_mem_alloc = 0x06000000; // Set to 96MB, since some games use more than the default (64MB) - config_mem.base_mem_alloc = 0x01400000; // Default base memory is 20MB - config_mem.sys_mem_alloc = Memory::FCRAM_SIZE - (config_mem.app_mem_alloc + config_mem.base_mem_alloc); config_mem.firm_unk = 0; config_mem.firm_version_rev = 0; config_mem.firm_version_min = 0x40; diff --git a/src/core/hle/hle.cpp b/src/core/hle/hle.cpp index 98dc8dd58..331b1b22a 100644 --- a/src/core/hle/hle.cpp +++ b/src/core/hle/hle.cpp @@ -34,8 +34,6 @@ void Reschedule(const char *reason) { void Init() { Service::Init(); - ConfigMem::Init(); - SharedPage::Init(); g_reschedule = false; diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index 5711c0405..7a401a965 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -7,11 +7,14 @@ #include "common/assert.h" #include "common/logging/log.h" +#include "core/hle/config_mem.h" #include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/resource_limit.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" #include "core/hle/kernel/timer.h" +#include "core/hle/shared_page.h" namespace Kernel { @@ -119,6 +122,13 @@ void HandleTable::Clear() { /// Initialize the kernel void Init() { + ConfigMem::Init(); + SharedPage::Init(); + + // TODO(yuriks): The memory type parameter needs to be determined by the ExHeader field instead + // For now it defaults to the one with a largest allocation to the app + Kernel::MemoryInit(2); // Allocates 96MB to the application + Kernel::ResourceLimitsInit(); Kernel::ThreadingInit(); Kernel::TimersInit(); @@ -131,11 +141,14 @@ void Init() { /// Shutdown the kernel void Shutdown() { + g_handle_table.Clear(); // Free all kernel objects + Kernel::ThreadingShutdown(); + g_current_process = nullptr; + Kernel::TimersShutdown(); Kernel::ResourceLimitsShutdown(); - g_handle_table.Clear(); // Free all kernel objects - g_current_process = nullptr; + Kernel::MemoryShutdown(); } } // namespace diff --git a/src/core/hle/kernel/memory.cpp b/src/core/hle/kernel/memory.cpp index 57e1912d3..e69b121eb 100644 --- a/src/core/hle/kernel/memory.cpp +++ b/src/core/hle/kernel/memory.cpp @@ -11,6 +11,7 @@ #include "common/logging/log.h" #include "core/hle/config_mem.h" +#include "core/hle/kernel/memory.h" #include "core/hle/kernel/vm_manager.h" #include "core/hle/result.h" #include "core/hle/shared_page.h" @@ -19,6 +20,77 @@ //////////////////////////////////////////////////////////////////////////////////////////////////// +namespace Kernel { + +static MemoryRegionInfo memory_regions[3]; + +/// Size of the APPLICATION, SYSTEM and BASE memory regions (respectively) for each sytem +/// 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].linear_heap_memory = std::make_shared>(); + + base += memory_regions[i].size; + } + + // We must've allocated the entire FCRAM by the end + ASSERT(base == Memory::FCRAM_SIZE); + + using ConfigMem::config_mem; + config_mem.app_mem_type = mem_type; + // app_mem_malloc does not always match the configured size for memory_region[0]: in case the + // n3DS type override is in effect it reports the size the game expects, not the real one. + config_mem.app_mem_alloc = memory_region_sizes[mem_type][0]; + config_mem.sys_mem_alloc = memory_regions[1].size; + config_mem.base_mem_alloc = memory_regions[2].size; +} + +void MemoryShutdown() { + for (auto& region : memory_regions) { + region.base = 0; + region.size = 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(); + } +} + +} + namespace Memory { namespace { diff --git a/src/core/hle/kernel/memory.h b/src/core/hle/kernel/memory.h index cba8a0714..2e2cae17d 100644 --- a/src/core/hle/kernel/memory.h +++ b/src/core/hle/kernel/memory.h @@ -4,10 +4,27 @@ #pragma once +#include + #include "common/common_types.h" +#include "core/hle/kernel/process.h" + namespace Kernel { + class VMManager; + +struct MemoryRegionInfo { + u32 base; // Not an address, but offset from start of FCRAM + u32 size; + + std::shared_ptr> linear_heap_memory; +}; + +void MemoryInit(u32 mem_type); +void MemoryShutdown(); +MemoryRegionInfo* GetMemoryRegion(MemoryRegion region); + } namespace Memory { diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp index 2cd1cfc14..1f45e6cf8 100644 --- a/src/core/hle/kernel/process.cpp +++ b/src/core/hle/kernel/process.cpp @@ -96,7 +96,7 @@ void Process::ParseKernelCaps(const u32* kernel_caps, size_t len) { int minor = kernel_version & 0xFF; int major = (kernel_version >> 8) & 0xFF; - LOG_DEBUG(Loader, "ExHeader kernel version: %d.%d", major, minor); + LOG_INFO(Loader, "ExHeader kernel version: %d.%d", major, minor); } else { LOG_ERROR(Loader, "Unhandled kernel caps descriptor: 0x%08X", descriptor); } @@ -104,6 +104,8 @@ void Process::ParseKernelCaps(const u32* kernel_caps, size_t len) { } void Process::Run(s32 main_thread_priority, u32 stack_size) { + memory_region = GetMemoryRegion(flags.memory_region); + auto MapSegment = [&](CodeSet::Segment& segment, VMAPermission permissions, MemoryState memory_state) { auto vma = vm_manager.MapMemoryBlock(segment.addr, codeset->memory, segment.offset, segment.size, memory_state).Unwrap(); @@ -124,6 +126,15 @@ void Process::Run(s32 main_thread_priority, u32 stack_size) { Kernel::SetupMainThread(codeset->entrypoint, main_thread_priority); } +VAddr Process::GetLinearHeapBase() const { + return (kernel_version < 0x22C ? Memory::LINEAR_HEAP_VADDR : Memory::NEW_LINEAR_HEAP_SIZE) + + memory_region->base; +} + +VAddr Process::GetLinearHeapLimit() const { + return GetLinearHeapBase() + memory_region->size; +} + ResultVal Process::HeapAllocate(VAddr target, u32 size, VMAPermission perms) { if (target < Memory::HEAP_VADDR || target + size > Memory::HEAP_VADDR_END || target + size < target) { return ERR_INVALID_ADDRESS; @@ -166,19 +177,16 @@ ResultCode Process::HeapFree(VAddr target, u32 size) { } ResultVal Process::LinearAllocate(VAddr target, u32 size, VMAPermission perms) { - if (linear_heap_memory == nullptr) { - // Initialize heap - linear_heap_memory = std::make_shared>(); - } + auto& linheap_memory = memory_region->linear_heap_memory; - VAddr heap_end = Memory::LINEAR_HEAP_VADDR + (u32)linear_heap_memory->size(); + VAddr heap_end = GetLinearHeapBase() + (u32)linheap_memory->size(); // Games and homebrew only ever seem to pass 0 here (which lets the kernel decide the address), // but explicit addresses are also accepted and respected. if (target == 0) { target = heap_end; } - if (target < Memory::LINEAR_HEAP_VADDR || target + size > Memory::LINEAR_HEAP_VADDR_END || + if (target < GetLinearHeapBase() || target + size > GetLinearHeapLimit() || target > heap_end || target + size < target) { return ERR_INVALID_ADDRESS; @@ -188,25 +196,29 @@ ResultVal Process::LinearAllocate(VAddr target, u32 size, VMAPermission p // end. It's possible to free gaps in the middle of the heap and then reallocate them later, // but expansions are only allowed at the end. if (target == heap_end) { - linear_heap_memory->insert(linear_heap_memory->end(), size, 0); - vm_manager.RefreshMemoryBlockMappings(linear_heap_memory.get()); + linheap_memory->insert(linheap_memory->end(), size, 0); + vm_manager.RefreshMemoryBlockMappings(linheap_memory.get()); } - size_t offset = target - Memory::LINEAR_HEAP_VADDR; - CASCADE_RESULT(auto vma, vm_manager.MapMemoryBlock(target, linear_heap_memory, offset, size, MemoryState::Continuous)); + // TODO(yuriks): As is, this lets processes map memory allocated by other processes from the + // same region. It is unknown if or how the 3DS kernel checks against this. + size_t offset = target - GetLinearHeapBase(); + CASCADE_RESULT(auto vma, vm_manager.MapMemoryBlock(target, linheap_memory, offset, size, MemoryState::Continuous)); vm_manager.Reprotect(vma, perms); return MakeResult(target); } ResultCode Process::LinearFree(VAddr target, u32 size) { - if (linear_heap_memory == nullptr || target < Memory::LINEAR_HEAP_VADDR || - target + size > Memory::LINEAR_HEAP_VADDR_END || target + size < target) { + auto& linheap_memory = memory_region->linear_heap_memory; + + if (target < GetLinearHeapBase() || target + size > GetLinearHeapLimit() || + target + size < target) { return ERR_INVALID_ADDRESS; } - VAddr heap_end = Memory::LINEAR_HEAP_VADDR + (u32)linear_heap_memory->size(); + VAddr heap_end = GetLinearHeapBase() + (u32)linheap_memory->size(); if (target + size > heap_end) { return ERR_INVALID_ADDRESS_STATE; } @@ -221,8 +233,8 @@ ResultCode Process::LinearFree(VAddr target, u32 size) { ASSERT(vma != vm_manager.vma_map.end()); ASSERT(vma->second.type == VMAType::Free); VAddr new_end = vma->second.base; - if (new_end >= Memory::LINEAR_HEAP_VADDR) { - linear_heap_memory->resize(new_end - Memory::LINEAR_HEAP_VADDR); + if (new_end >= GetLinearHeapBase()) { + linheap_memory->resize(new_end - GetLinearHeapBase()); } } diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h index 5c7de9044..7c3a78b9e 100644 --- a/src/core/hle/kernel/process.h +++ b/src/core/hle/kernel/process.h @@ -49,6 +49,7 @@ union ProcessFlags { }; class ResourceLimit; +struct MemoryRegionInfo; struct CodeSet final : public Object { static SharedPtr Create(std::string name, u64 program_id); @@ -135,11 +136,14 @@ public: // The left/right bounds of the address space covered by heap_memory. VAddr heap_start = 0, heap_end = 0; - std::shared_ptr> linear_heap_memory; + MemoryRegionInfo* memory_region = nullptr; /// Bitmask of the used TLS slots std::bitset<300> used_tls_slots; + VAddr GetLinearHeapBase() const; + VAddr GetLinearHeapLimit() const; + ResultVal HeapAllocate(VAddr target, u32 size, VMAPermission perms); ResultCode HeapFree(VAddr target, u32 size); diff --git a/src/core/hle/svc.cpp b/src/core/hle/svc.cpp index b944f4af0..e1a416def 100644 --- a/src/core/hle/svc.cpp +++ b/src/core/hle/svc.cpp @@ -102,7 +102,7 @@ static ResultCode ControlMemory(u32* out_addr, u32 operation, u32 addr0, u32 add if (addr0 >= Memory::HEAP_VADDR && addr0 < Memory::HEAP_VADDR_END) { ResultCode result = process.HeapFree(addr0, size); if (result.IsError()) return result; - } else if (addr0 >= Memory::LINEAR_HEAP_VADDR && addr0 < Memory::LINEAR_HEAP_VADDR_END) { + } else if (addr0 >= process.GetLinearHeapBase() && addr0 < process.GetLinearHeapLimit()) { ResultCode result = process.LinearFree(addr0, size); if (result.IsError()) return result; } else { diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 935dac90f..cde390b8a 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -9,6 +9,7 @@ #include "common/logging/log.h" #include "common/swap.h" +#include "core/hle/kernel/process.h" #include "core/memory.h" #include "core/memory_setup.h" @@ -208,6 +209,8 @@ PAddr VirtualToPhysicalAddress(const VAddr addr) { 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; + } else if (addr >= NEW_LINEAR_HEAP_VADDR && addr < NEW_LINEAR_HEAP_VADDR_END) { + return addr - NEW_LINEAR_HEAP_VADDR + FCRAM_PADDR; } LOG_ERROR(HW_Memory, "Unknown virtual address @ 0x%08X", addr); @@ -221,7 +224,7 @@ VAddr PhysicalToVirtualAddress(const PAddr addr) { } 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 + LINEAR_HEAP_VADDR; + return addr - FCRAM_PADDR + Kernel::g_current_process->GetLinearHeapBase(); } 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) { diff --git a/src/core/memory.h b/src/core/memory.h index e6da3e2a5..d1d32f0dd 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -107,6 +107,11 @@ enum : VAddr { TLS_AREA_VADDR = 0x1FF82000, TLS_AREA_SIZE = 0x00030000, // Each TLS buffer is 0x200 bytes, allows for 300 threads TLS_AREA_VADDR_END = TLS_AREA_VADDR + TLS_AREA_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, }; u8 Read8(VAddr addr);