kernel: Improvements to process cleanup. (#6680)
* kernel: Properly clean up process threads on exit. * kernel: Track process-owned memory and free on destruction. * apt: Implement DoApplicationJump via home menu when available. * kernel: Move TLS allocation management to owning process.
This commit is contained in:
parent
8b6b58a364
commit
9cb14044ec
|
@ -135,10 +135,10 @@ public:
|
||||||
std::shared_ptr<Process> CreateProcess(std::shared_ptr<CodeSet> code_set);
|
std::shared_ptr<Process> CreateProcess(std::shared_ptr<CodeSet> code_set);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes a process from the kernel process list
|
* Terminates a process, killing its threads and removing it from the process list.
|
||||||
* @param process Process to remove
|
* @param process Process to terminate.
|
||||||
*/
|
*/
|
||||||
void RemoveProcess(std::shared_ptr<Process> process);
|
void TerminateProcess(std::shared_ptr<Process> process);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates and returns a new thread. The new thread is immediately scheduled
|
* Creates and returns a new thread. The new thread is immediately scheduled
|
||||||
|
@ -208,7 +208,7 @@ public:
|
||||||
* @param name Optional object name, used for debugging purposes.
|
* @param name Optional object name, used for debugging purposes.
|
||||||
*/
|
*/
|
||||||
ResultVal<std::shared_ptr<SharedMemory>> CreateSharedMemory(
|
ResultVal<std::shared_ptr<SharedMemory>> CreateSharedMemory(
|
||||||
Process* owner_process, u32 size, MemoryPermission permissions,
|
std::shared_ptr<Process> owner_process, u32 size, MemoryPermission permissions,
|
||||||
MemoryPermission other_permissions, VAddr address = 0,
|
MemoryPermission other_permissions, VAddr address = 0,
|
||||||
MemoryRegion region = MemoryRegion::BASE, std::string name = "Unknown");
|
MemoryRegion region = MemoryRegion::BASE, std::string name = "Unknown");
|
||||||
|
|
||||||
|
|
|
@ -79,7 +79,18 @@ std::shared_ptr<Process> KernelSystem::CreateProcess(std::shared_ptr<CodeSet> co
|
||||||
return process;
|
return process;
|
||||||
}
|
}
|
||||||
|
|
||||||
void KernelSystem::RemoveProcess(std::shared_ptr<Process> process) {
|
void KernelSystem::TerminateProcess(std::shared_ptr<Process> process) {
|
||||||
|
LOG_INFO(Kernel_SVC, "Process {} exiting", process->process_id);
|
||||||
|
|
||||||
|
ASSERT_MSG(process->status == ProcessStatus::Running, "Process has already exited");
|
||||||
|
process->status = ProcessStatus::Exited;
|
||||||
|
|
||||||
|
// Stop all process threads.
|
||||||
|
for (u32 core = 0; core < Core::GetNumCores(); core++) {
|
||||||
|
GetThreadManager(core).TerminateProcessThreads(process);
|
||||||
|
}
|
||||||
|
|
||||||
|
process->Exit();
|
||||||
std::erase(process_list, process);
|
std::erase(process_list, process);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -268,6 +279,7 @@ ResultVal<VAddr> Process::HeapAllocate(VAddr target, u32 size, VMAPermission per
|
||||||
interval_target += interval_size;
|
interval_target += interval_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
holding_memory += allocated_fcram;
|
||||||
memory_used += size;
|
memory_used += size;
|
||||||
resource_limit->current_commit += size;
|
resource_limit->current_commit += size;
|
||||||
|
|
||||||
|
@ -288,13 +300,14 @@ ResultCode Process::HeapFree(VAddr target, u32 size) {
|
||||||
|
|
||||||
// Free heaps block by block
|
// Free heaps block by block
|
||||||
CASCADE_RESULT(auto backing_blocks, vm_manager.GetBackingBlocksForRange(target, size));
|
CASCADE_RESULT(auto backing_blocks, vm_manager.GetBackingBlocksForRange(target, size));
|
||||||
for (const auto& [backing_memory, block_size] : backing_blocks) {
|
for (const auto& backing_block : backing_blocks) {
|
||||||
memory_region->Free(kernel.memory.GetFCRAMOffset(backing_memory.GetPtr()), block_size);
|
memory_region->Free(backing_block.lower(), backing_block.upper() - backing_block.lower());
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultCode result = vm_manager.UnmapRange(target, size);
|
ResultCode result = vm_manager.UnmapRange(target, size);
|
||||||
ASSERT(result.IsSuccess());
|
ASSERT(result.IsSuccess());
|
||||||
|
|
||||||
|
holding_memory -= backing_blocks;
|
||||||
memory_used -= size;
|
memory_used -= size;
|
||||||
resource_limit->current_commit -= size;
|
resource_limit->current_commit -= size;
|
||||||
|
|
||||||
|
@ -340,6 +353,7 @@ ResultVal<VAddr> Process::LinearAllocate(VAddr target, u32 size, VMAPermission p
|
||||||
ASSERT(vma.Succeeded());
|
ASSERT(vma.Succeeded());
|
||||||
vm_manager.Reprotect(vma.Unwrap(), perms);
|
vm_manager.Reprotect(vma.Unwrap(), perms);
|
||||||
|
|
||||||
|
holding_memory += MemoryRegionInfo::Interval(physical_offset, physical_offset + size);
|
||||||
memory_used += size;
|
memory_used += size;
|
||||||
resource_limit->current_commit += size;
|
resource_limit->current_commit += size;
|
||||||
|
|
||||||
|
@ -365,15 +379,86 @@ ResultCode Process::LinearFree(VAddr target, u32 size) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
memory_used -= size;
|
|
||||||
resource_limit->current_commit -= size;
|
|
||||||
|
|
||||||
u32 physical_offset = target - GetLinearHeapAreaAddress(); // relative to FCRAM
|
u32 physical_offset = target - GetLinearHeapAreaAddress(); // relative to FCRAM
|
||||||
memory_region->Free(physical_offset, size);
|
memory_region->Free(physical_offset, size);
|
||||||
|
|
||||||
|
holding_memory -= MemoryRegionInfo::Interval(physical_offset, physical_offset + size);
|
||||||
|
memory_used -= size;
|
||||||
|
resource_limit->current_commit -= size;
|
||||||
|
|
||||||
return RESULT_SUCCESS;
|
return RESULT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ResultVal<VAddr> Process::AllocateThreadLocalStorage() {
|
||||||
|
std::size_t tls_page;
|
||||||
|
std::size_t tls_slot;
|
||||||
|
bool needs_allocation = true;
|
||||||
|
|
||||||
|
// Iterate over all the allocated pages, and try to find one where not all slots are used.
|
||||||
|
for (tls_page = 0; tls_page < tls_slots.size(); ++tls_page) {
|
||||||
|
const auto& page_tls_slots = tls_slots[tls_page];
|
||||||
|
if (!page_tls_slots.all()) {
|
||||||
|
// We found a page with at least one free slot, find which slot it is.
|
||||||
|
for (tls_slot = 0; tls_slot < page_tls_slots.size(); ++tls_slot) {
|
||||||
|
if (!page_tls_slots.test(tls_slot)) {
|
||||||
|
needs_allocation = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!needs_allocation) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needs_allocation) {
|
||||||
|
tls_page = tls_slots.size();
|
||||||
|
tls_slot = 0;
|
||||||
|
|
||||||
|
LOG_DEBUG(Kernel, "Allocating new TLS page in slot {}", tls_page);
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
auto base_memory_region = kernel.GetMemoryRegion(MemoryRegion::BASE);
|
||||||
|
|
||||||
|
// Allocate some memory from the end of the linear heap for this region.
|
||||||
|
auto offset = base_memory_region->LinearAllocate(Memory::CITRA_PAGE_SIZE);
|
||||||
|
if (!offset) {
|
||||||
|
LOG_ERROR(Kernel_SVC,
|
||||||
|
"Not enough space in BASE linear region to allocate a new TLS page");
|
||||||
|
return ERR_OUT_OF_MEMORY;
|
||||||
|
}
|
||||||
|
|
||||||
|
holding_tls_memory +=
|
||||||
|
MemoryRegionInfo::Interval(*offset, *offset + Memory::CITRA_PAGE_SIZE);
|
||||||
|
memory_used += Memory::CITRA_PAGE_SIZE;
|
||||||
|
|
||||||
|
// The page is completely available at the start.
|
||||||
|
tls_slots.emplace_back(0);
|
||||||
|
|
||||||
|
// Map the page to the current process' address space.
|
||||||
|
auto tls_page_addr =
|
||||||
|
Memory::TLS_AREA_VADDR + static_cast<VAddr>(tls_page) * Memory::CITRA_PAGE_SIZE;
|
||||||
|
vm_manager.MapBackingMemory(tls_page_addr, kernel.memory.GetFCRAMRef(*offset),
|
||||||
|
Memory::CITRA_PAGE_SIZE, MemoryState::Locked);
|
||||||
|
|
||||||
|
LOG_DEBUG(Kernel, "Allocated TLS page at addr={:08X}", tls_page_addr);
|
||||||
|
} else {
|
||||||
|
LOG_DEBUG(Kernel, "Allocating TLS in existing page slot {}", tls_page);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark the slot as used
|
||||||
|
tls_slots[tls_page].set(tls_slot);
|
||||||
|
|
||||||
|
auto tls_address = Memory::TLS_AREA_VADDR +
|
||||||
|
static_cast<VAddr>(tls_page) * Memory::CITRA_PAGE_SIZE +
|
||||||
|
static_cast<VAddr>(tls_slot) * Memory::TLS_ENTRY_SIZE;
|
||||||
|
kernel.memory.ZeroBlock(*this, tls_address, Memory::TLS_ENTRY_SIZE);
|
||||||
|
|
||||||
|
return tls_address;
|
||||||
|
}
|
||||||
|
|
||||||
ResultCode Process::Map(VAddr target, VAddr source, u32 size, VMAPermission perms,
|
ResultCode Process::Map(VAddr target, VAddr source, u32 size, VMAPermission perms,
|
||||||
bool privileged) {
|
bool privileged) {
|
||||||
LOG_DEBUG(Kernel, "Map memory target={:08X}, source={:08X}, size={:08X}, perms={:08X}", target,
|
LOG_DEBUG(Kernel, "Map memory target={:08X}, source={:08X}, size={:08X}, perms={:08X}", target,
|
||||||
|
@ -419,7 +504,9 @@ ResultCode Process::Map(VAddr target, VAddr source, u32 size, VMAPermission perm
|
||||||
|
|
||||||
CASCADE_RESULT(auto backing_blocks, vm_manager.GetBackingBlocksForRange(source, size));
|
CASCADE_RESULT(auto backing_blocks, vm_manager.GetBackingBlocksForRange(source, size));
|
||||||
VAddr interval_target = target;
|
VAddr interval_target = target;
|
||||||
for (const auto& [backing_memory, block_size] : backing_blocks) {
|
for (const auto& backing_block : backing_blocks) {
|
||||||
|
auto backing_memory = kernel.memory.GetFCRAMRef(backing_block.lower());
|
||||||
|
auto block_size = backing_block.upper() - backing_block.lower();
|
||||||
auto target_vma =
|
auto target_vma =
|
||||||
vm_manager.MapBackingMemory(interval_target, backing_memory, block_size, target_state);
|
vm_manager.MapBackingMemory(interval_target, backing_memory, block_size, target_state);
|
||||||
ASSERT(target_vma.Succeeded());
|
ASSERT(target_vma.Succeeded());
|
||||||
|
@ -471,6 +558,42 @@ ResultCode Process::Unmap(VAddr target, VAddr source, u32 size, VMAPermission pe
|
||||||
return RESULT_SUCCESS;
|
return RESULT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Process::FreeAllMemory() {
|
||||||
|
if (memory_region == nullptr || resource_limit == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free any heap/linear memory allocations.
|
||||||
|
for (auto& entry : holding_memory) {
|
||||||
|
LOG_DEBUG(Kernel, "Freeing process memory region 0x{:08X} - 0x{:08X}", entry.lower(),
|
||||||
|
entry.upper());
|
||||||
|
auto size = entry.upper() - entry.lower();
|
||||||
|
memory_region->Free(entry.lower(), size);
|
||||||
|
memory_used -= size;
|
||||||
|
resource_limit->current_commit -= size;
|
||||||
|
}
|
||||||
|
holding_memory.clear();
|
||||||
|
|
||||||
|
// Free any TLS memory allocations.
|
||||||
|
auto base_memory_region = kernel.GetMemoryRegion(MemoryRegion::BASE);
|
||||||
|
for (auto& entry : holding_tls_memory) {
|
||||||
|
LOG_DEBUG(Kernel, "Freeing process TLS memory region 0x{:08X} - 0x{:08X}", entry.lower(),
|
||||||
|
entry.upper());
|
||||||
|
auto size = entry.upper() - entry.lower();
|
||||||
|
base_memory_region->Free(entry.lower(), size);
|
||||||
|
memory_used -= size;
|
||||||
|
}
|
||||||
|
holding_tls_memory.clear();
|
||||||
|
tls_slots.clear();
|
||||||
|
|
||||||
|
// Diagnostics for debugging.
|
||||||
|
// TODO: The way certain non-application shared memory is allocated can result in very slight
|
||||||
|
// leaks in these values still.
|
||||||
|
LOG_DEBUG(Kernel, "Remaining memory used after process cleanup: 0x{:08X}", memory_used);
|
||||||
|
LOG_DEBUG(Kernel, "Remaining memory resource commit after process cleanup: 0x{:08X}",
|
||||||
|
resource_limit->current_commit);
|
||||||
|
}
|
||||||
|
|
||||||
Kernel::Process::Process(KernelSystem& kernel)
|
Kernel::Process::Process(KernelSystem& kernel)
|
||||||
: Object(kernel), handle_table(kernel), vm_manager(kernel.memory, *this), kernel(kernel) {
|
: Object(kernel), handle_table(kernel), vm_manager(kernel.memory, *this), kernel(kernel) {
|
||||||
kernel.memory.RegisterPageTable(vm_manager.page_table);
|
kernel.memory.RegisterPageTable(vm_manager.page_table);
|
||||||
|
@ -484,6 +607,7 @@ Kernel::Process::~Process() {
|
||||||
// memory etc.) even if they are still referenced by other processes.
|
// memory etc.) even if they are still referenced by other processes.
|
||||||
handle_table.Clear();
|
handle_table.Clear();
|
||||||
|
|
||||||
|
FreeAllMemory();
|
||||||
kernel.memory.UnregisterPageTable(vm_manager.page_table);
|
kernel.memory.UnregisterPageTable(vm_manager.page_table);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -217,6 +217,8 @@ public:
|
||||||
u32 memory_used = 0;
|
u32 memory_used = 0;
|
||||||
|
|
||||||
std::shared_ptr<MemoryRegionInfo> memory_region = nullptr;
|
std::shared_ptr<MemoryRegionInfo> memory_region = nullptr;
|
||||||
|
MemoryRegionInfo::IntervalSet holding_memory;
|
||||||
|
MemoryRegionInfo::IntervalSet holding_tls_memory;
|
||||||
|
|
||||||
/// The Thread Local Storage area is allocated as processes create threads,
|
/// 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
|
/// each TLS area is 0x200 bytes, so one page (0x1000) is split up in 8 parts, and each part
|
||||||
|
@ -237,12 +239,16 @@ public:
|
||||||
ResultVal<VAddr> LinearAllocate(VAddr target, u32 size, VMAPermission perms);
|
ResultVal<VAddr> LinearAllocate(VAddr target, u32 size, VMAPermission perms);
|
||||||
ResultCode LinearFree(VAddr target, u32 size);
|
ResultCode LinearFree(VAddr target, u32 size);
|
||||||
|
|
||||||
|
ResultVal<VAddr> AllocateThreadLocalStorage();
|
||||||
|
|
||||||
ResultCode Map(VAddr target, VAddr source, u32 size, VMAPermission perms,
|
ResultCode Map(VAddr target, VAddr source, u32 size, VMAPermission perms,
|
||||||
bool privileged = false);
|
bool privileged = false);
|
||||||
ResultCode Unmap(VAddr target, VAddr source, u32 size, VMAPermission perms,
|
ResultCode Unmap(VAddr target, VAddr source, u32 size, VMAPermission perms,
|
||||||
bool privileged = false);
|
bool privileged = false);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void FreeAllMemory();
|
||||||
|
|
||||||
KernelSystem& kernel;
|
KernelSystem& kernel;
|
||||||
|
|
||||||
friend class boost::serialization::access;
|
friend class boost::serialization::access;
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#include <cstring>
|
|
||||||
#include "common/archives.h"
|
#include "common/archives.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "core/hle/kernel/errors.h"
|
#include "core/hle/kernel/errors.h"
|
||||||
|
@ -20,15 +19,21 @@ SharedMemory::~SharedMemory() {
|
||||||
kernel.GetMemoryRegion(MemoryRegion::SYSTEM)
|
kernel.GetMemoryRegion(MemoryRegion::SYSTEM)
|
||||||
->Free(interval.lower(), interval.upper() - interval.lower());
|
->Free(interval.lower(), interval.upper() - interval.lower());
|
||||||
}
|
}
|
||||||
if (base_address != 0 && owner_process != nullptr) {
|
|
||||||
owner_process->vm_manager.ChangeMemoryState(base_address, size, MemoryState::Locked,
|
auto process = owner_process.lock();
|
||||||
|
if (process) {
|
||||||
|
if (base_address != 0) {
|
||||||
|
process->vm_manager.ChangeMemoryState(base_address, size, MemoryState::Locked,
|
||||||
VMAPermission::None, MemoryState::Private,
|
VMAPermission::None, MemoryState::Private,
|
||||||
VMAPermission::ReadWrite);
|
VMAPermission::ReadWrite);
|
||||||
|
} else {
|
||||||
|
process->memory_used -= size;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<std::shared_ptr<SharedMemory>> KernelSystem::CreateSharedMemory(
|
ResultVal<std::shared_ptr<SharedMemory>> KernelSystem::CreateSharedMemory(
|
||||||
Process* owner_process, u32 size, MemoryPermission permissions,
|
std::shared_ptr<Process> owner_process, u32 size, MemoryPermission permissions,
|
||||||
MemoryPermission other_permissions, VAddr address, MemoryRegion region, std::string name) {
|
MemoryPermission other_permissions, VAddr address, MemoryRegion region, std::string name) {
|
||||||
auto shared_memory{std::make_shared<SharedMemory>(*this)};
|
auto shared_memory{std::make_shared<SharedMemory>(*this)};
|
||||||
|
|
||||||
|
@ -52,11 +57,11 @@ ResultVal<std::shared_ptr<SharedMemory>> KernelSystem::CreateSharedMemory(
|
||||||
shared_memory->linear_heap_phys_offset = *offset;
|
shared_memory->linear_heap_phys_offset = *offset;
|
||||||
|
|
||||||
// Increase the amount of used linear heap memory for the owner process.
|
// Increase the amount of used linear heap memory for the owner process.
|
||||||
if (shared_memory->owner_process != nullptr) {
|
if (owner_process != nullptr) {
|
||||||
shared_memory->owner_process->memory_used += size;
|
owner_process->memory_used += size;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
auto& vm_manager = shared_memory->owner_process->vm_manager;
|
auto& vm_manager = owner_process->vm_manager;
|
||||||
// The memory is already available and mapped in the owner process.
|
// The memory is already available and mapped in the owner process.
|
||||||
|
|
||||||
CASCADE_CODE(vm_manager.ChangeMemoryState(address, size, MemoryState::Private,
|
CASCADE_CODE(vm_manager.ChangeMemoryState(address, size, MemoryState::Private,
|
||||||
|
@ -65,7 +70,10 @@ ResultVal<std::shared_ptr<SharedMemory>> KernelSystem::CreateSharedMemory(
|
||||||
|
|
||||||
auto backing_blocks = vm_manager.GetBackingBlocksForRange(address, size);
|
auto backing_blocks = vm_manager.GetBackingBlocksForRange(address, size);
|
||||||
ASSERT(backing_blocks.Succeeded()); // should success after verifying memory state above
|
ASSERT(backing_blocks.Succeeded()); // should success after verifying memory state above
|
||||||
shared_memory->backing_blocks = std::move(backing_blocks).Unwrap();
|
for (const auto& interval : backing_blocks.Unwrap()) {
|
||||||
|
shared_memory->backing_blocks.emplace_back(memory.GetFCRAMRef(interval.lower()),
|
||||||
|
interval.upper() - interval.lower());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_memory->base_address = address;
|
shared_memory->base_address = address;
|
||||||
|
@ -82,14 +90,14 @@ std::shared_ptr<SharedMemory> KernelSystem::CreateSharedMemoryForApplet(
|
||||||
auto backing_blocks = memory_region->HeapAllocate(size);
|
auto backing_blocks = memory_region->HeapAllocate(size);
|
||||||
ASSERT_MSG(!backing_blocks.empty(), "Not enough space in region to allocate shared memory!");
|
ASSERT_MSG(!backing_blocks.empty(), "Not enough space in region to allocate shared memory!");
|
||||||
shared_memory->holding_memory = backing_blocks;
|
shared_memory->holding_memory = backing_blocks;
|
||||||
shared_memory->owner_process = nullptr;
|
shared_memory->owner_process = std::weak_ptr<Process>();
|
||||||
shared_memory->name = std::move(name);
|
shared_memory->name = std::move(name);
|
||||||
shared_memory->size = size;
|
shared_memory->size = size;
|
||||||
shared_memory->permissions = permissions;
|
shared_memory->permissions = permissions;
|
||||||
shared_memory->other_permissions = other_permissions;
|
shared_memory->other_permissions = other_permissions;
|
||||||
for (const auto& interval : backing_blocks) {
|
for (const auto& interval : backing_blocks) {
|
||||||
shared_memory->backing_blocks.push_back(
|
shared_memory->backing_blocks.emplace_back(memory.GetFCRAMRef(interval.lower()),
|
||||||
{memory.GetFCRAMRef(interval.lower()), interval.upper() - interval.lower()});
|
interval.upper() - interval.lower());
|
||||||
std::fill(memory.GetFCRAMPointer(interval.lower()),
|
std::fill(memory.GetFCRAMPointer(interval.lower()),
|
||||||
memory.GetFCRAMPointer(interval.upper()), 0);
|
memory.GetFCRAMPointer(interval.upper()), 0);
|
||||||
}
|
}
|
||||||
|
@ -102,7 +110,7 @@ ResultCode SharedMemory::Map(Process& target_process, VAddr address, MemoryPermi
|
||||||
MemoryPermission other_permissions) {
|
MemoryPermission other_permissions) {
|
||||||
|
|
||||||
MemoryPermission own_other_permissions =
|
MemoryPermission own_other_permissions =
|
||||||
&target_process == owner_process ? this->permissions : this->other_permissions;
|
&target_process == owner_process.lock().get() ? this->permissions : this->other_permissions;
|
||||||
|
|
||||||
// Automatically allocated memory blocks can only be mapped with other_permissions = DontCare
|
// Automatically allocated memory blocks can only be mapped with other_permissions = DontCare
|
||||||
if (base_address == 0 && other_permissions != MemoryPermission::DontCare) {
|
if (base_address == 0 && other_permissions != MemoryPermission::DontCare) {
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include <boost/serialization/base_object.hpp>
|
#include <boost/serialization/base_object.hpp>
|
||||||
#include <boost/serialization/export.hpp>
|
#include <boost/serialization/export.hpp>
|
||||||
#include <boost/serialization/string.hpp>
|
#include <boost/serialization/string.hpp>
|
||||||
|
#include <boost/serialization/weak_ptr.hpp>
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "common/memory_ref.h"
|
#include "common/memory_ref.h"
|
||||||
#include "core/hle/kernel/object.h"
|
#include "core/hle/kernel/object.h"
|
||||||
|
@ -98,7 +99,7 @@ private:
|
||||||
/// Permission restrictions applied to other processes mapping the block.
|
/// Permission restrictions applied to other processes mapping the block.
|
||||||
MemoryPermission other_permissions{};
|
MemoryPermission other_permissions{};
|
||||||
/// Process that created this shared memory block.
|
/// Process that created this shared memory block.
|
||||||
Process* owner_process;
|
std::weak_ptr<Process> owner_process;
|
||||||
/// Address of shared memory block in the owner process if specified.
|
/// Address of shared memory block in the owner process if specified.
|
||||||
VAddr base_address = 0;
|
VAddr base_address = 0;
|
||||||
/// Name of shared memory object.
|
/// Name of shared memory object.
|
||||||
|
|
|
@ -369,6 +369,7 @@ private:
|
||||||
ResultCode ControlMemory(u32* out_addr, u32 addr0, u32 addr1, u32 size, u32 operation,
|
ResultCode ControlMemory(u32* out_addr, u32 addr0, u32 addr1, u32 size, u32 operation,
|
||||||
u32 permissions);
|
u32 permissions);
|
||||||
void ExitProcess();
|
void ExitProcess();
|
||||||
|
ResultCode TerminateProcess(Handle handle);
|
||||||
ResultCode MapMemoryBlock(Handle handle, u32 addr, u32 permissions, u32 other_permissions);
|
ResultCode MapMemoryBlock(Handle handle, u32 addr, u32 permissions, u32 other_permissions);
|
||||||
ResultCode UnmapMemoryBlock(Handle handle, u32 addr);
|
ResultCode UnmapMemoryBlock(Handle handle, u32 addr);
|
||||||
ResultCode ConnectToPort(Handle* out_handle, VAddr port_name_address);
|
ResultCode ConnectToPort(Handle* out_handle, VAddr port_name_address);
|
||||||
|
@ -535,41 +536,18 @@ ResultCode SVC::ControlMemory(u32* out_addr, u32 addr0, u32 addr1, u32 size, u32
|
||||||
}
|
}
|
||||||
|
|
||||||
void SVC::ExitProcess() {
|
void SVC::ExitProcess() {
|
||||||
std::shared_ptr<Process> current_process = kernel.GetCurrentProcess();
|
kernel.TerminateProcess(kernel.GetCurrentProcess());
|
||||||
LOG_INFO(Kernel_SVC, "Process {} exiting", current_process->process_id);
|
}
|
||||||
|
|
||||||
ASSERT_MSG(current_process->status == ProcessStatus::Running, "Process has already exited");
|
ResultCode SVC::TerminateProcess(Handle handle) {
|
||||||
|
std::shared_ptr<Process> process =
|
||||||
current_process->status = ProcessStatus::Exited;
|
kernel.GetCurrentProcess()->handle_table.Get<Process>(handle);
|
||||||
|
if (process == nullptr) {
|
||||||
// Stop all the process threads that are currently waiting for objects.
|
return ERR_INVALID_HANDLE;
|
||||||
const auto thread_list = kernel.GetCurrentThreadManager().GetThreadList();
|
|
||||||
for (auto& thread : thread_list) {
|
|
||||||
if (thread->owner_process.lock() != current_process) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (thread.get() == kernel.GetCurrentThreadManager().GetCurrentThread()) {
|
kernel.TerminateProcess(process);
|
||||||
continue;
|
return RESULT_SUCCESS;
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(Subv): When are the other running/ready threads terminated?
|
|
||||||
ASSERT_MSG(thread->status == ThreadStatus::WaitSynchAny ||
|
|
||||||
thread->status == ThreadStatus::WaitSynchAll,
|
|
||||||
"Exiting processes with non-waiting threads is currently unimplemented");
|
|
||||||
|
|
||||||
thread->Stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
current_process->Exit();
|
|
||||||
|
|
||||||
// Kill the current thread
|
|
||||||
kernel.GetCurrentThreadManager().GetCurrentThread()->Stop();
|
|
||||||
|
|
||||||
// Remove kernel reference to process so it can be cleaned up.
|
|
||||||
kernel.RemoveProcess(current_process);
|
|
||||||
|
|
||||||
system.PrepareReschedule();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Maps a memory block to specified address
|
/// Maps a memory block to specified address
|
||||||
|
@ -1690,7 +1668,7 @@ ResultCode SVC::CreateMemoryBlock(Handle* out_handle, u32 addr, u32 size, u32 my
|
||||||
|
|
||||||
CASCADE_RESULT(shared_memory,
|
CASCADE_RESULT(shared_memory,
|
||||||
kernel.CreateSharedMemory(
|
kernel.CreateSharedMemory(
|
||||||
current_process.get(), size, static_cast<MemoryPermission>(my_permission),
|
current_process, size, static_cast<MemoryPermission>(my_permission),
|
||||||
static_cast<MemoryPermission>(other_permission), addr, region));
|
static_cast<MemoryPermission>(other_permission), addr, region));
|
||||||
CASCADE_RESULT(*out_handle, current_process->handle_table.Create(std::move(shared_memory)));
|
CASCADE_RESULT(*out_handle, current_process->handle_table.Create(std::move(shared_memory)));
|
||||||
|
|
||||||
|
@ -2244,7 +2222,7 @@ const std::array<SVC::FunctionDef, 180> SVC::SVC_Table{{
|
||||||
{0x73, nullptr, "CreateCodeSet"},
|
{0x73, nullptr, "CreateCodeSet"},
|
||||||
{0x74, nullptr, "RandomStub"},
|
{0x74, nullptr, "RandomStub"},
|
||||||
{0x75, nullptr, "CreateProcess"},
|
{0x75, nullptr, "CreateProcess"},
|
||||||
{0x76, nullptr, "TerminateProcess"},
|
{0x76, &SVC::Wrap<&SVC::TerminateProcess>, "TerminateProcess"},
|
||||||
{0x77, nullptr, "SetProcessResourceLimits"},
|
{0x77, nullptr, "SetProcessResourceLimits"},
|
||||||
{0x78, nullptr, "CreateResourceLimit"},
|
{0x78, nullptr, "CreateResourceLimit"},
|
||||||
{0x79, nullptr, "SetResourceLimitValues"},
|
{0x79, nullptr, "SetResourceLimitValues"},
|
||||||
|
|
|
@ -199,11 +199,34 @@ void ThreadManager::WaitCurrentThread_Sleep() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ThreadManager::ExitCurrentThread() {
|
void ThreadManager::ExitCurrentThread() {
|
||||||
Thread* thread = GetCurrentThread();
|
current_thread->Stop();
|
||||||
|
std::erase(thread_list, current_thread);
|
||||||
|
kernel.PrepareReschedule();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ThreadManager::TerminateProcessThreads(std::shared_ptr<Process> process) {
|
||||||
|
auto iter = thread_list.begin();
|
||||||
|
while (iter != thread_list.end()) {
|
||||||
|
auto& thread = *iter;
|
||||||
|
if (thread == current_thread || thread->owner_process.lock() != process) {
|
||||||
|
iter++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thread->status != ThreadStatus::WaitSynchAny &&
|
||||||
|
thread->status != ThreadStatus::WaitSynchAll) {
|
||||||
|
// TODO: How does the real kernel handle non-waiting threads?
|
||||||
|
LOG_WARNING(Kernel, "Terminating non-waiting thread {}", thread->thread_id);
|
||||||
|
}
|
||||||
|
|
||||||
thread->Stop();
|
thread->Stop();
|
||||||
thread_list.erase(std::remove_if(thread_list.begin(), thread_list.end(),
|
iter = thread_list.erase(iter);
|
||||||
[thread](const auto& p) { return p.get() == thread; }),
|
}
|
||||||
thread_list.end());
|
|
||||||
|
// Kill the current thread last, if applicable.
|
||||||
|
if (current_thread != nullptr && current_thread->owner_process.lock() == process) {
|
||||||
|
ExitCurrentThread();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ThreadManager::ThreadWakeupCallback(u64 thread_id, s64 cycles_late) {
|
void ThreadManager::ThreadWakeupCallback(u64 thread_id, s64 cycles_late) {
|
||||||
|
@ -295,32 +318,6 @@ void ThreadManager::DebugThreadQueue() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds a free location for the TLS section of a thread.
|
|
||||||
* @param tls_slots The TLS page array of the thread's owner process.
|
|
||||||
* Returns a tuple of (page, slot, alloc_needed) where:
|
|
||||||
* page: The index of the first allocated TLS page that has free slots.
|
|
||||||
* slot: The index of the first free slot in the indicated page.
|
|
||||||
* alloc_needed: Whether there's a need to allocate a new TLS page (All pages are full).
|
|
||||||
*/
|
|
||||||
static std::tuple<std::size_t, std::size_t, bool> GetFreeThreadLocalSlot(
|
|
||||||
std::span<const std::bitset<8>> tls_slots) {
|
|
||||||
// Iterate over all the allocated pages, and try to find one where not all slots are used.
|
|
||||||
for (std::size_t page = 0; page < tls_slots.size(); ++page) {
|
|
||||||
const auto& page_tls_slots = tls_slots[page];
|
|
||||||
if (!page_tls_slots.all()) {
|
|
||||||
// We found a page with at least one free slot, find which slot it is
|
|
||||||
for (std::size_t slot = 0; slot < page_tls_slots.size(); ++slot) {
|
|
||||||
if (!page_tls_slots.test(slot)) {
|
|
||||||
return std::make_tuple(page, slot, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::make_tuple(0, 0, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resets a thread context, making it ready to be scheduled and run by the CPU
|
* Resets a thread context, making it ready to be scheduled and run by the CPU
|
||||||
* @param context Thread context to reset
|
* @param context Thread context to reset
|
||||||
|
@ -376,45 +373,7 @@ ResultVal<std::shared_ptr<Thread>> KernelSystem::CreateThread(
|
||||||
thread->name = std::move(name);
|
thread->name = std::move(name);
|
||||||
thread_managers[processor_id]->wakeup_callback_table[thread->thread_id] = thread.get();
|
thread_managers[processor_id]->wakeup_callback_table[thread->thread_id] = thread.get();
|
||||||
thread->owner_process = owner_process;
|
thread->owner_process = owner_process;
|
||||||
|
CASCADE_RESULT(thread->tls_address, owner_process->AllocateThreadLocalStorage());
|
||||||
// Find the next available TLS index, and mark it as used
|
|
||||||
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.
|
|
||||||
auto memory_region = GetMemoryRegion(MemoryRegion::BASE);
|
|
||||||
|
|
||||||
// Allocate some memory from the end of the linear heap for this region.
|
|
||||||
auto offset = memory_region->LinearAllocate(Memory::CITRA_PAGE_SIZE);
|
|
||||||
if (!offset) {
|
|
||||||
LOG_ERROR(Kernel_SVC,
|
|
||||||
"Not enough space in region to allocate a new TLS page for thread");
|
|
||||||
return ERR_OUT_OF_MEMORY;
|
|
||||||
}
|
|
||||||
owner_process->memory_used += Memory::CITRA_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;
|
|
||||||
|
|
||||||
// Map the page to the current process' address space.
|
|
||||||
vm_manager.MapBackingMemory(
|
|
||||||
Memory::TLS_AREA_VADDR + static_cast<VAddr>(available_page) * Memory::CITRA_PAGE_SIZE,
|
|
||||||
memory.GetFCRAMRef(*offset), Memory::CITRA_PAGE_SIZE, MemoryState::Locked);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark the slot as used
|
|
||||||
tls_slots[available_page].set(available_slot);
|
|
||||||
thread->tls_address = Memory::TLS_AREA_VADDR +
|
|
||||||
static_cast<VAddr>(available_page) * Memory::CITRA_PAGE_SIZE +
|
|
||||||
static_cast<VAddr>(available_slot) * Memory::TLS_ENTRY_SIZE;
|
|
||||||
|
|
||||||
memory.ZeroBlock(*owner_process, thread->tls_address, Memory::TLS_ENTRY_SIZE);
|
|
||||||
|
|
||||||
// TODO(peachum): move to ScheduleThread() when scheduler is added so selected core is used
|
// TODO(peachum): move to ScheduleThread() when scheduler is added so selected core is used
|
||||||
// to initialize the context
|
// to initialize the context
|
||||||
|
|
|
@ -111,6 +111,11 @@ public:
|
||||||
*/
|
*/
|
||||||
void ExitCurrentThread();
|
void ExitCurrentThread();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Terminates all threads belonging to a specific process.
|
||||||
|
*/
|
||||||
|
void TerminateProcessThreads(std::shared_ptr<Process> process);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a const reference to the thread list for debug use
|
* Get a const reference to the thread list for debug use
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -391,9 +391,9 @@ void VMManager::UpdatePageTableForVMA(const VirtualMemoryArea& vma) {
|
||||||
plgldr->OnMemoryChanged(process, Core::System::GetInstance().Kernel());
|
plgldr->OnMemoryChanged(process, Core::System::GetInstance().Kernel());
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<std::vector<std::pair<MemoryRef, u32>>> VMManager::GetBackingBlocksForRange(VAddr address,
|
ResultVal<MemoryRegionInfo::IntervalSet> VMManager::GetBackingBlocksForRange(VAddr address,
|
||||||
u32 size) {
|
u32 size) {
|
||||||
std::vector<std::pair<MemoryRef, u32>> backing_blocks;
|
MemoryRegionInfo::IntervalSet backing_blocks;
|
||||||
VAddr interval_target = address;
|
VAddr interval_target = address;
|
||||||
while (interval_target != address + size) {
|
while (interval_target != address + size) {
|
||||||
auto vma = FindVMA(interval_target);
|
auto vma = FindVMA(interval_target);
|
||||||
|
@ -404,8 +404,10 @@ ResultVal<std::vector<std::pair<MemoryRef, u32>>> VMManager::GetBackingBlocksFor
|
||||||
|
|
||||||
VAddr interval_end = std::min(address + size, vma->second.base + vma->second.size);
|
VAddr interval_end = std::min(address + size, vma->second.base + vma->second.size);
|
||||||
u32 interval_size = interval_end - interval_target;
|
u32 interval_size = interval_end - interval_target;
|
||||||
auto backing_memory = vma->second.backing_memory + (interval_target - vma->second.base);
|
auto backing_memory = memory.GetFCRAMOffset(vma->second.backing_memory +
|
||||||
backing_blocks.push_back({backing_memory, interval_size});
|
(interval_target - vma->second.base));
|
||||||
|
backing_blocks +=
|
||||||
|
MemoryRegionInfo::Interval(backing_memory, backing_memory + interval_size);
|
||||||
|
|
||||||
interval_target += interval_size;
|
interval_target += interval_size;
|
||||||
}
|
}
|
||||||
|
|
|
@ -205,8 +205,7 @@ public:
|
||||||
void LogLayout(Common::Log::Level log_level) const;
|
void LogLayout(Common::Log::Level log_level) const;
|
||||||
|
|
||||||
/// Gets a list of backing memory blocks for the specified range
|
/// Gets a list of backing memory blocks for the specified range
|
||||||
ResultVal<std::vector<std::pair<MemoryRef, u32>>> GetBackingBlocksForRange(VAddr address,
|
ResultVal<MemoryRegionInfo::IntervalSet> GetBackingBlocksForRange(VAddr address, u32 size);
|
||||||
u32 size);
|
|
||||||
|
|
||||||
/// Each VMManager has its own page table, which is set as the main one when the owning process
|
/// Each VMManager has its own page table, which is set as the main one when the owning process
|
||||||
/// is scheduled.
|
/// is scheduled.
|
||||||
|
|
|
@ -988,13 +988,18 @@ ResultCode AppletManager::PrepareToDoApplicationJump(u64 title_id, FS::MediaType
|
||||||
// Save the title data to send it to the Home Menu when DoApplicationJump is called.
|
// Save the title data to send it to the Home Menu when DoApplicationJump is called.
|
||||||
auto application_slot_data = GetAppletSlot(AppletSlot::Application);
|
auto application_slot_data = GetAppletSlot(AppletSlot::Application);
|
||||||
app_jump_parameters.current_title_id = application_slot_data->title_id;
|
app_jump_parameters.current_title_id = application_slot_data->title_id;
|
||||||
// TODO(Subv): Retrieve the correct media type of the currently-running application. For now
|
// TODO: Basic heuristic to guess media type, needs proper implementation.
|
||||||
// just assume NAND.
|
app_jump_parameters.current_media_type =
|
||||||
app_jump_parameters.current_media_type = FS::MediaType::NAND;
|
((application_slot_data->title_id >> 32) & 0xFFFFFFFF) == 0x00040000
|
||||||
app_jump_parameters.next_title_id = flags == ApplicationJumpFlags::UseCurrentParameters
|
? Service::FS::MediaType::SDMC
|
||||||
? application_slot_data->title_id
|
: Service::FS::MediaType::NAND;
|
||||||
: title_id;
|
if (flags == ApplicationJumpFlags::UseCurrentParameters) {
|
||||||
|
app_jump_parameters.next_title_id = app_jump_parameters.current_title_id;
|
||||||
|
app_jump_parameters.next_media_type = app_jump_parameters.current_media_type;
|
||||||
|
} else {
|
||||||
|
app_jump_parameters.next_title_id = title_id;
|
||||||
app_jump_parameters.next_media_type = media_type;
|
app_jump_parameters.next_media_type = media_type;
|
||||||
|
}
|
||||||
app_jump_parameters.flags = flags;
|
app_jump_parameters.flags = flags;
|
||||||
|
|
||||||
// Note: The real console uses the Home Menu to perform the application jump, therefore the menu
|
// Note: The real console uses the Home Menu to perform the application jump, therefore the menu
|
||||||
|
@ -1020,36 +1025,26 @@ ResultCode AppletManager::DoApplicationJump(const DeliverArg& arg) {
|
||||||
deliver_arg->source_program_id = title_id;
|
deliver_arg->source_program_id = title_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(Subv): Terminate the current Application.
|
if (GetAppletSlot(AppletSlot::HomeMenu)->registered) {
|
||||||
|
// If the home menu is running, use it to jump to the next application.
|
||||||
|
// The home menu will call GetProgramIdOnApplicationJump and
|
||||||
|
// PrepareToStartApplication/StartApplication to launch the title.
|
||||||
|
active_slot = AppletSlot::HomeMenu;
|
||||||
|
SendParameter({
|
||||||
|
.sender_id = AppletId::Application,
|
||||||
|
.destination_id = AppletId::HomeMenu,
|
||||||
|
.signal = SignalType::WakeupToLaunchApplication,
|
||||||
|
});
|
||||||
|
|
||||||
// Note: The real console sends signal 17 (WakeupToLaunchApplication) to the Home Menu, this
|
// TODO: APT terminates the application here, usually it will exit itself properly though.
|
||||||
// prompts it to call GetProgramIdOnApplicationJump and
|
|
||||||
// PrepareToStartApplication/StartApplication on the title to launch.
|
|
||||||
active_slot = AppletSlot::Application;
|
|
||||||
|
|
||||||
// Perform a soft-reset if we're trying to relaunch the same title.
|
|
||||||
// TODO(Subv): Note that this reboots the entire emulated system, a better way would be to
|
|
||||||
// simply re-launch the title without closing all services, but this would only work for
|
|
||||||
// installed titles since we have no way of getting the file path of an arbitrary game dump
|
|
||||||
// based only on the title id.
|
|
||||||
|
|
||||||
auto new_path = Service::AM::GetTitleContentPath(app_jump_parameters.next_media_type,
|
|
||||||
app_jump_parameters.next_title_id);
|
|
||||||
if (new_path.empty() || !FileUtil::Exists(new_path)) {
|
|
||||||
LOG_CRITICAL(
|
|
||||||
Service_APT,
|
|
||||||
"Failed to find title during application jump: {} Resetting current title instead.",
|
|
||||||
new_path);
|
|
||||||
new_path.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
system.RequestReset(new_path);
|
|
||||||
return RESULT_SUCCESS;
|
return RESULT_SUCCESS;
|
||||||
|
} else {
|
||||||
|
// Otherwise, work around the missing home menu by launching the title directly.
|
||||||
|
|
||||||
// Launch the title directly.
|
// TODO: The emulator does not support terminating the old process immediately.
|
||||||
// The emulator does not suport terminating old processes, would require a lot of cleanup
|
// We could call TerminateProcess but references to the process are still held elsewhere,
|
||||||
// This code is left commented for when this is implemented, for now we cannot use NS
|
// preventing clean up. This code is left commented for when this is implemented, for now we
|
||||||
// as the old process resources would interfere with the new ones
|
// cannot use NS as the old process resources would interfere with the new ones.
|
||||||
/*
|
/*
|
||||||
auto process =
|
auto process =
|
||||||
NS::LaunchTitle(app_jump_parameters.next_media_type, app_jump_parameters.next_title_id);
|
NS::LaunchTitle(app_jump_parameters.next_media_type, app_jump_parameters.next_title_id);
|
||||||
|
@ -1059,6 +1054,22 @@ ResultCode AppletManager::DoApplicationJump(const DeliverArg& arg) {
|
||||||
}
|
}
|
||||||
return RESULT_SUCCESS;
|
return RESULT_SUCCESS;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
auto new_path = Service::AM::GetTitleContentPath(app_jump_parameters.next_media_type,
|
||||||
|
app_jump_parameters.next_title_id);
|
||||||
|
if (new_path.empty() || !FileUtil::Exists(new_path)) {
|
||||||
|
// TODO: This can happen if the requested title is not installed. Need a way to find
|
||||||
|
// non-installed titles in the game list.
|
||||||
|
LOG_CRITICAL(
|
||||||
|
Service_APT,
|
||||||
|
"Failed to find title during application jump: {} Resetting current title instead.",
|
||||||
|
new_path);
|
||||||
|
new_path.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
system.RequestReset(new_path);
|
||||||
|
return RESULT_SUCCESS;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultCode AppletManager::PrepareToStartApplication(u64 title_id, FS::MediaType media_type) {
|
ResultCode AppletManager::PrepareToStartApplication(u64 title_id, FS::MediaType media_type) {
|
||||||
|
|
Reference in New Issue