diff options
Diffstat (limited to 'src/core/hle')
-rw-r--r-- | src/core/hle/config_mem.cpp | 7 | ||||
-rw-r--r-- | src/core/hle/config_mem.h | 1 | ||||
-rw-r--r-- | src/core/hle/function_wrappers.h | 8 | ||||
-rw-r--r-- | src/core/hle/hle.cpp | 4 | ||||
-rw-r--r-- | src/core/hle/kernel/kernel.cpp | 19 | ||||
-rw-r--r-- | src/core/hle/kernel/memory.cpp | 136 | ||||
-rw-r--r-- | src/core/hle/kernel/memory.h | 35 | ||||
-rw-r--r-- | src/core/hle/kernel/process.cpp | 152 | ||||
-rw-r--r-- | src/core/hle/kernel/process.h | 39 | ||||
-rw-r--r-- | src/core/hle/kernel/resource_limit.cpp | 1 | ||||
-rw-r--r-- | src/core/hle/kernel/thread.cpp | 4 | ||||
-rw-r--r-- | src/core/hle/kernel/vm_manager.cpp | 118 | ||||
-rw-r--r-- | src/core/hle/kernel/vm_manager.h | 38 | ||||
-rw-r--r-- | src/core/hle/service/apt/apt.cpp | 21 | ||||
-rw-r--r-- | src/core/hle/service/gsp_gpu.cpp | 1 | ||||
-rw-r--r-- | src/core/hle/service/y2r_u.cpp | 1 | ||||
-rw-r--r-- | src/core/hle/shared_page.cpp | 3 | ||||
-rw-r--r-- | src/core/hle/shared_page.h | 1 | ||||
-rw-r--r-- | src/core/hle/svc.cpp | 158 |
19 files changed, 659 insertions, 88 deletions
diff --git a/src/core/hle/config_mem.cpp b/src/core/hle/config_mem.cpp index aea936d2d..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; @@ -36,7 +32,4 @@ void Init() { config_mem.firm_sys_core_ver = 0x2; } -void Shutdown() { -} - } // namespace diff --git a/src/core/hle/config_mem.h b/src/core/hle/config_mem.h index 9825a09e8..24a1254f2 100644 --- a/src/core/hle/config_mem.h +++ b/src/core/hle/config_mem.h @@ -52,6 +52,5 @@ static_assert(sizeof(ConfigMemDef) == Memory::CONFIG_MEMORY_SIZE, "Config Memory extern ConfigMemDef config_mem; void Init(); -void Shutdown(); } // namespace diff --git a/src/core/hle/function_wrappers.h b/src/core/hle/function_wrappers.h index 1a0518926..5846a161b 100644 --- a/src/core/hle/function_wrappers.h +++ b/src/core/hle/function_wrappers.h @@ -172,6 +172,14 @@ template<ResultCode func(u32, s64, s64)> void Wrap() { FuncReturn(func(PARAM(0), param1, param2).raw); } +template<ResultCode func(s64*, Handle, u32)> void Wrap() { + s64 param_1 = 0; + u32 retval = func(¶m_1, PARAM(1), PARAM(2)).raw; + Core::g_app_core->SetReg(1, (u32)param_1); + Core::g_app_core->SetReg(2, (u32)(param_1 >> 32)); + FuncReturn(retval); +} + //////////////////////////////////////////////////////////////////////////////////////////////////// // Function wrappers that return type u32 diff --git a/src/core/hle/hle.cpp b/src/core/hle/hle.cpp index cd0a400dc..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; @@ -43,8 +41,6 @@ void Init() { } void Shutdown() { - ConfigMem::Shutdown(); - SharedPage::Shutdown(); Service::Shutdown(); LOG_DEBUG(Kernel, "shutdown OK"); 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 new file mode 100644 index 000000000..e4fc5f3c4 --- /dev/null +++ b/src/core/hle/kernel/memory.cpp @@ -0,0 +1,136 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <map> +#include <memory> +#include <utility> +#include <vector> + +#include "common/common_types.h" +#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" +#include "core/memory.h" +#include "core/memory_setup.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +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<std::vector<u8>>(); + + 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 { + +struct MemoryArea { + u32 base; + u32 size; + const char* name; +}; + +// We don't declare the IO regions in here since its handled by other means. +static MemoryArea memory_areas[] = { + {SHARED_MEMORY_VADDR, SHARED_MEMORY_SIZE, "Shared Memory"}, // Shared memory + {VRAM_VADDR, VRAM_SIZE, "VRAM"}, // Video memory (VRAM) + {DSP_RAM_VADDR, DSP_RAM_SIZE, "DSP RAM"}, // DSP memory + {TLS_AREA_VADDR, TLS_AREA_SIZE, "TLS Area"}, // TLS memory +}; + +} + +void Init() { + InitMemoryMap(); + LOG_DEBUG(HW_Memory, "initialized OK"); +} + +void InitLegacyAddressSpace(Kernel::VMManager& address_space) { + using namespace Kernel; + + for (MemoryArea& area : memory_areas) { + auto block = std::make_shared<std::vector<u8>>(area.size); + address_space.MapMemoryBlock(area.base, std::move(block), 0, area.size, MemoryState::Private).Unwrap(); + } + + auto cfg_mem_vma = address_space.MapBackingMemory(CONFIG_MEMORY_VADDR, + (u8*)&ConfigMem::config_mem, CONFIG_MEMORY_SIZE, MemoryState::Shared).MoveFrom(); + address_space.Reprotect(cfg_mem_vma, VMAPermission::Read); + + auto shared_page_vma = address_space.MapBackingMemory(SHARED_PAGE_VADDR, + (u8*)&SharedPage::shared_page, SHARED_PAGE_SIZE, MemoryState::Shared).MoveFrom(); + address_space.Reprotect(shared_page_vma, VMAPermission::Read); +} + +} // namespace diff --git a/src/core/hle/kernel/memory.h b/src/core/hle/kernel/memory.h new file mode 100644 index 000000000..36690b091 --- /dev/null +++ b/src/core/hle/kernel/memory.h @@ -0,0 +1,35 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> + +#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<std::vector<u8>> linear_heap_memory; +}; + +void MemoryInit(u32 mem_type); +void MemoryShutdown(); +MemoryRegionInfo* GetMemoryRegion(MemoryRegion region); + +} + +namespace Memory { + +void Init(); +void InitLegacyAddressSpace(Kernel::VMManager& address_space); + +} // namespace diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp index a7892c652..124047a53 100644 --- a/src/core/hle/kernel/process.cpp +++ b/src/core/hle/kernel/process.cpp @@ -7,11 +7,11 @@ #include "common/logging/log.h" #include "common/make_unique.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/vm_manager.h" -#include "core/mem_map.h" #include "core/memory.h" namespace Kernel { @@ -36,8 +36,7 @@ SharedPtr<Process> Process::Create(SharedPtr<CodeSet> code_set) { process->codeset = std::move(code_set); process->flags.raw = 0; process->flags.memory_region = MemoryRegion::APPLICATION; - process->address_space = Common::make_unique<VMManager>(); - Memory::InitLegacyAddressSpace(*process->address_space); + Memory::InitLegacyAddressSpace(process->vm_manager); return process; } @@ -93,9 +92,11 @@ void Process::ParseKernelCaps(const u32* kernel_caps, size_t len) { mapping.unk_flag = false; } else if ((type & 0xFE0) == 0xFC0) { // 0x01FF // Kernel version - int minor = descriptor & 0xFF; - int major = (descriptor >> 8) & 0xFF; - LOG_INFO(Loader, "ExHeader kernel version ignored: %d.%d", major, minor); + kernel_version = descriptor & 0xFFFF; + + int minor = kernel_version & 0xFF; + int major = (kernel_version >> 8) & 0xFF; + LOG_INFO(Loader, "ExHeader kernel version: %d.%d", major, minor); } else { LOG_ERROR(Loader, "Unhandled kernel caps descriptor: 0x%08X", descriptor); } @@ -103,20 +104,153 @@ 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 = address_space->MapMemoryBlock(segment.addr, codeset->memory, + auto vma = vm_manager.MapMemoryBlock(segment.addr, codeset->memory, segment.offset, segment.size, memory_state).Unwrap(); - address_space->Reprotect(vma, permissions); + vm_manager.Reprotect(vma, permissions); + misc_memory_used += segment.size; }; + // Map CodeSet segments MapSegment(codeset->code, VMAPermission::ReadExecute, MemoryState::Code); MapSegment(codeset->rodata, VMAPermission::Read, MemoryState::Code); MapSegment(codeset->data, VMAPermission::ReadWrite, MemoryState::Private); - address_space->LogLayout(); + // Allocate and map stack + vm_manager.MapMemoryBlock(Memory::HEAP_VADDR_END - stack_size, + std::make_shared<std::vector<u8>>(stack_size, 0), 0, stack_size, MemoryState::Locked + ).Unwrap(); + misc_memory_used += stack_size; + + vm_manager.LogLayout(Log::Level::Debug); 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<VAddr> 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; + } + + if (heap_memory == nullptr) { + // Initialize heap + heap_memory = std::make_shared<std::vector<u8>>(); + heap_start = heap_end = target; + } + + // If necessary, expand backing vector to cover new heap extents. + if (target < heap_start) { + heap_memory->insert(begin(*heap_memory), heap_start - target, 0); + heap_start = target; + vm_manager.RefreshMemoryBlockMappings(heap_memory.get()); + } + if (target + size > heap_end) { + heap_memory->insert(end(*heap_memory), (target + size) - heap_end, 0); + heap_end = target + size; + vm_manager.RefreshMemoryBlockMappings(heap_memory.get()); + } + ASSERT(heap_end - heap_start == heap_memory->size()); + + CASCADE_RESULT(auto vma, vm_manager.MapMemoryBlock(target, heap_memory, target - heap_start, size, MemoryState::Private)); + vm_manager.Reprotect(vma, perms); + + heap_used += size; + + return MakeResult<VAddr>(heap_end - size); +} + +ResultCode Process::HeapFree(VAddr target, u32 size) { + if (target < Memory::HEAP_VADDR || target + size > Memory::HEAP_VADDR_END || target + size < target) { + return ERR_INVALID_ADDRESS; + } + + ResultCode result = vm_manager.UnmapRange(target, size); + if (result.IsError()) return result; + + heap_used -= size; + + return RESULT_SUCCESS; +} + +ResultVal<VAddr> Process::LinearAllocate(VAddr target, u32 size, VMAPermission perms) { + auto& linheap_memory = memory_region->linear_heap_memory; + + 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 < GetLinearHeapBase() || target + size > GetLinearHeapLimit() || + target > heap_end || target + size < target) { + + return ERR_INVALID_ADDRESS; + } + + // Expansion of the linear heap is only allowed if you do an allocation immediatelly at its + // 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) { + linheap_memory->insert(linheap_memory->end(), size, 0); + vm_manager.RefreshMemoryBlockMappings(linheap_memory.get()); + } + + // 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); + + linear_heap_used += size; + + return MakeResult<VAddr>(target); +} + +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; + } + + 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; + + 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; +} + Kernel::Process::Process() {} Kernel::Process::~Process() {} diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h index 83d3aceae..60e17f251 100644 --- a/src/core/hle/kernel/process.h +++ b/src/core/hle/kernel/process.h @@ -15,6 +15,7 @@ #include "common/common_types.h" #include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/vm_manager.h" namespace Kernel { @@ -48,7 +49,7 @@ union ProcessFlags { }; class ResourceLimit; -class VMManager; +struct MemoryRegionInfo; struct CodeSet final : public Object { static SharedPtr<CodeSet> Create(std::string name, u64 program_id); @@ -104,14 +105,12 @@ public: /// processes access to specific I/O regions and device memory. boost::container::static_vector<AddressMapping, 8> address_mappings; ProcessFlags flags; + /// Kernel compatibility version for this process + u16 kernel_version = 0; /// The id of this process u32 process_id = next_process_id++; - /// Bitmask of the used TLS slots - std::bitset<300> used_tls_slots; - std::unique_ptr<VMManager> address_space; - /** * Parses a list of kernel capability descriptors (as found in the ExHeader) and applies them * to this process. @@ -123,6 +122,36 @@ public: */ void Run(s32 main_thread_priority, u32 stack_size); + + /////////////////////////////////////////////////////////////////////////////////////////////// + // Memory Management + + VMManager vm_manager; + + // Memory used to back the allocations in the regular heap. A single vector is used to cover + // the entire virtual address space extents that bound the allocations, including any holes. + // 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<std::vector<u8>> heap_memory; + // The left/right bounds of the address space covered by heap_memory. + VAddr heap_start = 0, heap_end = 0; + + u32 heap_used = 0, linear_heap_used = 0, misc_memory_used = 0; + + MemoryRegionInfo* memory_region = nullptr; + + /// Bitmask of the used TLS slots + std::bitset<300> used_tls_slots; + + VAddr GetLinearHeapBase() const; + VAddr GetLinearHeapLimit() const; + + ResultVal<VAddr> HeapAllocate(VAddr target, u32 size, VMAPermission perms); + ResultCode HeapFree(VAddr target, u32 size); + + ResultVal<VAddr> LinearAllocate(VAddr target, u32 size, VMAPermission perms); + ResultCode LinearFree(VAddr target, u32 size); + private: Process(); ~Process() override; diff --git a/src/core/hle/kernel/resource_limit.cpp b/src/core/hle/kernel/resource_limit.cpp index 94b3e3298..67dde08c2 100644 --- a/src/core/hle/kernel/resource_limit.cpp +++ b/src/core/hle/kernel/resource_limit.cpp @@ -6,7 +6,6 @@ #include "common/logging/log.h" -#include "core/mem_map.h" #include "core/hle/kernel/resource_limit.h" namespace Kernel { diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index 29ea6d531..c10126513 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -117,6 +117,7 @@ void Thread::Stop() { wait_objects.clear(); Kernel::g_current_process->used_tls_slots[tls_index] = false; + g_current_process->misc_memory_used -= Memory::TLS_ENTRY_SIZE; HLE::Reschedule(__func__); } @@ -414,6 +415,7 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point, } ASSERT_MSG(thread->tls_index != -1, "Out of TLS space"); + g_current_process->misc_memory_used += Memory::TLS_ENTRY_SIZE; // TODO(peachum): move to ScheduleThread() when scheduler is added so selected core is used // to initialize the context @@ -504,7 +506,7 @@ void Thread::SetWaitSynchronizationOutput(s32 output) { } VAddr Thread::GetTLSAddress() const { - return Memory::TLS_AREA_VADDR + tls_index * 0x200; + return Memory::TLS_AREA_VADDR + tls_index * Memory::TLS_ENTRY_SIZE; } //////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp index 205cc7b53..2610acf76 100644 --- a/src/core/hle/kernel/vm_manager.cpp +++ b/src/core/hle/kernel/vm_manager.cpp @@ -11,6 +11,15 @@ namespace Kernel { +static const char* GetMemoryStateName(MemoryState state) { + static const char* names[] = { + "Free", "Reserved", "IO", "Static", "Code", "Private", "Shared", "Continuous", "Aliased", + "Alias", "AliasCode", "Locked", + }; + + return names[(int)state]; +} + bool VirtualMemoryArea::CanBeMergedWith(const VirtualMemoryArea& next) const { ASSERT(base + size == next.base); if (permissions != next.permissions || @@ -51,11 +60,15 @@ void VMManager::Reset() { } VMManager::VMAHandle VMManager::FindVMA(VAddr target) const { - return std::prev(vma_map.upper_bound(target)); + if (target >= MAX_ADDRESS) { + return vma_map.end(); + } else { + return std::prev(vma_map.upper_bound(target)); + } } ResultVal<VMManager::VMAHandle> VMManager::MapMemoryBlock(VAddr target, - std::shared_ptr<std::vector<u8>> block, u32 offset, u32 size, MemoryState state) { + std::shared_ptr<std::vector<u8>> block, size_t offset, u32 size, MemoryState state) { ASSERT(block != nullptr); ASSERT(offset + size <= block->size()); @@ -106,10 +119,8 @@ ResultVal<VMManager::VMAHandle> VMManager::MapMMIO(VAddr target, PAddr paddr, u3 return MakeResult<VMAHandle>(MergeAdjacent(vma_handle)); } -void VMManager::Unmap(VMAHandle vma_handle) { - VMAIter iter = StripIterConstness(vma_handle); - - VirtualMemoryArea& vma = iter->second; +VMManager::VMAIter VMManager::Unmap(VMAIter vma_handle) { + VirtualMemoryArea& vma = vma_handle->second; vma.type = VMAType::Free; vma.permissions = VMAPermission::None; vma.meminfo_state = MemoryState::Free; @@ -121,26 +132,67 @@ void VMManager::Unmap(VMAHandle vma_handle) { UpdatePageTableForVMA(vma); - MergeAdjacent(iter); + return MergeAdjacent(vma_handle); +} + +ResultCode VMManager::UnmapRange(VAddr target, u32 size) { + CASCADE_RESULT(VMAIter vma, CarveVMARange(target, size)); + VAddr target_end = target + size; + + VMAIter end = vma_map.end(); + // The comparison against the end of the range must be done using addresses since VMAs can be + // merged during this process, causing invalidation of the iterators. + while (vma != end && vma->second.base < target_end) { + vma = std::next(Unmap(vma)); + } + + ASSERT(FindVMA(target)->second.size >= size); + return RESULT_SUCCESS; } -void VMManager::Reprotect(VMAHandle vma_handle, VMAPermission new_perms) { +VMManager::VMAHandle VMManager::Reprotect(VMAHandle vma_handle, VMAPermission new_perms) { VMAIter iter = StripIterConstness(vma_handle); VirtualMemoryArea& vma = iter->second; vma.permissions = new_perms; UpdatePageTableForVMA(vma); - MergeAdjacent(iter); + return MergeAdjacent(iter); +} + +ResultCode VMManager::ReprotectRange(VAddr target, u32 size, VMAPermission new_perms) { + CASCADE_RESULT(VMAIter vma, CarveVMARange(target, size)); + VAddr target_end = target + size; + + VMAIter end = vma_map.end(); + // The comparison against the end of the range must be done using addresses since VMAs can be + // merged during this process, causing invalidation of the iterators. + while (vma != end && vma->second.base < target_end) { + vma = std::next(StripIterConstness(Reprotect(vma, new_perms))); + } + + return RESULT_SUCCESS; } -void VMManager::LogLayout() const { +void VMManager::RefreshMemoryBlockMappings(const std::vector<u8>* block) { + // If this ever proves to have a noticeable performance impact, allow users of the function to + // specify a specific range of addresses to limit the scan to. for (const auto& p : vma_map) { const VirtualMemoryArea& vma = p.second; - LOG_DEBUG(Kernel, "%08X - %08X size: %8X %c%c%c", vma.base, vma.base + vma.size, vma.size, + if (block == vma.backing_block.get()) { + UpdatePageTableForVMA(vma); + } + } +} + +void VMManager::LogLayout(Log::Level log_level) const { + for (const auto& p : vma_map) { + const VirtualMemoryArea& vma = p.second; + LOG_GENERIC(Log::Class::Kernel, log_level, "%08X - %08X size: %8X %c%c%c %s", + vma.base, vma.base + vma.size, vma.size, (u8)vma.permissions & (u8)VMAPermission::Read ? 'R' : '-', (u8)vma.permissions & (u8)VMAPermission::Write ? 'W' : '-', - (u8)vma.permissions & (u8)VMAPermission::Execute ? 'X' : '-'); + (u8)vma.permissions & (u8)VMAPermission::Execute ? 'X' : '-', GetMemoryStateName(vma.meminfo_state)); } } @@ -151,21 +203,19 @@ VMManager::VMAIter VMManager::StripIterConstness(const VMAHandle & iter) { } ResultVal<VMManager::VMAIter> VMManager::CarveVMA(VAddr base, u32 size) { - ASSERT_MSG((size & Memory::PAGE_MASK) == 0, "non-page aligned size: %8X", size); - ASSERT_MSG((base & Memory::PAGE_MASK) == 0, "non-page aligned base: %08X", base); + ASSERT_MSG((size & Memory::PAGE_MASK) == 0, "non-page aligned size: 0x%8X", size); + ASSERT_MSG((base & Memory::PAGE_MASK) == 0, "non-page aligned base: 0x%08X", base); VMAIter vma_handle = StripIterConstness(FindVMA(base)); if (vma_handle == vma_map.end()) { // Target address is outside the range managed by the kernel - return ResultCode(ErrorDescription::InvalidAddress, ErrorModule::OS, - ErrorSummary::InvalidArgument, ErrorLevel::Usage); // 0xE0E01BF5 + return ERR_INVALID_ADDRESS; } VirtualMemoryArea& vma = vma_handle->second; if (vma.type != VMAType::Free) { // Region is already allocated - return ResultCode(ErrorDescription::InvalidAddress, ErrorModule::OS, - ErrorSummary::InvalidState, ErrorLevel::Usage); // 0xE0A01BF5 + return ERR_INVALID_ADDRESS_STATE; } u32 start_in_vma = base - vma.base; @@ -173,8 +223,7 @@ ResultVal<VMManager::VMAIter> VMManager::CarveVMA(VAddr base, u32 size) { if (end_in_vma > vma.size) { // Requested allocation doesn't fit inside VMA - return ResultCode(ErrorDescription::InvalidAddress, ErrorModule::OS, - ErrorSummary::InvalidState, ErrorLevel::Usage); // 0xE0A01BF5 + return ERR_INVALID_ADDRESS_STATE; } if (end_in_vma != vma.size) { @@ -189,6 +238,35 @@ ResultVal<VMManager::VMAIter> VMManager::CarveVMA(VAddr base, u32 size) { return MakeResult<VMAIter>(vma_handle); } +ResultVal<VMManager::VMAIter> VMManager::CarveVMARange(VAddr target, u32 size) { + ASSERT_MSG((size & Memory::PAGE_MASK) == 0, "non-page aligned size: 0x%8X", size); + ASSERT_MSG((target & Memory::PAGE_MASK) == 0, "non-page aligned base: 0x%08X", target); + + VAddr target_end = target + size; + ASSERT(target_end >= target); + ASSERT(target_end <= MAX_ADDRESS); + ASSERT(size > 0); + + VMAIter begin_vma = StripIterConstness(FindVMA(target)); + VMAIter i_end = vma_map.lower_bound(target_end); + for (auto i = begin_vma; i != i_end; ++i) { + if (i->second.type == VMAType::Free) { + return ERR_INVALID_ADDRESS_STATE; + } + } + + if (target != begin_vma->second.base) { + begin_vma = SplitVMA(begin_vma, target - begin_vma->second.base); + } + + VMAIter end_vma = StripIterConstness(FindVMA(target_end)); + if (end_vma != vma_map.end() && target_end != end_vma->second.base) { + end_vma = SplitVMA(end_vma, target_end - end_vma->second.base); + } + + return MakeResult<VMAIter>(begin_vma); +} + VMManager::VMAIter VMManager::SplitVMA(VMAIter vma_handle, u32 offset_in_vma) { VirtualMemoryArea& old_vma = vma_handle->second; VirtualMemoryArea new_vma = old_vma; // Make a copy of the VMA diff --git a/src/core/hle/kernel/vm_manager.h b/src/core/hle/kernel/vm_manager.h index b3795a94a..4e95f1f0c 100644 --- a/src/core/hle/kernel/vm_manager.h +++ b/src/core/hle/kernel/vm_manager.h @@ -14,6 +14,14 @@ namespace Kernel { +const ResultCode ERR_INVALID_ADDRESS{ // 0xE0E01BF5 + ErrorDescription::InvalidAddress, ErrorModule::OS, + ErrorSummary::InvalidArgument, ErrorLevel::Usage}; + +const ResultCode ERR_INVALID_ADDRESS_STATE{ // 0xE0A01BF5 + ErrorDescription::InvalidAddress, ErrorModule::OS, + ErrorSummary::InvalidState, ErrorLevel::Usage}; + enum class VMAType : u8 { /// VMA represents an unmapped region of the address space. Free, @@ -75,7 +83,7 @@ struct VirtualMemoryArea { /// Memory block backing this VMA. std::shared_ptr<std::vector<u8>> backing_block = nullptr; /// Offset into the backing_memory the mapping starts from. - u32 offset = 0; + size_t offset = 0; // Settings for type = BackingMemory /// Pointer backing this VMA. It will not be destroyed or freed when the VMA is removed. @@ -141,7 +149,7 @@ public: * @param state MemoryState tag to attach to the VMA. */ ResultVal<VMAHandle> MapMemoryBlock(VAddr target, std::shared_ptr<std::vector<u8>> block, - u32 offset, u32 size, MemoryState state); + size_t offset, u32 size, MemoryState state); /** * Maps an unmanaged host memory pointer at a given address. @@ -163,14 +171,23 @@ public: */ ResultVal<VMAHandle> MapMMIO(VAddr target, PAddr paddr, u32 size, MemoryState state); - /// Unmaps the given VMA. - void Unmap(VMAHandle vma); + /// Unmaps a range of addresses, splitting VMAs as necessary. + ResultCode UnmapRange(VAddr target, u32 size); /// Changes the permissions of the given VMA. - void Reprotect(VMAHandle vma, VMAPermission new_perms); + VMAHandle Reprotect(VMAHandle vma, VMAPermission new_perms); + + /// Changes the permissions of a range of addresses, splitting VMAs as necessary. + ResultCode ReprotectRange(VAddr target, u32 size, VMAPermission new_perms); + + /** + * Scans all VMAs and updates the page table range of any that use the given vector as backing + * memory. This should be called after any operation that causes reallocation of the vector. + */ + void RefreshMemoryBlockMappings(const std::vector<u8>* block); /// Dumps the address space layout to the log, for debugging - void LogLayout() const; + void LogLayout(Log::Level log_level) const; private: using VMAIter = decltype(vma_map)::iterator; @@ -178,6 +195,9 @@ private: /// Converts a VMAHandle to a mutable VMAIter. VMAIter StripIterConstness(const VMAHandle& iter); + /// Unmaps the given VMA. + VMAIter Unmap(VMAIter vma); + /** * Carves a VMA of a specific size at the specified address by splitting Free VMAs while doing * the appropriate error checking. @@ -185,6 +205,12 @@ private: ResultVal<VMAIter> CarveVMA(VAddr base, u32 size); /** + * Splits the edges of the given range of non-Free VMAs so that there is a VMA split at each + * end of the range. + */ + ResultVal<VMAIter> CarveVMARange(VAddr base, u32 size); + + /** * Splits a VMA in two, at the specified offset. * @returns the right side of the split, with the original iterator becoming the left side. */ diff --git a/src/core/hle/service/apt/apt.cpp b/src/core/hle/service/apt/apt.cpp index 35402341b..6a2fdea2b 100644 --- a/src/core/hle/service/apt/apt.cpp +++ b/src/core/hle/service/apt/apt.cpp @@ -16,6 +16,7 @@ #include "core/hle/hle.h" #include "core/hle/kernel/event.h" #include "core/hle/kernel/mutex.h" +#include "core/hle/kernel/process.h" #include "core/hle/kernel/shared_memory.h" #include "core/hle/kernel/thread.h" @@ -37,7 +38,7 @@ static Kernel::SharedPtr<Kernel::Mutex> lock; static Kernel::SharedPtr<Kernel::Event> notification_event; ///< APT notification event static Kernel::SharedPtr<Kernel::Event> parameter_event; ///< APT parameter event -static std::vector<u8> shared_font; +static std::shared_ptr<std::vector<u8>> shared_font; static u32 cpu_percent; ///< CPU time available to the running application @@ -74,11 +75,12 @@ void Initialize(Service::Interface* self) { void GetSharedFont(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - if (!shared_font.empty()) { - // TODO(bunnei): This function shouldn't copy the shared font every time it's called. - // Instead, it should probably map the shared font as RO memory. We don't currently have - // an easy way to do this, but the copy should be sufficient for now. - memcpy(Memory::GetPointer(SHARED_FONT_VADDR), shared_font.data(), shared_font.size()); + if (shared_font != nullptr) { + // TODO(yuriks): This is a hack to keep this working right now even with our completely + // broken shared memory system. + shared_font_mem->base_address = SHARED_FONT_VADDR; + Kernel::g_current_process->vm_manager.MapMemoryBlock(shared_font_mem->base_address, + shared_font, 0, shared_font_mem->size, Kernel::MemoryState::Shared); cmd_buff[0] = IPC::MakeHeader(0x44, 2, 2); cmd_buff[1] = RESULT_SUCCESS.raw; // No error @@ -391,7 +393,6 @@ void Init() { // a homebrew app to do this: https://github.com/citra-emu/3dsutils. Put the resulting file // "shared_font.bin" in the Citra "sysdata" directory. - shared_font.clear(); std::string filepath = FileUtil::GetUserPath(D_SYSDATA_IDX) + SHARED_FONT; FileUtil::CreateFullPath(filepath); // Create path if not already created @@ -399,8 +400,8 @@ void Init() { if (file.IsOpen()) { // Read shared font data - shared_font.resize((size_t)file.GetSize()); - file.ReadBytes(shared_font.data(), (size_t)file.GetSize()); + shared_font = std::make_shared<std::vector<u8>>((size_t)file.GetSize()); + file.ReadBytes(shared_font->data(), shared_font->size()); // Create shared font memory object using Kernel::MemoryPermission; @@ -424,7 +425,7 @@ void Init() { } void Shutdown() { - shared_font.clear(); + shared_font = nullptr; shared_font_mem = nullptr; lock = nullptr; notification_event = nullptr; diff --git a/src/core/hle/service/gsp_gpu.cpp b/src/core/hle/service/gsp_gpu.cpp index 3c41e656c..fde508a13 100644 --- a/src/core/hle/service/gsp_gpu.cpp +++ b/src/core/hle/service/gsp_gpu.cpp @@ -4,7 +4,6 @@ #include "common/bit_field.h" -#include "core/mem_map.h" #include "core/memory.h" #include "core/hle/kernel/event.h" #include "core/hle/kernel/shared_memory.h" diff --git a/src/core/hle/service/y2r_u.cpp b/src/core/hle/service/y2r_u.cpp index 6e7dafaad..6b1b71fe4 100644 --- a/src/core/hle/service/y2r_u.cpp +++ b/src/core/hle/service/y2r_u.cpp @@ -10,7 +10,6 @@ #include "core/hle/kernel/event.h" #include "core/hle/service/y2r_u.h" #include "core/hw/y2r.h" -#include "core/mem_map.h" #include "video_core/renderer_base.h" #include "video_core/utils.h" diff --git a/src/core/hle/shared_page.cpp b/src/core/hle/shared_page.cpp index 26d87c7e2..50c5bc01b 100644 --- a/src/core/hle/shared_page.cpp +++ b/src/core/hle/shared_page.cpp @@ -18,7 +18,4 @@ void Init() { shared_page.running_hw = 0x1; // product } -void Shutdown() { -} - } // namespace diff --git a/src/core/hle/shared_page.h b/src/core/hle/shared_page.h index db6a5340b..379bb7b63 100644 --- a/src/core/hle/shared_page.h +++ b/src/core/hle/shared_page.h @@ -54,6 +54,5 @@ static_assert(sizeof(SharedPageDef) == Memory::SHARED_PAGE_SIZE, "Shared page st extern SharedPageDef shared_page; void Init(); -void Shutdown(); } // namespace diff --git a/src/core/hle/svc.cpp b/src/core/hle/svc.cpp index bb64fdfb7..89ac45a6f 100644 --- a/src/core/hle/svc.cpp +++ b/src/core/hle/svc.cpp @@ -10,11 +10,11 @@ #include "common/symbols.h" #include "core/core_timing.h" -#include "core/mem_map.h" #include "core/arm/arm_interface.h" #include "core/hle/kernel/address_arbiter.h" #include "core/hle/kernel/event.h" +#include "core/hle/kernel/memory.h" #include "core/hle/kernel/mutex.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/resource_limit.h" @@ -41,32 +41,114 @@ const ResultCode ERR_NOT_FOUND(ErrorDescription::NotFound, ErrorModule::Kernel, const ResultCode ERR_PORT_NAME_TOO_LONG(ErrorDescription(30), ErrorModule::OS, ErrorSummary::InvalidArgument, ErrorLevel::Usage); // 0xE0E0181E +const ResultCode ERR_MISALIGNED_ADDRESS{ // 0xE0E01BF1 + ErrorDescription::MisalignedAddress, ErrorModule::OS, + ErrorSummary::InvalidArgument, ErrorLevel::Usage}; +const ResultCode ERR_MISALIGNED_SIZE{ // 0xE0E01BF2 + ErrorDescription::MisalignedSize, ErrorModule::OS, + ErrorSummary::InvalidArgument, ErrorLevel::Usage}; +const ResultCode ERR_INVALID_COMBINATION{ // 0xE0E01BEE + ErrorDescription::InvalidCombination, ErrorModule::OS, + ErrorSummary::InvalidArgument, ErrorLevel::Usage}; + enum ControlMemoryOperation { - MEMORY_OPERATION_HEAP = 0x00000003, - MEMORY_OPERATION_GSP_HEAP = 0x00010003, + MEMOP_FREE = 1, + MEMOP_RESERVE = 2, // This operation seems to be unsupported in the kernel + MEMOP_COMMIT = 3, + MEMOP_MAP = 4, + MEMOP_UNMAP = 5, + MEMOP_PROTECT = 6, + MEMOP_OPERATION_MASK = 0xFF, + + MEMOP_REGION_APP = 0x100, + MEMOP_REGION_SYSTEM = 0x200, + MEMOP_REGION_BASE = 0x300, + MEMOP_REGION_MASK = 0xF00, + + MEMOP_LINEAR = 0x10000, }; /// Map application or GSP heap memory static ResultCode ControlMemory(u32* out_addr, u32 operation, u32 addr0, u32 addr1, u32 size, u32 permissions) { - LOG_TRACE(Kernel_SVC,"called operation=0x%08X, addr0=0x%08X, addr1=0x%08X, size=%08X, permissions=0x%08X", + using namespace Kernel; + + LOG_DEBUG(Kernel_SVC,"called operation=0x%08X, addr0=0x%08X, addr1=0x%08X, size=0x%X, permissions=0x%08X", operation, addr0, addr1, size, permissions); - switch (operation) { + if ((addr0 & Memory::PAGE_MASK) != 0 || (addr1 & Memory::PAGE_MASK) != 0) { + return ERR_MISALIGNED_ADDRESS; + } + if ((size & Memory::PAGE_MASK) != 0) { + return ERR_MISALIGNED_SIZE; + } + + u32 region = operation & MEMOP_REGION_MASK; + operation &= ~MEMOP_REGION_MASK; + + if (region != 0) { + LOG_WARNING(Kernel_SVC, "ControlMemory with specified region not supported, region=%X", region); + } + + if ((permissions & (u32)MemoryPermission::ReadWrite) != permissions) { + return ERR_INVALID_COMBINATION; + } + VMAPermission vma_permissions = (VMAPermission)permissions; + + auto& process = *g_current_process; + + switch (operation & MEMOP_OPERATION_MASK) { + case MEMOP_FREE: + { + if (addr0 >= Memory::HEAP_VADDR && addr0 < Memory::HEAP_VADDR_END) { + ResultCode result = process.HeapFree(addr0, size); + if (result.IsError()) return result; + } else if (addr0 >= process.GetLinearHeapBase() && addr0 < process.GetLinearHeapLimit()) { + ResultCode result = process.LinearFree(addr0, size); + if (result.IsError()) return result; + } else { + return ERR_INVALID_ADDRESS; + } + *out_addr = addr0; + break; + } + + case MEMOP_COMMIT: + { + if (operation & MEMOP_LINEAR) { + CASCADE_RESULT(*out_addr, process.LinearAllocate(addr0, size, vma_permissions)); + } else { + CASCADE_RESULT(*out_addr, process.HeapAllocate(addr0, size, vma_permissions)); + } + break; + } - // Map normal heap memory - case MEMORY_OPERATION_HEAP: - *out_addr = Memory::MapBlock_Heap(size, operation, permissions); + case MEMOP_MAP: // TODO: This is just a hack to avoid regressions until memory aliasing is implemented + { + CASCADE_RESULT(*out_addr, process.HeapAllocate(addr0, size, vma_permissions)); break; + } + + case MEMOP_UNMAP: // TODO: This is just a hack to avoid regressions until memory aliasing is implemented + { + ResultCode result = process.HeapFree(addr0, size); + if (result.IsError()) return result; + break; + } - // Map GSP heap memory - case MEMORY_OPERATION_GSP_HEAP: - *out_addr = Memory::MapBlock_HeapLinear(size, operation, permissions); + case MEMOP_PROTECT: + { + ResultCode result = process.vm_manager.ReprotectRange(addr0, size, vma_permissions); + if (result.IsError()) return result; break; + } - // Unknown ControlMemory operation default: LOG_ERROR(Kernel_SVC, "unknown operation=0x%08X", operation); + return ERR_INVALID_COMBINATION; } + + process.vm_manager.LogLayout(Log::Level::Trace); + return RESULT_SUCCESS; } @@ -537,9 +619,9 @@ static ResultCode QueryProcessMemory(MemoryInfo* memory_info, PageInfo* page_inf if (process == nullptr) return ERR_INVALID_HANDLE; - auto vma = process->address_space->FindVMA(addr); + auto vma = process->vm_manager.FindVMA(addr); - if (vma == process->address_space->vma_map.end()) + if (vma == Kernel::g_current_process->vm_manager.vma_map.end()) return ResultCode(ErrorDescription::InvalidAddress, ErrorModule::OS, ErrorSummary::InvalidArgument, ErrorLevel::Usage); memory_info->base_address = vma->second.base; @@ -692,6 +774,52 @@ static ResultCode CreateMemoryBlock(Handle* out_handle, u32 addr, u32 size, u32 return RESULT_SUCCESS; } +static ResultCode GetProcessInfo(s64* out, Handle process_handle, u32 type) { + LOG_TRACE(Kernel_SVC, "called process=0x%08X type=%u", process_handle, type); + + using Kernel::Process; + Kernel::SharedPtr<Process> process = Kernel::g_handle_table.Get<Process>(process_handle); + if (process == nullptr) + return ERR_INVALID_HANDLE; + + switch (type) { + case 0: + case 2: + // TODO(yuriks): Type 0 returns a slightly higher number than type 2, but I'm not sure + // what's the difference between them. + *out = process->heap_used + process->linear_heap_used + process->misc_memory_used; + break; + case 1: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + // These are valid, but not implemented yet + LOG_ERROR(Kernel_SVC, "unimplemented GetProcessInfo type=%u", type); + break; + case 20: + *out = Memory::FCRAM_PADDR - process->GetLinearHeapBase(); + break; + default: + LOG_ERROR(Kernel_SVC, "unknown GetProcessInfo type=%u", type); + + if (type >= 21 && type <= 23) { + return ResultCode( // 0xE0E01BF4 + ErrorDescription::NotImplemented, ErrorModule::OS, + ErrorSummary::InvalidArgument, ErrorLevel::Usage); + } else { + return ResultCode( // 0xD8E007ED + ErrorDescription::InvalidEnumValue, ErrorModule::Kernel, + ErrorSummary::InvalidArgument, ErrorLevel::Permanent); + } + break; + } + + return RESULT_SUCCESS; +} + namespace { struct FunctionDef { using Func = void(); @@ -746,7 +874,7 @@ static const FunctionDef SVC_Table[] = { {0x28, HLE::Wrap<GetSystemTick>, "GetSystemTick"}, {0x29, nullptr, "GetHandleInfo"}, {0x2A, nullptr, "GetSystemInfo"}, - {0x2B, nullptr, "GetProcessInfo"}, + {0x2B, HLE::Wrap<GetProcessInfo>, "GetProcessInfo"}, {0x2C, nullptr, "GetThreadInfo"}, {0x2D, HLE::Wrap<ConnectToPort>, "ConnectToPort"}, {0x2E, nullptr, "SendSyncRequest1"}, |