pm: complete rewrite
This commit is contained in:
45
stratosphere/pm/source/impl/pm_process_info.cpp
Normal file
45
stratosphere/pm/source/impl/pm_process_info.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stratosphere/sm/sm_manager_api.hpp>
|
||||
#include <stratosphere/ldr/ldr_pm_api.hpp>
|
||||
|
||||
#include "pm_process_info.hpp"
|
||||
|
||||
namespace sts::pm::impl {
|
||||
|
||||
ProcessInfo::ProcessInfo(Handle h, u64 pid, ldr::PinId pin, const ncm::TitleLocation &l) : process_id(pid), pin_id(pin), loc(l), handle(h), state(ProcessState_Created), flags(0) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
ProcessInfo::~ProcessInfo() {
|
||||
this->Cleanup();
|
||||
}
|
||||
|
||||
void ProcessInfo::Cleanup() {
|
||||
if (this->handle != INVALID_HANDLE) {
|
||||
/* Unregister the process. */
|
||||
fsprUnregisterProgram(this->process_id);
|
||||
sm::manager::UnregisterProcess(this->process_id);
|
||||
ldr::pm::UnpinTitle(this->pin_id);
|
||||
|
||||
/* Close the process's handle. */
|
||||
svcCloseHandle(this->handle);
|
||||
this->handle = INVALID_HANDLE;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
310
stratosphere/pm/source/impl/pm_process_info.hpp
Normal file
310
stratosphere/pm/source/impl/pm_process_info.hpp
Normal file
@@ -0,0 +1,310 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include <stratosphere/ldr.hpp>
|
||||
#include <stratosphere/pm.hpp>
|
||||
|
||||
#include "pm_process_manager.hpp"
|
||||
|
||||
namespace sts::pm::impl {
|
||||
|
||||
class ProcessInfo {
|
||||
NON_COPYABLE(ProcessInfo);
|
||||
private:
|
||||
enum Flag : u32 {
|
||||
Flag_SignalOnExit = (1 << 0),
|
||||
Flag_ExceptionOccurred = (1 << 1),
|
||||
Flag_ExceptionWaitingAttach = (1 << 2),
|
||||
Flag_SignalOnDebugEvent = (1 << 3),
|
||||
Flag_SuspendedStateChanged = (1 << 4),
|
||||
Flag_Suspended = (1 << 5),
|
||||
Flag_Application = (1 << 6),
|
||||
Flag_SignalOnStart = (1 << 7),
|
||||
Flag_StartedStateChanged = (1 << 8),
|
||||
};
|
||||
private:
|
||||
const u64 process_id;
|
||||
const ldr::PinId pin_id;
|
||||
const ncm::TitleLocation loc;
|
||||
Handle handle;
|
||||
ProcessState state;
|
||||
u32 flags;
|
||||
private:
|
||||
void SetFlag(Flag flag) {
|
||||
this->flags |= flag;
|
||||
}
|
||||
|
||||
void ClearFlag(Flag flag) {
|
||||
this->flags &= ~flag;
|
||||
}
|
||||
|
||||
bool HasFlag(Flag flag) const {
|
||||
return (this->flags & flag);
|
||||
}
|
||||
public:
|
||||
ProcessInfo(Handle h, u64 pid, ldr::PinId pin, const ncm::TitleLocation &l);
|
||||
~ProcessInfo();
|
||||
void Cleanup();
|
||||
|
||||
Handle GetHandle() const {
|
||||
return this->handle;
|
||||
}
|
||||
|
||||
u64 GetProcessId() const {
|
||||
return this->process_id;
|
||||
}
|
||||
|
||||
ldr::PinId GetPinId() const {
|
||||
return this->pin_id;
|
||||
}
|
||||
|
||||
const ncm::TitleLocation &GetTitleLocation() {
|
||||
return this->loc;
|
||||
}
|
||||
|
||||
ProcessState GetState() const {
|
||||
return this->state;
|
||||
}
|
||||
|
||||
void SetState(ProcessState state) {
|
||||
this->state = state;
|
||||
}
|
||||
|
||||
void SetSignalOnExit() {
|
||||
this->SetFlag(Flag_SignalOnExit);
|
||||
}
|
||||
|
||||
void SetExceptionOccurred() {
|
||||
this->SetFlag(Flag_ExceptionOccurred);
|
||||
this->SetFlag(Flag_ExceptionWaitingAttach);
|
||||
}
|
||||
|
||||
void ClearExceptionOccurred() {
|
||||
this->ClearFlag(Flag_ExceptionOccurred);
|
||||
}
|
||||
|
||||
void ClearExceptionWaitingAttach() {
|
||||
this->ClearFlag(Flag_ExceptionWaitingAttach);
|
||||
}
|
||||
|
||||
void SetSignalOnDebugEvent() {
|
||||
this->SetFlag(Flag_SignalOnDebugEvent);
|
||||
}
|
||||
|
||||
void SetSuspendedStateChanged() {
|
||||
this->SetFlag(Flag_SuspendedStateChanged);
|
||||
}
|
||||
|
||||
void ClearSuspendedStateChanged() {
|
||||
this->ClearFlag(Flag_SuspendedStateChanged);
|
||||
}
|
||||
|
||||
void SetSuspended() {
|
||||
this->SetFlag(Flag_Suspended);
|
||||
}
|
||||
|
||||
void ClearSuspended() {
|
||||
this->ClearFlag(Flag_Suspended);
|
||||
}
|
||||
|
||||
void SetApplication() {
|
||||
this->SetFlag(Flag_Application);
|
||||
}
|
||||
|
||||
void SetSignalOnStart() {
|
||||
this->SetFlag(Flag_SignalOnStart);
|
||||
}
|
||||
|
||||
void ClearSignalOnStart() {
|
||||
this->ClearFlag(Flag_SignalOnStart);
|
||||
}
|
||||
|
||||
void SetStartedStateChanged() {
|
||||
this->SetFlag(Flag_StartedStateChanged);
|
||||
}
|
||||
|
||||
void ClearStartedStateChanged() {
|
||||
this->ClearFlag(Flag_StartedStateChanged);
|
||||
}
|
||||
|
||||
bool HasStarted() const {
|
||||
return this->state != ProcessState_Created && this->state != ProcessState_CreatedAttached;
|
||||
}
|
||||
|
||||
bool HasExited() const {
|
||||
return this->state == ProcessState_Exited;
|
||||
}
|
||||
|
||||
bool ShouldSignalOnExit() const {
|
||||
return this->HasFlag(Flag_SignalOnExit);
|
||||
}
|
||||
|
||||
bool HasExceptionOccurred() const {
|
||||
return this->HasFlag(Flag_ExceptionOccurred);
|
||||
}
|
||||
|
||||
bool HasExceptionWaitingAttach() const {
|
||||
return this->HasFlag(Flag_ExceptionWaitingAttach);
|
||||
}
|
||||
|
||||
bool ShouldSignalOnDebugEvent() const {
|
||||
return this->HasFlag(Flag_SignalOnDebugEvent);
|
||||
}
|
||||
|
||||
bool ShouldSignalOnStart() const {
|
||||
return this->HasFlag(Flag_SignalOnStart);
|
||||
}
|
||||
|
||||
bool HasSuspendedStateChanged() const {
|
||||
return this->HasFlag(Flag_SuspendedStateChanged);
|
||||
}
|
||||
|
||||
bool IsSuspended() const {
|
||||
return this->HasFlag(Flag_Suspended);
|
||||
}
|
||||
|
||||
bool IsApplication() const {
|
||||
return this->HasFlag(Flag_Application);
|
||||
}
|
||||
|
||||
bool HasStartedStateChanged() const {
|
||||
return this->HasFlag(Flag_StartedStateChanged);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
Result OnProcessSignaled(std::shared_ptr<ProcessInfo> process_info);
|
||||
|
||||
class ProcessInfoWaiter final : public IWaitable {
|
||||
private:
|
||||
std::shared_ptr<ProcessInfo> process_info;
|
||||
public:
|
||||
ProcessInfoWaiter(std::shared_ptr<ProcessInfo> p) : process_info(p) { /* ... */ }
|
||||
|
||||
/* IWaitable */
|
||||
Handle GetHandle() override {
|
||||
return this->process_info->GetHandle();
|
||||
}
|
||||
|
||||
Result HandleSignaled(u64 timeout) override {
|
||||
return OnProcessSignaled(this->process_info);
|
||||
}
|
||||
};
|
||||
|
||||
class ProcessList final {
|
||||
private:
|
||||
HosMutex lock;
|
||||
std::vector<std::shared_ptr<ProcessInfo>> processes;
|
||||
public:
|
||||
void Lock() {
|
||||
this->lock.Lock();
|
||||
}
|
||||
|
||||
void Unlock() {
|
||||
this->lock.Unlock();
|
||||
}
|
||||
|
||||
size_t GetSize() const {
|
||||
return this->processes.size();
|
||||
}
|
||||
|
||||
std::shared_ptr<ProcessInfo> Pop() {
|
||||
auto front = this->processes[0];
|
||||
this->processes.erase(this->processes.begin());
|
||||
return front;
|
||||
}
|
||||
|
||||
void Add(std::shared_ptr<ProcessInfo> process_info) {
|
||||
this->processes.push_back(process_info);
|
||||
}
|
||||
|
||||
void Remove(u64 process_id) {
|
||||
for (size_t i = 0; i < this->processes.size(); i++) {
|
||||
if (this->processes[i]->GetProcessId() == process_id) {
|
||||
this->processes.erase(this->processes.begin() + i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<ProcessInfo> Find(u64 process_id) {
|
||||
for (size_t i = 0; i < this->processes.size(); i++) {
|
||||
if (this->processes[i]->GetProcessId() == process_id) {
|
||||
return this->processes[i];
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<ProcessInfo> Find(ncm::TitleId title_id) {
|
||||
for (size_t i = 0; i < this->processes.size(); i++) {
|
||||
if (this->processes[i]->GetTitleLocation().title_id == title_id) {
|
||||
return this->processes[i];
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<ProcessInfo> operator[](int i) {
|
||||
return this->processes[i];
|
||||
}
|
||||
|
||||
const std::shared_ptr<ProcessInfo> operator[](int i) const {
|
||||
return this->processes[i];
|
||||
}
|
||||
};
|
||||
|
||||
class ProcessListAccessor final {
|
||||
private:
|
||||
ProcessList &list;
|
||||
public:
|
||||
explicit ProcessListAccessor(ProcessList &l) : list(l) {
|
||||
this->list.Lock();
|
||||
}
|
||||
|
||||
~ProcessListAccessor() {
|
||||
this->list.Unlock();
|
||||
}
|
||||
|
||||
ProcessList *operator->() {
|
||||
return &this->list;
|
||||
}
|
||||
|
||||
const ProcessList *operator->() const {
|
||||
return &this->list;
|
||||
}
|
||||
|
||||
ProcessList &operator*() {
|
||||
return this->list;
|
||||
}
|
||||
|
||||
const ProcessList &operator*() const {
|
||||
return this->list;
|
||||
}
|
||||
|
||||
std::shared_ptr<ProcessInfo> operator[](int i) {
|
||||
return this->list[i];
|
||||
}
|
||||
|
||||
const std::shared_ptr<ProcessInfo> operator[](int i) const {
|
||||
return this->list[i];
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
679
stratosphere/pm/source/impl/pm_process_manager.cpp
Normal file
679
stratosphere/pm/source/impl/pm_process_manager.cpp
Normal file
@@ -0,0 +1,679 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <atomic>
|
||||
#include <stratosphere/spl.hpp>
|
||||
#include <stratosphere/ldr/ldr_pm_api.hpp>
|
||||
#include <stratosphere/sm/sm_manager_api.hpp>
|
||||
|
||||
#include "pm_process_manager.hpp"
|
||||
#include "pm_resource_manager.hpp"
|
||||
|
||||
#include "pm_process_info.hpp"
|
||||
|
||||
#include "../boot2/boot2_api.hpp"
|
||||
|
||||
namespace sts::pm::impl {
|
||||
|
||||
namespace {
|
||||
|
||||
/* Types. */
|
||||
enum HookType {
|
||||
HookType_TitleId = (1 << 0),
|
||||
HookType_Application = (1 << 1),
|
||||
};
|
||||
|
||||
struct LaunchProcessArgs {
|
||||
u64 *out_process_id;
|
||||
ncm::TitleLocation location;
|
||||
u32 flags;
|
||||
};
|
||||
|
||||
enum LaunchFlags {
|
||||
LaunchFlags_None = 0,
|
||||
LaunchFlags_SignalOnExit = (1 << 0),
|
||||
LaunchFlags_SignalOnStart = (1 << 1),
|
||||
LaunchFlags_SignalOnException = (1 << 2),
|
||||
LaunchFlags_SignalOnDebugEvent = (1 << 3),
|
||||
LaunchFlags_StartSuspended = (1 << 4),
|
||||
LaunchFlags_DisableAslr = (1 << 5),
|
||||
};
|
||||
|
||||
enum LaunchFlagsDeprecated {
|
||||
LaunchFlagsDeprecated_None = 0,
|
||||
LaunchFlagsDeprecated_SignalOnExit = (1 << 0),
|
||||
LaunchFlagsDeprecated_StartSuspended = (1 << 1),
|
||||
LaunchFlagsDeprecated_SignalOnException = (1 << 2),
|
||||
LaunchFlagsDeprecated_DisableAslr = (1 << 3),
|
||||
LaunchFlagsDeprecated_SignalOnDebugEvent = (1 << 4),
|
||||
LaunchFlagsDeprecated_SignalOnStart = (1 << 5),
|
||||
};
|
||||
|
||||
#define GET_FLAG_MASK(flag) (firmware_version >= FirmwareVersion_500 ? static_cast<u32>(LaunchFlags_##flag) : static_cast<u32>(LaunchFlagsDeprecated_##flag))
|
||||
|
||||
inline bool ShouldSignalOnExit(u32 launch_flags) {
|
||||
const auto firmware_version = GetRuntimeFirmwareVersion();
|
||||
return launch_flags & GET_FLAG_MASK(SignalOnExit);
|
||||
}
|
||||
|
||||
inline bool ShouldSignalOnStart(u32 launch_flags) {
|
||||
const auto firmware_version = GetRuntimeFirmwareVersion();
|
||||
if (firmware_version < FirmwareVersion_200) {
|
||||
return false;
|
||||
}
|
||||
return launch_flags & GET_FLAG_MASK(SignalOnStart);
|
||||
}
|
||||
|
||||
inline bool ShouldSignalOnException(u32 launch_flags) {
|
||||
const auto firmware_version = GetRuntimeFirmwareVersion();
|
||||
return launch_flags & GET_FLAG_MASK(SignalOnException);
|
||||
}
|
||||
|
||||
inline bool ShouldSignalOnDebugEvent(u32 launch_flags) {
|
||||
const auto firmware_version = GetRuntimeFirmwareVersion();
|
||||
return launch_flags & GET_FLAG_MASK(SignalOnDebugEvent);
|
||||
}
|
||||
|
||||
inline bool ShouldStartSuspended(u32 launch_flags) {
|
||||
const auto firmware_version = GetRuntimeFirmwareVersion();
|
||||
return launch_flags & GET_FLAG_MASK(StartSuspended);
|
||||
}
|
||||
|
||||
inline bool ShouldDisableAslr(u32 launch_flags) {
|
||||
const auto firmware_version = GetRuntimeFirmwareVersion();
|
||||
return launch_flags & GET_FLAG_MASK(DisableAslr);
|
||||
}
|
||||
|
||||
#undef GET_FLAG_MASK
|
||||
|
||||
enum class ProcessEvent {
|
||||
None = 0,
|
||||
Exited = 1,
|
||||
Started = 2,
|
||||
Exception = 3,
|
||||
DebugRunning = 4,
|
||||
DebugSuspended = 5,
|
||||
};
|
||||
|
||||
enum class ProcessEventDeprecated {
|
||||
None = 0,
|
||||
Exception = 1,
|
||||
Exited = 2,
|
||||
DebugRunning = 3,
|
||||
DebugSuspended = 4,
|
||||
Started = 5,
|
||||
};
|
||||
|
||||
inline u32 GetProcessEventValue(ProcessEvent event) {
|
||||
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_500) {
|
||||
return static_cast<u32>(event);
|
||||
}
|
||||
switch (event) {
|
||||
case ProcessEvent::None:
|
||||
return static_cast<u32>(ProcessEventDeprecated::None);
|
||||
case ProcessEvent::Exited:
|
||||
return static_cast<u32>(ProcessEventDeprecated::Exited);
|
||||
case ProcessEvent::Started:
|
||||
return static_cast<u32>(ProcessEventDeprecated::Started);
|
||||
case ProcessEvent::Exception:
|
||||
return static_cast<u32>(ProcessEventDeprecated::Exception);
|
||||
case ProcessEvent::DebugRunning:
|
||||
return static_cast<u32>(ProcessEventDeprecated::DebugRunning);
|
||||
case ProcessEvent::DebugSuspended:
|
||||
return static_cast<u32>(ProcessEventDeprecated::DebugSuspended);
|
||||
default:
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
/* Process Tracking globals. */
|
||||
HosThread g_process_track_thread;
|
||||
SessionManagerBase *g_process_waitable_manager = nullptr;
|
||||
|
||||
/* Process lists. */
|
||||
ProcessList g_process_list;
|
||||
ProcessList g_dead_process_list;
|
||||
|
||||
/* Global events. */
|
||||
IEvent *g_process_event = nullptr;
|
||||
IEvent *g_hook_to_create_process_event = nullptr;
|
||||
IEvent *g_hook_to_create_application_process_event = nullptr;
|
||||
IEvent *g_boot_finished_event = nullptr;
|
||||
|
||||
/* Process Launch synchronization globals. */
|
||||
IEvent *g_process_launch_start_event = nullptr;
|
||||
HosSignal g_process_launch_finish_signal;
|
||||
Result g_process_launch_result = ResultSuccess;
|
||||
LaunchProcessArgs g_process_launch_args = {};
|
||||
|
||||
/* Hook globals. */
|
||||
std::atomic<ncm::TitleId> g_title_id_hook;
|
||||
std::atomic<bool> g_application_hook;
|
||||
|
||||
/* Helpers. */
|
||||
void ProcessTrackingMain(void *arg) {
|
||||
/* This is the main loop of the process tracking thread. */
|
||||
|
||||
/* Create waitable manager. */
|
||||
static auto s_process_waiter = WaitableManager(1);
|
||||
g_process_waitable_manager = &s_process_waiter;
|
||||
|
||||
/* Service processes. */
|
||||
g_process_waitable_manager->AddWaitable(g_process_launch_start_event);
|
||||
g_process_waitable_manager->Process();
|
||||
}
|
||||
|
||||
inline u32 GetLoaderCreateProcessFlags(u32 launch_flags) {
|
||||
u32 ldr_flags = 0;
|
||||
|
||||
if (ShouldSignalOnException(launch_flags) || (GetRuntimeFirmwareVersion() >= FirmwareVersion_200 && !ShouldStartSuspended(launch_flags))) {
|
||||
ldr_flags |= ldr::CreateProcessFlag_EnableDebug;
|
||||
}
|
||||
if (ShouldDisableAslr(launch_flags)) {
|
||||
ldr_flags |= ldr::CreateProcessFlag_DisableAslr;
|
||||
}
|
||||
|
||||
return ldr_flags;
|
||||
}
|
||||
|
||||
Result StartProcess(std::shared_ptr<ProcessInfo> process_info, const ldr::ProgramInfo *program_info) {
|
||||
R_TRY(svcStartProcess(process_info->GetHandle(), program_info->main_thread_priority, program_info->default_cpu_id, program_info->main_thread_stack_size));
|
||||
process_info->SetState(ProcessState_Running);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result LaunchProcess(const LaunchProcessArgs *args) {
|
||||
/* Get Program Info. */
|
||||
ldr::ProgramInfo program_info;
|
||||
R_TRY(ldr::pm::GetProgramInfo(&program_info, args->location));
|
||||
const bool is_application = (program_info.flags & ldr::ProgramInfoFlag_ApplicationTypeMask) == ldr::ProgramInfoFlag_Application;
|
||||
const bool allow_debug = (program_info.flags & ldr::ProgramInfoFlag_AllowDebug) || GetRuntimeFirmwareVersion() < FirmwareVersion_200;
|
||||
|
||||
/* Ensure we only try to run one application. */
|
||||
if (is_application) {
|
||||
return ResultPmApplicationRunning;
|
||||
}
|
||||
|
||||
/* Fix the title location to use the right title id. */
|
||||
const ncm::TitleLocation location = ncm::MakeTitleLocation(program_info.title_id, static_cast<ncm::StorageId>(args->location.storage_id));
|
||||
|
||||
/* Pin the program with loader. */
|
||||
ldr::PinId pin_id;
|
||||
R_TRY(ldr::pm::PinTitle(&pin_id, location));
|
||||
|
||||
/* Ensure resources are available. */
|
||||
resource::WaitResourceAvailable(&program_info);
|
||||
|
||||
/* Actually create the process. */
|
||||
Handle process_handle;
|
||||
R_TRY_CLEANUP(ldr::pm::CreateProcess(&process_handle, pin_id, GetLoaderCreateProcessFlags(args->flags), resource::GetResourceLimitHandle(&program_info)), {
|
||||
ldr::pm::UnpinTitle(pin_id);
|
||||
});
|
||||
|
||||
/* Get the process id. */
|
||||
u64 process_id;
|
||||
R_ASSERT(svcGetProcessId(&process_id, process_handle));
|
||||
|
||||
/* Make new process info. */
|
||||
auto process_info = std::make_shared<ProcessInfo>(process_handle, process_id, pin_id, location);
|
||||
|
||||
const u8 *acid_sac = program_info.ac_buffer;
|
||||
const u8 *aci_sac = acid_sac + program_info.acid_sac_size;
|
||||
const u8 *acid_fac = aci_sac + program_info.aci_sac_size;
|
||||
const u8 *aci_fah = acid_fac + program_info.acid_fac_size;
|
||||
|
||||
/* Register with FS and SM. */
|
||||
R_TRY(fsprRegisterProgram(process_id, static_cast<u64>(location.title_id), static_cast<FsStorageId>(location.storage_id), aci_fah, program_info.aci_fah_size, acid_fac, program_info.acid_fac_size));
|
||||
R_TRY(sm::manager::RegisterProcess(process_id, acid_sac, program_info.acid_sac_size, aci_sac, program_info.aci_sac_size));
|
||||
|
||||
/* Set flags. */
|
||||
if (is_application) {
|
||||
process_info->SetApplication();
|
||||
}
|
||||
if (ShouldSignalOnStart(args->flags) && allow_debug) {
|
||||
process_info->SetSignalOnStart();
|
||||
}
|
||||
if (ShouldSignalOnExit(args->flags)) {
|
||||
process_info->SetSignalOnExit();
|
||||
}
|
||||
if (ShouldSignalOnDebugEvent(args->flags) && allow_debug) {
|
||||
process_info->SetSignalOnDebugEvent();
|
||||
}
|
||||
|
||||
/* Process hooks/signaling. */
|
||||
if (location.title_id == g_title_id_hook) {
|
||||
g_hook_to_create_process_event->Signal();
|
||||
g_title_id_hook = ncm::InvalidTitleId;
|
||||
} else if (is_application && g_application_hook) {
|
||||
g_hook_to_create_application_process_event->Signal();
|
||||
g_application_hook = false;
|
||||
} else if (!ShouldStartSuspended(args->flags)) {
|
||||
R_TRY(StartProcess(process_info, &program_info));
|
||||
}
|
||||
|
||||
/* Add process to list. */
|
||||
{
|
||||
ProcessListAccessor list(g_process_list);
|
||||
list->Add(process_info);
|
||||
g_process_waitable_manager->AddWaitable(new ProcessInfoWaiter(process_info));
|
||||
}
|
||||
|
||||
*args->out_process_id = process_id;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result LaunchProcessEventCallback(u64 timeout) {
|
||||
g_process_launch_start_event->Clear();
|
||||
g_process_launch_result = LaunchProcess(&g_process_launch_args);
|
||||
g_process_launch_finish_signal.Signal();
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
void CleanupProcess(ProcessListAccessor &list, std::shared_ptr<ProcessInfo> process_info) {
|
||||
/* Remove the process from the list. */
|
||||
list->Remove(process_info->GetProcessId());
|
||||
|
||||
/* Close process resources. */
|
||||
process_info->Cleanup();
|
||||
|
||||
/* Handle the case where we need to keep the process alive some time longer. */
|
||||
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_500 && process_info->ShouldSignalOnExit()) {
|
||||
/* Add the process to the list of dead processes. */
|
||||
{
|
||||
ProcessListAccessor dead_list(g_dead_process_list);
|
||||
dead_list->Add(process_info);
|
||||
}
|
||||
/* Signal. */
|
||||
g_process_event->Signal();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Initialization. */
|
||||
Result InitializeProcessManager() {
|
||||
/* Create events. */
|
||||
g_process_event = CreateWriteOnlySystemEvent();
|
||||
g_hook_to_create_process_event = CreateWriteOnlySystemEvent();
|
||||
g_hook_to_create_application_process_event = CreateWriteOnlySystemEvent();
|
||||
g_boot_finished_event = CreateWriteOnlySystemEvent();
|
||||
|
||||
/* Process launch is signaled via non-system event. */
|
||||
g_process_launch_start_event = CreateHosEvent(&LaunchProcessEventCallback);
|
||||
|
||||
/* Initialize resource limits. */
|
||||
R_TRY(resource::InitializeResourceManager());
|
||||
|
||||
/* Start thread. */
|
||||
R_ASSERT(g_process_track_thread.Initialize(&ProcessTrackingMain, nullptr, 0x4000, 0x15));
|
||||
R_ASSERT(g_process_track_thread.Start());
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
/* Process Info API. */
|
||||
Result OnProcessSignaled(std::shared_ptr<ProcessInfo> process_info) {
|
||||
/* Resest the process's signal. */
|
||||
svcResetSignal(process_info->GetHandle());
|
||||
|
||||
/* Update the process's state. */
|
||||
const ProcessState old_state = process_info->GetState();
|
||||
{
|
||||
u64 tmp = 0;
|
||||
R_ASSERT(svcGetProcessInfo(&tmp, process_info->GetHandle(), ProcessInfoType_ProcessState));
|
||||
process_info->SetState(static_cast<ProcessState>(tmp));
|
||||
}
|
||||
const ProcessState new_state = process_info->GetState();
|
||||
|
||||
/* If we're transitioning away from crashed, clear waiting attached. */
|
||||
if (old_state == ProcessState_Crashed && new_state != ProcessState_Crashed) {
|
||||
process_info->ClearExceptionWaitingAttach();
|
||||
}
|
||||
|
||||
switch (new_state) {
|
||||
case ProcessState_Created:
|
||||
case ProcessState_CreatedAttached:
|
||||
case ProcessState_Exiting:
|
||||
break;
|
||||
case ProcessState_Running:
|
||||
if (process_info->ShouldSignalOnDebugEvent()) {
|
||||
process_info->ClearSuspended();
|
||||
process_info->SetSuspendedStateChanged();
|
||||
g_process_event->Signal();
|
||||
} else if (GetRuntimeFirmwareVersion() >= FirmwareVersion_200 && process_info->ShouldSignalOnStart()) {
|
||||
process_info->SetStartedStateChanged();
|
||||
process_info->ClearSignalOnStart();
|
||||
g_process_event->Signal();
|
||||
}
|
||||
break;
|
||||
case ProcessState_Crashed:
|
||||
process_info->SetExceptionOccurred();
|
||||
g_process_event->Signal();
|
||||
break;
|
||||
case ProcessState_RunningAttached:
|
||||
if (process_info->ShouldSignalOnDebugEvent()) {
|
||||
process_info->ClearSuspended();
|
||||
process_info->SetSuspendedStateChanged();
|
||||
g_process_event->Signal();
|
||||
}
|
||||
break;
|
||||
case ProcessState_Exited:
|
||||
if (GetRuntimeFirmwareVersion() < FirmwareVersion_500 && process_info->ShouldSignalOnExit()) {
|
||||
g_process_event->Signal();
|
||||
} else {
|
||||
ProcessListAccessor list(g_process_list);
|
||||
CleanupProcess(list, process_info);
|
||||
}
|
||||
/* Return ConnectionClosed to cause libstratosphere to stop waiting on the process. */
|
||||
return ResultKernelConnectionClosed;
|
||||
case ProcessState_DebugSuspended:
|
||||
if (process_info->ShouldSignalOnDebugEvent()) {
|
||||
process_info->SetSuspended();
|
||||
process_info->SetSuspendedStateChanged();
|
||||
g_process_event->Signal();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
/* Process Management. */
|
||||
Result LaunchTitle(u64 *out_process_id, const ncm::TitleLocation &loc, u32 flags) {
|
||||
/* Ensure we only try to launch one title at a time. */
|
||||
static HosMutex s_lock;
|
||||
std::scoped_lock<HosMutex> lk(s_lock);
|
||||
|
||||
/* Set global arguments, signal, wait. */
|
||||
g_process_launch_args = {
|
||||
.out_process_id = out_process_id,
|
||||
.location = loc,
|
||||
.flags = flags,
|
||||
};
|
||||
g_process_launch_finish_signal.Reset();
|
||||
g_process_launch_start_event->Signal();
|
||||
g_process_launch_finish_signal.Wait();
|
||||
|
||||
return g_process_launch_result;
|
||||
}
|
||||
|
||||
Result StartProcess(u64 process_id) {
|
||||
ProcessListAccessor list(g_process_list);
|
||||
|
||||
auto process_info = list->Find(process_id);
|
||||
if (process_info == nullptr) {
|
||||
return ResultPmProcessNotFound;
|
||||
}
|
||||
|
||||
if (process_info->HasStarted()) {
|
||||
return ResultPmAlreadyStarted;
|
||||
}
|
||||
|
||||
ldr::ProgramInfo program_info;
|
||||
R_TRY(ldr::pm::GetProgramInfo(&program_info, process_info->GetTitleLocation()));
|
||||
return StartProcess(process_info, &program_info);
|
||||
}
|
||||
|
||||
Result TerminateProcess(u64 process_id) {
|
||||
ProcessListAccessor list(g_process_list);
|
||||
|
||||
auto process_info = list->Find(process_id);
|
||||
if (process_info == nullptr) {
|
||||
return ResultPmProcessNotFound;
|
||||
}
|
||||
|
||||
return svcTerminateProcess(process_info->GetHandle());
|
||||
}
|
||||
|
||||
Result TerminateTitle(ncm::TitleId title_id) {
|
||||
ProcessListAccessor list(g_process_list);
|
||||
|
||||
auto process_info = list->Find(title_id);
|
||||
if (process_info == nullptr) {
|
||||
return ResultPmProcessNotFound;
|
||||
}
|
||||
|
||||
return svcTerminateProcess(process_info->GetHandle());
|
||||
}
|
||||
|
||||
Result GetProcessEventHandle(Handle *out) {
|
||||
return g_process_event->GetHandle();
|
||||
}
|
||||
|
||||
Result GetProcessEventInfo(ProcessEventInfo *out) {
|
||||
/* Check for event from current process. */
|
||||
{
|
||||
ProcessListAccessor list(g_process_list);
|
||||
|
||||
for (size_t i = 0; i < list->GetSize(); i++) {
|
||||
auto process_info = list[i];
|
||||
if (process_info->HasStarted() && process_info->HasStartedStateChanged()) {
|
||||
out->event = GetProcessEventValue(ProcessEvent::Started);
|
||||
out->process_id = process_info->GetProcessId();
|
||||
return ResultSuccess;
|
||||
}
|
||||
if (process_info->HasSuspendedStateChanged()) {
|
||||
if (process_info->IsSuspended()) {
|
||||
out->event = GetProcessEventValue(ProcessEvent::DebugSuspended);
|
||||
} else {
|
||||
out->event = GetProcessEventValue(ProcessEvent::DebugRunning);
|
||||
}
|
||||
out->process_id = process_info->GetProcessId();
|
||||
return ResultSuccess;
|
||||
}
|
||||
if (process_info->HasExceptionOccurred()) {
|
||||
process_info->ClearExceptionOccurred();
|
||||
out->event = GetProcessEventValue(ProcessEvent::Exception);
|
||||
out->process_id = process_info->GetProcessId();
|
||||
return ResultSuccess;
|
||||
}
|
||||
if (GetRuntimeFirmwareVersion() < FirmwareVersion_500 && process_info->ShouldSignalOnExit() && process_info->HasExited()) {
|
||||
out->event = GetProcessEventValue(ProcessEvent::Exited);
|
||||
out->process_id = process_info->GetProcessId();
|
||||
return ResultSuccess;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Check for event from exited process. */
|
||||
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_500) {
|
||||
ProcessListAccessor dead_list(g_dead_process_list);
|
||||
|
||||
if (dead_list->GetSize() > 0) {
|
||||
auto process_info = dead_list->Pop();
|
||||
out->event = GetProcessEventValue(ProcessEvent::Exited);
|
||||
out->process_id = process_info->GetProcessId();
|
||||
return ResultSuccess;
|
||||
}
|
||||
}
|
||||
|
||||
out->process_id = 0;
|
||||
out->event = GetProcessEventValue(ProcessEvent::None);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result CleanupProcess(u64 process_id) {
|
||||
ProcessListAccessor list(g_process_list);
|
||||
|
||||
auto process_info = list->Find(process_id);
|
||||
if (process_info == nullptr) {
|
||||
return ResultPmProcessNotFound;
|
||||
}
|
||||
|
||||
if (!process_info->HasExited()) {
|
||||
return ResultPmNotExited;
|
||||
}
|
||||
|
||||
CleanupProcess(list, process_info);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ClearExceptionOccurred(u64 process_id) {
|
||||
ProcessListAccessor list(g_process_list);
|
||||
|
||||
auto process_info = list->Find(process_id);
|
||||
if (process_info == nullptr) {
|
||||
return ResultPmProcessNotFound;
|
||||
}
|
||||
|
||||
process_info->ClearExceptionOccurred();
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
/* Information Getters. */
|
||||
Result GetModuleIdList(u32 *out_count, u8 *out_buf, size_t max_out_count, u64 unused) {
|
||||
/* This function was always stubbed... */
|
||||
*out_count = 0;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result GetExceptionProcessIdList(u32 *out_count, u64 *out_process_ids, size_t max_out_count) {
|
||||
ProcessListAccessor list(g_process_list);
|
||||
|
||||
size_t count = 0;
|
||||
for (size_t i = 0; i < list->GetSize() && count < max_out_count; i++) {
|
||||
auto process_info = list[i];
|
||||
if (process_info->HasExceptionWaitingAttach()) {
|
||||
out_process_ids[count++] = process_info->GetProcessId();
|
||||
}
|
||||
}
|
||||
|
||||
*out_count = static_cast<u32>(count);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result GetProcessId(u64 *out, ncm::TitleId title_id) {
|
||||
ProcessListAccessor list(g_process_list);
|
||||
|
||||
auto process_info = list->Find(title_id);
|
||||
if (process_info == nullptr) {
|
||||
return ResultPmProcessNotFound;
|
||||
}
|
||||
|
||||
*out = process_info->GetProcessId();
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result GetTitleId(ncm::TitleId *out, u64 process_id) {
|
||||
ProcessListAccessor list(g_process_list);
|
||||
|
||||
auto process_info = list->Find(process_id);
|
||||
if (process_info == nullptr) {
|
||||
return ResultPmProcessNotFound;
|
||||
}
|
||||
|
||||
*out = process_info->GetTitleLocation().title_id;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result GetApplicationProcessId(u64 *out_process_id) {
|
||||
ProcessListAccessor list(g_process_list);
|
||||
|
||||
for (size_t i = 0; i < list->GetSize(); i++) {
|
||||
auto process_info = list[i];
|
||||
if (process_info->IsApplication()) {
|
||||
*out_process_id = process_info->GetProcessId();
|
||||
return ResultSuccess;
|
||||
}
|
||||
}
|
||||
|
||||
return ResultPmProcessNotFound;
|
||||
}
|
||||
|
||||
Result AtmosphereGetProcessInfo(Handle *out_process_handle, ncm::TitleLocation *out_loc, u64 process_id) {
|
||||
ProcessListAccessor list(g_process_list);
|
||||
|
||||
auto process_info = list->Find(process_id);
|
||||
if (process_info == nullptr) {
|
||||
return ResultPmProcessNotFound;
|
||||
}
|
||||
|
||||
*out_process_handle = process_info->GetHandle();
|
||||
*out_loc = process_info->GetTitleLocation();
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
/* Hook API. */
|
||||
Result HookToCreateProcess(Handle *out_hook, ncm::TitleId title_id) {
|
||||
*out_hook = INVALID_HANDLE;
|
||||
|
||||
ncm::TitleId old_value = ncm::InvalidTitleId;
|
||||
if (!g_title_id_hook.compare_exchange_strong(old_value, title_id)) {
|
||||
return ResultPmDebugHookInUse;
|
||||
}
|
||||
|
||||
*out_hook = g_hook_to_create_process_event->GetHandle();
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result HookToCreateApplicationProcess(Handle *out_hook) {
|
||||
*out_hook = INVALID_HANDLE;
|
||||
|
||||
bool old_value = false;
|
||||
if (!g_application_hook.compare_exchange_strong(old_value, true)) {
|
||||
return ResultPmDebugHookInUse;
|
||||
}
|
||||
|
||||
*out_hook = g_hook_to_create_application_process_event->GetHandle();
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ClearHook(u32 which) {
|
||||
if (which & HookType_TitleId) {
|
||||
g_title_id_hook = ncm::InvalidTitleId;
|
||||
}
|
||||
if (which & HookType_Application) {
|
||||
g_application_hook = false;
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
/* Boot API. */
|
||||
Result NotifyBootFinished() {
|
||||
static bool g_has_boot_finished = false;
|
||||
if (!g_has_boot_finished) {
|
||||
boot2::LaunchBootPrograms();
|
||||
g_has_boot_finished = true;
|
||||
g_boot_finished_event->Signal();
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result GetBootFinishedEventHandle(Handle *out) {
|
||||
/* In 8.0.0, Nintendo added this command, which signals that the boot sysmodule has finished. */
|
||||
/* Nintendo only signals it in safe mode FIRM, and this function aborts on normal FIRM. */
|
||||
/* We will signal it always, but only allow this function to succeed on safe mode. */
|
||||
if (!spl::IsRecoveryBoot()) {
|
||||
std::abort();
|
||||
}
|
||||
*out = g_boot_finished_event->GetHandle();
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
/* Resource Limit API. */
|
||||
Result BoostSystemMemoryResourceLimit(u64 boost_size) {
|
||||
return resource::BoostSystemMemoryResourceLimit(boost_size);
|
||||
}
|
||||
|
||||
Result BoostSystemThreadResourceLimit() {
|
||||
return resource::BoostSystemThreadResourceLimit();
|
||||
}
|
||||
|
||||
Result AtmosphereGetCurrentLimitInfo(u64 *out_cur_val, u64 *out_lim_val, u32 group, u32 resource) {
|
||||
return resource::GetResourceLimitValues(out_cur_val, out_lim_val, static_cast<ResourceLimitGroup>(group), static_cast<LimitableResource>(resource));
|
||||
}
|
||||
|
||||
}
|
||||
60
stratosphere/pm/source/impl/pm_process_manager.hpp
Normal file
60
stratosphere/pm/source/impl/pm_process_manager.hpp
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include <stratosphere/ldr.hpp>
|
||||
#include <stratosphere/pm.hpp>
|
||||
|
||||
namespace sts::pm::impl {
|
||||
|
||||
/* Initialization. */
|
||||
Result InitializeProcessManager();
|
||||
|
||||
/* Process Management. */
|
||||
Result LaunchTitle(u64 *out_process_id, const ncm::TitleLocation &loc, u32 flags);
|
||||
Result StartProcess(u64 process_id);
|
||||
Result TerminateProcess(u64 process_id);
|
||||
Result TerminateTitle(ncm::TitleId title_id);
|
||||
Result GetProcessEventHandle(Handle *out);
|
||||
Result GetProcessEventInfo(ProcessEventInfo *out);
|
||||
Result CleanupProcess(u64 process_id);
|
||||
Result ClearExceptionOccurred(u64 process_id);
|
||||
|
||||
/* Information Getters. */
|
||||
Result GetModuleIdList(u32 *out_count, u8 *out_buf, size_t max_out_count, u64 unused);
|
||||
Result GetExceptionProcessIdList(u32 *out_count, u64 *out_process_ids, size_t max_out_count);
|
||||
Result GetProcessId(u64 *out, ncm::TitleId title_id);
|
||||
Result GetTitleId(ncm::TitleId *out, u64 process_id);
|
||||
Result GetApplicationProcessId(u64 *out_process_id);
|
||||
Result AtmosphereGetProcessInfo(Handle *out_process_handle, ncm::TitleLocation *out_loc, u64 process_id);
|
||||
|
||||
/* Hook API. */
|
||||
Result HookToCreateProcess(Handle *out_hook, ncm::TitleId title_id);
|
||||
Result HookToCreateApplicationProcess(Handle *out_hook);
|
||||
Result ClearHook(u32 which);
|
||||
|
||||
/* Boot API. */
|
||||
Result NotifyBootFinished();
|
||||
Result GetBootFinishedEventHandle(Handle *out);
|
||||
|
||||
/* Resource Limit API. */
|
||||
Result BoostSystemMemoryResourceLimit(u64 boost_size);
|
||||
Result BoostSystemThreadResourceLimit();
|
||||
Result AtmosphereGetCurrentLimitInfo(u64 *out_cur_val, u64 *out_lim_val, u32 group, u32 resource);
|
||||
|
||||
}
|
||||
334
stratosphere/pm/source/impl/pm_resource_manager.cpp
Normal file
334
stratosphere/pm/source/impl/pm_resource_manager.cpp
Normal file
@@ -0,0 +1,334 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stratosphere/spl.hpp>
|
||||
|
||||
#include "pm_resource_manager.hpp"
|
||||
|
||||
namespace sts::pm::resource {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr LimitableResource LimitableResources[] = {
|
||||
LimitableResource_Memory,
|
||||
LimitableResource_Threads,
|
||||
LimitableResource_Events,
|
||||
LimitableResource_TransferMemories,
|
||||
LimitableResource_Sessions,
|
||||
};
|
||||
constexpr size_t LimitableResource_Count = sizeof(LimitableResources) / sizeof(LimitableResources[0]);
|
||||
|
||||
constexpr size_t Megabyte = 0x100000;
|
||||
|
||||
/* Definitions for limit differences over time. */
|
||||
constexpr size_t ExtraSystemThreadCount400 = 100;
|
||||
constexpr size_t ExtraSystemMemorySize400 = 10 * Megabyte;
|
||||
constexpr size_t ExtraSystemMemorySize500 = 12 * Megabyte;
|
||||
constexpr size_t ExtraSystemEventCount600 = 100;
|
||||
constexpr size_t ExtraSystemSessionCount600 = 100;
|
||||
constexpr size_t ReservedMemorySize600 = 5 * Megabyte;
|
||||
|
||||
/* Atmosphere always allocates 24 extra megabytes for system usage. */
|
||||
constexpr size_t ExtraSystemMemorySizeAtmosphere = 24 * Megabyte;
|
||||
|
||||
/* Globals. */
|
||||
HosMutex g_resource_limit_lock;
|
||||
Handle g_resource_limit_handles[ResourceLimitGroup_Count];
|
||||
spl::MemoryArrangement g_memory_arrangement = spl::MemoryArrangement_Standard;
|
||||
u64 g_system_memory_boost_size = 0;
|
||||
|
||||
u64 g_resource_limits[ResourceLimitGroup_Count][LimitableResource_Count] = {
|
||||
[ResourceLimitGroup_System] = {
|
||||
[LimitableResource_Memory] = 0, /* Initialized by more complicated logic later. */
|
||||
[LimitableResource_Threads] = 508,
|
||||
[LimitableResource_Events] = 600,
|
||||
[LimitableResource_TransferMemories] = 128,
|
||||
[LimitableResource_Sessions] = 794,
|
||||
},
|
||||
[ResourceLimitGroup_Application] = {
|
||||
[LimitableResource_Memory] = 0, /* Initialized by more complicated logic later. */
|
||||
[LimitableResource_Threads] = 96,
|
||||
[LimitableResource_Events] = 0,
|
||||
[LimitableResource_TransferMemories] = 32,
|
||||
[LimitableResource_Sessions] = 1,
|
||||
},
|
||||
[ResourceLimitGroup_Applet] = {
|
||||
[LimitableResource_Memory] = 0, /* Initialized by more complicated logic later. */
|
||||
[LimitableResource_Threads] = 96,
|
||||
[LimitableResource_Events] = 0,
|
||||
[LimitableResource_TransferMemories] = 32,
|
||||
[LimitableResource_Sessions] = 5,
|
||||
},
|
||||
};
|
||||
|
||||
u64 g_memory_resource_limits[spl::MemoryArrangement_Count][ResourceLimitGroup_Count] = {
|
||||
[spl::MemoryArrangement_Standard] = {
|
||||
[ResourceLimitGroup_System] = 269 * Megabyte,
|
||||
[ResourceLimitGroup_Application] = 3285 * Megabyte,
|
||||
[ResourceLimitGroup_Applet] = 535 * Megabyte,
|
||||
},
|
||||
[spl::MemoryArrangement_StandardForAppletDev] = {
|
||||
[ResourceLimitGroup_System] = 481 * Megabyte,
|
||||
[ResourceLimitGroup_Application] = 2048 * Megabyte,
|
||||
[ResourceLimitGroup_Applet] = 1560 * Megabyte,
|
||||
},
|
||||
[spl::MemoryArrangement_StandardForSystemDev] = {
|
||||
[ResourceLimitGroup_System] = 328 * Megabyte,
|
||||
[ResourceLimitGroup_Application] = 3285 * Megabyte,
|
||||
[ResourceLimitGroup_Applet] = 476 * Megabyte,
|
||||
},
|
||||
[spl::MemoryArrangement_Expanded] = {
|
||||
[ResourceLimitGroup_System] = 653 * Megabyte,
|
||||
[ResourceLimitGroup_Application] = 4916 * Megabyte,
|
||||
[ResourceLimitGroup_Applet] = 568 * Megabyte,
|
||||
},
|
||||
[spl::MemoryArrangement_ExpandedForAppletDev] = {
|
||||
[ResourceLimitGroup_System] = 653 * Megabyte,
|
||||
[ResourceLimitGroup_Application] = 3285 * Megabyte,
|
||||
[ResourceLimitGroup_Applet] = 2199 * Megabyte,
|
||||
},
|
||||
};
|
||||
|
||||
/* Helpers. */
|
||||
Result SetMemoryResourceLimitLimitValue(ResourceLimitGroup group, u64 new_memory_limit) {
|
||||
const u64 old_memory_limit = g_resource_limits[group][LimitableResource_Memory];
|
||||
g_resource_limits[group][LimitableResource_Memory] = new_memory_limit;
|
||||
R_TRY_CLEANUP(svcSetResourceLimitLimitValue(GetResourceLimitHandle(group), LimitableResource_Memory, g_resource_limits[group][LimitableResource_Memory]), {
|
||||
/* If we fail, restore the old memory limit. */
|
||||
g_resource_limits[group][LimitableResource_Memory] = old_memory_limit;
|
||||
});
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result SetResourceLimitLimitValues(ResourceLimitGroup group, u64 new_memory_limit) {
|
||||
/* First, set memory limit. */
|
||||
R_TRY(SetMemoryResourceLimitLimitValue(group, new_memory_limit));
|
||||
|
||||
/* Set other limit values. */
|
||||
for (size_t i = 0; i < LimitableResource_Count; i++) {
|
||||
const auto resource = LimitableResources[i];
|
||||
if (resource == LimitableResource_Memory) {
|
||||
continue;
|
||||
}
|
||||
R_TRY(svcSetResourceLimitLimitValue(GetResourceLimitHandle(group), LimitableResources[resource], g_resource_limits[group][resource]));
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
inline ResourceLimitGroup GetResourceLimitGroup(const ldr::ProgramInfo *info) {
|
||||
switch (info->flags & ldr::ProgramInfoFlag_ApplicationTypeMask) {
|
||||
case ldr::ProgramInfoFlag_Application:
|
||||
return ResourceLimitGroup_Application;
|
||||
case ldr::ProgramInfoFlag_Applet:
|
||||
return ResourceLimitGroup_Applet;
|
||||
default:
|
||||
return ResourceLimitGroup_System;
|
||||
}
|
||||
}
|
||||
|
||||
void WaitResourceAvailable(ResourceLimitGroup group) {
|
||||
const Handle reslimit_hnd = GetResourceLimitHandle(group);
|
||||
for (size_t i = 0; i < LimitableResource_Count; i++) {
|
||||
const auto resource = LimitableResources[i];
|
||||
|
||||
u64 value = 0;
|
||||
while (true) {
|
||||
R_ASSERT(svcGetResourceLimitCurrentValue(&value, reslimit_hnd, resource));
|
||||
if (value == 0) {
|
||||
break;
|
||||
}
|
||||
svcSleepThread(1'000'000ul);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WaitApplicationMemoryAvailable() {
|
||||
u64 value = 0;
|
||||
while (true) {
|
||||
R_ASSERT(svcGetSystemInfo(&value, SystemInfoType_UsedPhysicalMemorySize, INVALID_HANDLE, PhysicalMemoryInfo_Application));
|
||||
if (value == 0) {
|
||||
break;
|
||||
}
|
||||
svcSleepThread(1'000'000ul);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Resource API. */
|
||||
Result InitializeResourceManager() {
|
||||
/* Create resource limit handles. */
|
||||
for (size_t i = 0; i < ResourceLimitGroup_Count; i++) {
|
||||
if (i == ResourceLimitGroup_System) {
|
||||
u64 value = 0;
|
||||
R_ASSERT(svcGetInfo(&value, InfoType_ResourceLimit, INVALID_HANDLE, 0));
|
||||
g_resource_limit_handles[i] = static_cast<Handle>(value);
|
||||
} else {
|
||||
R_ASSERT(svcCreateResourceLimit(&g_resource_limit_handles[i]));
|
||||
}
|
||||
}
|
||||
|
||||
/* Adjust resource limits based on firmware version. */
|
||||
const auto firmware_version = GetRuntimeFirmwareVersion();
|
||||
if (firmware_version >= FirmwareVersion_400) {
|
||||
/* 4.0.0 increased the system thread limit. */
|
||||
g_resource_limits[ResourceLimitGroup_System][LimitableResource_Threads] += ExtraSystemThreadCount400;
|
||||
/* 4.0.0 also took memory away from applet and gave it to system, for the Standard and StandardForSystemDev profiles. */
|
||||
g_memory_resource_limits[spl::MemoryArrangement_Standard][ResourceLimitGroup_System] += ExtraSystemMemorySize400;
|
||||
g_memory_resource_limits[spl::MemoryArrangement_Standard][ResourceLimitGroup_Applet] -= ExtraSystemMemorySize400;
|
||||
g_memory_resource_limits[spl::MemoryArrangement_StandardForSystemDev][ResourceLimitGroup_System] += ExtraSystemMemorySize400;
|
||||
g_memory_resource_limits[spl::MemoryArrangement_StandardForSystemDev][ResourceLimitGroup_Applet] -= ExtraSystemMemorySize400;
|
||||
}
|
||||
if (firmware_version >= FirmwareVersion_500) {
|
||||
/* 5.0.0 took more memory away from applet and gave it to system, for the Standard and StandardForSystemDev profiles. */
|
||||
g_memory_resource_limits[spl::MemoryArrangement_Standard][ResourceLimitGroup_System] += ExtraSystemMemorySize500;
|
||||
g_memory_resource_limits[spl::MemoryArrangement_Standard][ResourceLimitGroup_Applet] -= ExtraSystemMemorySize500;
|
||||
g_memory_resource_limits[spl::MemoryArrangement_StandardForSystemDev][ResourceLimitGroup_System] += ExtraSystemMemorySize500;
|
||||
g_memory_resource_limits[spl::MemoryArrangement_StandardForSystemDev][ResourceLimitGroup_Applet] -= ExtraSystemMemorySize500;
|
||||
}
|
||||
if (firmware_version >= FirmwareVersion_600) {
|
||||
/* 6.0.0 increased the system event and session limits. */
|
||||
g_resource_limits[ResourceLimitGroup_System][LimitableResource_Events] += ExtraSystemEventCount600;
|
||||
g_resource_limits[ResourceLimitGroup_System][LimitableResource_Sessions] += ExtraSystemSessionCount600;
|
||||
}
|
||||
|
||||
/* 7.0.0+: Nintendo restricts the number of system threads here, from 0x260 -> 0x60. */
|
||||
/* We will not do this. */
|
||||
|
||||
/* Choose and initialize memory arrangement. */
|
||||
if (firmware_version >= FirmwareVersion_600) {
|
||||
/* 6.0.0 retrieves memory limit information from the kernel, rather than using a hardcoded profile. */
|
||||
g_memory_arrangement = spl::MemoryArrangement_Dynamic;
|
||||
|
||||
/* Get total memory available. */
|
||||
u64 total_memory = 0;
|
||||
R_ASSERT(svcGetResourceLimitLimitValue(&total_memory, GetResourceLimitHandle(ResourceLimitGroup_System), LimitableResource_Memory));
|
||||
|
||||
/* Get and save application + applet memory. */
|
||||
R_ASSERT(svcGetSystemInfo(&g_memory_resource_limits[spl::MemoryArrangement_Dynamic][ResourceLimitGroup_Application], SystemInfoType_TotalPhysicalMemorySize, INVALID_HANDLE, PhysicalMemoryInfo_Application));
|
||||
R_ASSERT(svcGetSystemInfo(&g_memory_resource_limits[spl::MemoryArrangement_Dynamic][ResourceLimitGroup_Applet], SystemInfoType_TotalPhysicalMemorySize, INVALID_HANDLE, PhysicalMemoryInfo_Applet));
|
||||
|
||||
const u64 application_size = g_memory_resource_limits[spl::MemoryArrangement_Dynamic][ResourceLimitGroup_Application];
|
||||
const u64 applet_size = g_memory_resource_limits[spl::MemoryArrangement_Dynamic][ResourceLimitGroup_Applet];
|
||||
const u64 reserved_non_system_size = (application_size + applet_size + ReservedMemorySize600);
|
||||
|
||||
/* Ensure there's enough memory for the system region. */
|
||||
if (reserved_non_system_size >= total_memory) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
g_memory_resource_limits[spl::MemoryArrangement_Dynamic][ResourceLimitGroup_System] = total_memory - reserved_non_system_size;
|
||||
} else {
|
||||
g_memory_arrangement = spl::GetMemoryArrangement();
|
||||
}
|
||||
|
||||
/* Adjust memory limits for atmosphere. */
|
||||
/* We take memory away from applet normally, but away from application on < 3.0.0 to avoid a rare hang on boot. */
|
||||
for (size_t i = 0; i < spl::MemoryArrangement_Count; i++) {
|
||||
g_memory_resource_limits[i][ResourceLimitGroup_System] += ExtraSystemMemorySizeAtmosphere;
|
||||
if (firmware_version >= FirmwareVersion_300) {
|
||||
g_memory_resource_limits[i][ResourceLimitGroup_Applet] -= ExtraSystemMemorySizeAtmosphere;
|
||||
} else {
|
||||
g_memory_resource_limits[i][ResourceLimitGroup_Application] -= ExtraSystemMemorySizeAtmosphere;
|
||||
}
|
||||
}
|
||||
|
||||
/* Actually set resource limits. */
|
||||
{
|
||||
std::scoped_lock<HosMutex> lk(g_resource_limit_lock);
|
||||
|
||||
for (size_t group = 0; group < ResourceLimitGroup_Count; group++) {
|
||||
R_ASSERT(SetResourceLimitLimitValues(static_cast<ResourceLimitGroup>(group), g_memory_resource_limits[g_memory_arrangement][group]));
|
||||
}
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result BoostSystemMemoryResourceLimit(u64 boost_size) {
|
||||
/* Don't allow all application memory to be taken away. */
|
||||
if (boost_size > g_memory_resource_limits[g_memory_arrangement][ResourceLimitGroup_Application]) {
|
||||
return ResultPmInvalidSize;
|
||||
}
|
||||
|
||||
const u64 new_app_size = g_memory_resource_limits[g_memory_arrangement][ResourceLimitGroup_Application] - boost_size;
|
||||
{
|
||||
std::scoped_lock<HosMutex> lk(g_resource_limit_lock);
|
||||
|
||||
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_500) {
|
||||
/* Starting in 5.0.0, PM does not allow for only one of the sets to fail. */
|
||||
if (boost_size < g_system_memory_boost_size) {
|
||||
R_TRY(svcSetUnsafeLimit(boost_size));
|
||||
R_ASSERT(SetMemoryResourceLimitLimitValue(ResourceLimitGroup_Application, new_app_size));
|
||||
} else {
|
||||
R_TRY(SetMemoryResourceLimitLimitValue(ResourceLimitGroup_Application, new_app_size));
|
||||
R_ASSERT(svcSetUnsafeLimit(boost_size));
|
||||
}
|
||||
} else {
|
||||
const u64 new_sys_size = g_memory_resource_limits[g_memory_arrangement][ResourceLimitGroup_System] + boost_size;
|
||||
if (boost_size < g_system_memory_boost_size) {
|
||||
R_TRY(SetMemoryResourceLimitLimitValue(ResourceLimitGroup_System, new_sys_size));
|
||||
R_TRY(SetMemoryResourceLimitLimitValue(ResourceLimitGroup_Application, new_app_size));
|
||||
} else {
|
||||
R_TRY(SetMemoryResourceLimitLimitValue(ResourceLimitGroup_Application, new_app_size));
|
||||
R_TRY(SetMemoryResourceLimitLimitValue(ResourceLimitGroup_System, new_sys_size));
|
||||
}
|
||||
}
|
||||
|
||||
g_system_memory_boost_size = boost_size;
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result BoostSystemThreadResourceLimit() {
|
||||
/* Starting in 7.0.0, Nintendo reduces the number of system threads from 0x260 to 0x60, */
|
||||
/* Until this command is called to double that amount to 0xC0. */
|
||||
/* We will simply not reduce the number of system threads available for no reason. */
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Handle GetResourceLimitHandle(ResourceLimitGroup group) {
|
||||
return g_resource_limit_handles[group];
|
||||
}
|
||||
|
||||
Handle GetResourceLimitHandle(const ldr::ProgramInfo *info) {
|
||||
return GetResourceLimitHandle(GetResourceLimitGroup(info));
|
||||
}
|
||||
|
||||
void WaitResourceAvailable(const ldr::ProgramInfo *info) {
|
||||
if (GetResourceLimitGroup(info) == ResourceLimitGroup_Application) {
|
||||
WaitResourceAvailable(ResourceLimitGroup_Application);
|
||||
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_500) {
|
||||
WaitApplicationMemoryAvailable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result GetResourceLimitValues(u64 *out_cur, u64 *out_lim, ResourceLimitGroup group, LimitableResource resource) {
|
||||
/* Do not allow out of bounds access. */
|
||||
if (group >= ResourceLimitGroup_Count || resource >= LimitableResource_Count) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
const Handle reslimit_hnd = GetResourceLimitHandle(group);
|
||||
R_TRY(svcGetResourceLimitCurrentValue(out_cur, reslimit_hnd, resource));
|
||||
R_TRY(svcGetResourceLimitLimitValue(out_lim, reslimit_hnd, resource));
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
}
|
||||
35
stratosphere/pm/source/impl/pm_resource_manager.hpp
Normal file
35
stratosphere/pm/source/impl/pm_resource_manager.hpp
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include <stratosphere/ldr.hpp>
|
||||
#include <stratosphere/pm.hpp>
|
||||
|
||||
namespace sts::pm::resource {
|
||||
|
||||
/* Resource API. */
|
||||
Result InitializeResourceManager();
|
||||
Result BoostSystemMemoryResourceLimit(u64 boost_size);
|
||||
Result BoostSystemThreadResourceLimit();
|
||||
Handle GetResourceLimitHandle(ResourceLimitGroup group);
|
||||
Handle GetResourceLimitHandle(const ldr::ProgramInfo *info);
|
||||
void WaitResourceAvailable(const ldr::ProgramInfo *info);
|
||||
|
||||
Result GetResourceLimitValues(u64 *out_cur, u64 *out_lim, ResourceLimitGroup group, LimitableResource resource);
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user