diff --git a/stratosphere/pm/pm.json b/stratosphere/pm/pm.json index f999cdc..005504b 100644 --- a/stratosphere/pm/pm.json +++ b/stratosphere/pm/pm.json @@ -1,7 +1,7 @@ { "name": "ProcessManager", "title_id": "0x0100000000000003", - "main_thread_stack_size": "0x00002000", + "main_thread_stack_size": "0x00003000", "main_thread_priority": 49, "default_cpu_id": 3, "process_category": 1, diff --git a/stratosphere/pm/source/impl/pm_process_info.cpp b/stratosphere/pm/source/impl/pm_process_info.cpp index a867cac..90373b5 100644 --- a/stratosphere/pm/source/impl/pm_process_info.cpp +++ b/stratosphere/pm/source/impl/pm_process_info.cpp @@ -18,6 +18,64 @@ namespace ams::pm::impl { + namespace { + + template + class ProcessInfoAllocator { + NON_COPYABLE(ProcessInfoAllocator); + NON_MOVEABLE(ProcessInfoAllocator); + static_assert(MaxProcessInfos >= 0x40, "MaxProcessInfos is too small."); + private: + util::TypedStorage m_process_info_storages[MaxProcessInfos]{}; + bool m_process_info_allocated[MaxProcessInfos]{}; + os::SdkMutex m_lock{}; + private: + constexpr inline size_t GetProcessInfoIndex(ProcessInfo *process_info) const { + return process_info - GetPointer(m_process_info_storages[0]); + } + public: + constexpr ProcessInfoAllocator() = default; + + template + ProcessInfo *AllocateProcessInfo(Args &&... args) { + std::scoped_lock lk(m_lock); + + for (size_t i = 0; i < MaxProcessInfos; i++) { + if (!m_process_info_allocated[i]) { + m_process_info_allocated[i] = true; + + std::memset(m_process_info_storages + i, 0, sizeof(m_process_info_storages[i])); + + return util::ConstructAt(m_process_info_storages[i], std::forward(args)...); + } + } + + return nullptr; + } + + void FreeProcessInfo(ProcessInfo *process_info) { + std::scoped_lock lk(m_lock); + + const size_t index = this->GetProcessInfoIndex(process_info); + AMS_ABORT_UNLESS(index < MaxProcessInfos); + AMS_ABORT_UNLESS(m_process_info_allocated[index]); + + util::DestroyAt(m_process_info_storages[index]); + m_process_info_allocated[index] = false; + } + }; + + /* Process lists. */ + constinit ProcessList g_process_list; + constinit ProcessList g_exit_list; + + /* Process Info Allocation. */ + /* Note: The kernel slabheap is size 0x50 -- we allow slightly larger to account for the dead process list. */ + constexpr size_t MaxProcessCount = 0x60; + constinit ProcessInfoAllocator g_process_info_allocator; + + } + ProcessInfo::ProcessInfo(os::NativeHandle h, os::ProcessId pid, ldr::PinId pin, const ncm::ProgramLocation &l, const cfg::OverrideStatus &s) : m_process_id(pid), m_pin_id(pin), m_loc(l), m_status(s), m_handle(h), m_state(svc::ProcessState_Created), m_flags(0) { os::InitializeMultiWaitHolder(std::addressof(m_multi_wait_holder), m_handle); os::SetMultiWaitHolderUserData(std::addressof(m_multi_wait_holder), reinterpret_cast(this)); @@ -37,10 +95,27 @@ /* Close the process's handle. */ os::CloseNativeHandle(m_handle); m_handle = os::InvalidNativeHandle; - - /* Unlink the process from its multi wait. */ - os::UnlinkMultiWaitHolder(std::addressof(m_multi_wait_holder)); } } + ProcessListAccessor GetProcessList() { + return ProcessListAccessor(g_process_list); + } + + ProcessListAccessor GetExitList() { + return ProcessListAccessor(g_exit_list); + } + + ProcessInfo *AllocateProcessInfo(svc::Handle process_handle, os::ProcessId process_id, ldr::PinId pin_id, const ncm::ProgramLocation &location, const cfg::OverrideStatus &override_status) { + return g_process_info_allocator.AllocateProcessInfo(process_handle, process_id, pin_id, location, override_status); + } + + void CleanupProcessInfo(ProcessListAccessor &list, ProcessInfo *process_info) { + /* Remove the process from the list. */ + list->Remove(process_info); + + /* Delete the process. */ + g_process_info_allocator.FreeProcessInfo(process_info); + } + } diff --git a/stratosphere/pm/source/impl/pm_process_info.hpp b/stratosphere/pm/source/impl/pm_process_info.hpp index f894037..f2dae02 100644 --- a/stratosphere/pm/source/impl/pm_process_info.hpp +++ b/stratosphere/pm/source/impl/pm_process_info.hpp @@ -64,8 +64,8 @@ ~ProcessInfo(); void Cleanup(); - void LinkToMultiWait(os::MultiWaitType &multi_wait) { - os::LinkMultiWaitHolder(std::addressof(multi_wait), std::addressof(m_multi_wait_holder)); + os::MultiWaitHolderType *GetMultiWaitHolder() { + return std::addressof(m_multi_wait_holder); } os::NativeHandle GetHandle() const { @@ -232,4 +232,10 @@ } }; + ProcessListAccessor GetProcessList(); + ProcessListAccessor GetExitList(); + + ProcessInfo *AllocateProcessInfo(svc::Handle process_handle, os::ProcessId process_id, ldr::PinId pin_id, const ncm::ProgramLocation &location, const cfg::OverrideStatus &override_status); + void CleanupProcessInfo(ProcessListAccessor &list, ProcessInfo *process_info); + } diff --git a/stratosphere/pm/source/impl/pm_process_manager.cpp b/stratosphere/pm/source/impl/pm_process_manager.cpp index 036814f..32ef0fe 100644 --- a/stratosphere/pm/source/impl/pm_process_manager.cpp +++ b/stratosphere/pm/source/impl/pm_process_manager.cpp @@ -15,9 +15,9 @@ */ #include #include "pm_process_manager.hpp" -#include "pm_resource_manager.hpp" - +#include "pm_process_tracker.hpp" #include "pm_process_info.hpp" +#include "pm_spec.hpp" namespace ams::pm::impl { @@ -29,13 +29,7 @@ HookType_Application = (1 << 1), }; - struct LaunchProcessArgs { - os::ProcessId *out_process_id; - ncm::ProgramLocation location; - u32 flags; - }; - -#define GET_FLAG_MASK(flag) (hos_version >= hos::Version_5_0_0 ? static_cast(LaunchFlags_##flag) : static_cast(LaunchFlagsDeprecated_##flag)) + #define GET_FLAG_MASK(flag) (hos_version >= hos::Version_5_0_0 ? static_cast(LaunchFlags_##flag) : static_cast(LaunchFlagsDeprecated_##flag)) inline bool ShouldSignalOnExit(u32 launch_flags) { const auto hos_version = hos::GetVersion(); @@ -70,115 +64,26 @@ return launch_flags & GET_FLAG_MASK(DisableAslr); } -#undef GET_FLAG_MASK - - template - class ProcessInfoAllocator { - NON_COPYABLE(ProcessInfoAllocator); - NON_MOVEABLE(ProcessInfoAllocator); - static_assert(MaxProcessInfos >= 0x40, "MaxProcessInfos is too small."); - private: - util::TypedStorage m_process_info_storages[MaxProcessInfos]{}; - bool m_process_info_allocated[MaxProcessInfos]{}; - os::SdkMutex m_lock{}; - private: - constexpr inline size_t GetProcessInfoIndex(ProcessInfo *process_info) const { - return process_info - GetPointer(m_process_info_storages[0]); - } - public: - constexpr ProcessInfoAllocator() = default; - - template - ProcessInfo *AllocateProcessInfo(Args &&... args) { - std::scoped_lock lk(m_lock); - - for (size_t i = 0; i < MaxProcessInfos; i++) { - if (!m_process_info_allocated[i]) { - m_process_info_allocated[i] = true; - - std::memset(m_process_info_storages + i, 0, sizeof(m_process_info_storages[i])); - - return util::ConstructAt(m_process_info_storages[i], std::forward(args)...); - } - } - - return nullptr; - } - - void FreeProcessInfo(ProcessInfo *process_info) { - std::scoped_lock lk(m_lock); - - const size_t index = this->GetProcessInfoIndex(process_info); - AMS_ABORT_UNLESS(index < MaxProcessInfos); - AMS_ABORT_UNLESS(m_process_info_allocated[index]); - - util::DestroyAt(m_process_info_storages[index]); - m_process_info_allocated[index] = false; - } - }; + #undef GET_FLAG_MASK /* Process Tracking globals. */ - void ProcessTrackingMain(void *); - - constinit os::ThreadType g_process_track_thread; - alignas(os::ThreadStackAlignment) constinit u8 g_process_track_thread_stack[16_KB]; - - /* Process lists. */ - constinit ProcessList g_process_list; - constinit ProcessList g_dead_process_list; - - /* Process Info Allocation. */ - /* Note: The kernel slabheap is size 0x50 -- we allow slightly larger to account for the dead process list. */ - constexpr size_t MaxProcessCount = 0x60; - constinit ProcessInfoAllocator g_process_info_allocator; + constinit ProcessTracker g_process_tracker; + alignas(os::ThreadStackAlignment) constinit u8 g_process_track_thread_stack[8_KB]; /* Global events. */ - constinit os::SystemEventType g_process_event; constinit os::SystemEventType g_hook_to_create_process_event; constinit os::SystemEventType g_hook_to_create_application_process_event; constinit os::SystemEventType g_boot_finished_event; - /* Process Launch synchronization globals. */ - constinit os::SdkMutex g_launch_program_lock; - os::Event g_process_launch_start_event(os::EventClearMode_AutoClear); - os::Event g_process_launch_finish_event(os::EventClearMode_AutoClear); - constinit Result g_process_launch_result = ResultSuccess(); - constinit LaunchProcessArgs g_process_launch_args = {}; - /* Hook globals. */ constinit std::atomic g_program_id_hook; constinit std::atomic g_application_hook; - /* Forward declarations. */ - Result LaunchProcess(os::MultiWaitType &multi_wait, const LaunchProcessArgs &args); - void OnProcessSignaled(ProcessListAccessor &list, ProcessInfo *process_info); - /* Helpers. */ - void ProcessTrackingMain(void *) { - /* This is the main loop of the process tracking thread. */ - - /* Setup multi wait/holders. */ - os::MultiWaitType process_multi_wait; - os::MultiWaitHolderType start_event_holder; - os::InitializeMultiWait(std::addressof(process_multi_wait)); - os::InitializeMultiWaitHolder(std::addressof(start_event_holder), g_process_launch_start_event.GetBase()); - os::LinkMultiWaitHolder(std::addressof(process_multi_wait), std::addressof(start_event_holder)); - - while (true) { - auto signaled_holder = os::WaitAny(std::addressof(process_multi_wait)); - if (signaled_holder == std::addressof(start_event_holder)) { - /* Launch start event signaled. */ - /* TryWait will clear signaled, preventing duplicate notifications. */ - if (g_process_launch_start_event.TryWait()) { - g_process_launch_result = LaunchProcess(process_multi_wait, g_process_launch_args); - g_process_launch_finish_event.Signal(); - } - } else { - /* Some process was signaled. */ - ProcessListAccessor list(g_process_list); - OnProcessSignaled(list, reinterpret_cast(os::GetMultiWaitHolderUserData(signaled_holder))); - } - } + void CreateDebuggerEvent() { + /* Create debugger hook events. */ + R_ABORT_UNLESS(os::CreateSystemEvent(std::addressof(g_hook_to_create_process_event), os::EventClearMode_AutoClear, true)); + R_ABORT_UNLESS(os::CreateSystemEvent(std::addressof(g_hook_to_create_application_process_event), os::EventClearMode_AutoClear, true)); } inline u32 GetLoaderCreateProcessFlags(u32 launch_flags) { @@ -195,7 +100,7 @@ } bool HasApplicationProcess() { - ProcessListAccessor list(g_process_list); + auto list = GetProcessList(); for (auto &process : *list) { if (process.IsApplication()) { @@ -212,19 +117,14 @@ R_SUCCEED(); } - void CleanupProcessInfo(ProcessListAccessor &list, ProcessInfo *process_info) { - /* Remove the process from the list. */ - list->Remove(process_info); + Result LaunchProgramImpl(ProcessInfo **out_process_info, os::ProcessId *out_process_id, const ncm::ProgramLocation &loc, u32 flags) { + /* Set the output to nullptr, if we fail. */ + *out_process_info = nullptr; - /* Delete the process. */ - g_process_info_allocator.FreeProcessInfo(process_info); - } - - Result LaunchProcess(os::MultiWaitType &multi_wait, const LaunchProcessArgs &args) { /* Get Program Info. */ ldr::ProgramInfo program_info; cfg::OverrideStatus override_status; - R_TRY(ldr::pm::AtmosphereGetProgramInfo(std::addressof(program_info), std::addressof(override_status), args.location)); + R_TRY(ldr::pm::AtmosphereGetProgramInfo(std::addressof(program_info), std::addressof(override_status), loc)); const bool is_application = (program_info.flags & ldr::ProgramInfoFlag_ApplicationTypeMask) == ldr::ProgramInfoFlag_Application; const bool allow_debug = (program_info.flags & ldr::ProgramInfoFlag_AllowDebug) || hos::GetVersion() < hos::Version_2_0_0; @@ -232,14 +132,14 @@ R_UNLESS(!is_application || !HasApplicationProcess(), pm::ResultApplicationRunning()); /* Fix the program location to use the right program id. */ - const ncm::ProgramLocation location = ncm::ProgramLocation::Make(program_info.program_id, static_cast(args.location.storage_id)); + const ncm::ProgramLocation fixed_location = ncm::ProgramLocation::Make(program_info.program_id, static_cast(loc.storage_id)); /* Pin and create the process. */ os::NativeHandle process_handle; ldr::PinId pin_id; { /* Pin the program with loader. */ - R_TRY(ldr::pm::AtmospherePinProgram(std::addressof(pin_id), location, override_status)); + R_TRY(ldr::pm::AtmospherePinProgram(std::addressof(pin_id), fixed_location, override_status)); /* If we fail after now, unpin. */ ON_RESULT_FAILURE { ldr::pm::UnpinProgram(pin_id); }; @@ -263,29 +163,28 @@ ON_RESULT_FAILURE_2 { if (mitm_boost_size > 0 || is_application) { R_ABORT_UNLESS(BoostSystemMemoryResourceLimitForMitm(0)); } }; /* Ensure resources are available. */ - resource::WaitResourceAvailable(std::addressof(program_info)); + WaitResourceAvailable(std::addressof(program_info)); /* Actually create the process. */ - R_TRY(ldr::pm::CreateProcess(std::addressof(process_handle), pin_id, GetLoaderCreateProcessFlags(args.flags), resource::GetResourceLimitHandle(std::addressof(program_info)))); + R_TRY(ldr::pm::CreateProcess(std::addressof(process_handle), pin_id, GetLoaderCreateProcessFlags(flags), GetResourceLimitHandle(std::addressof(program_info)))); } /* Get the process id. */ os::ProcessId process_id = os::GetProcessId(process_handle); /* Make new process info. */ - ProcessInfo *process_info = g_process_info_allocator.AllocateProcessInfo(process_handle, process_id, pin_id, location, override_status); + ProcessInfo *process_info = AllocateProcessInfo(process_handle, process_id, pin_id, fixed_location, override_status); AMS_ABORT_UNLESS(process_info != nullptr); - /* Link new process info. */ + /* Add the new process info to the process list. */ { - ProcessListAccessor list(g_process_list); + auto list = GetProcessList(); list->push_back(*process_info); - process_info->LinkToMultiWait(multi_wait); } /* Prevent resource leakage if register fails. */ ON_RESULT_FAILURE { - ProcessListAccessor list(g_process_list); + auto list = GetProcessList(); process_info->Cleanup(); CleanupProcessInfo(list, process_info); }; @@ -296,166 +195,73 @@ const u8 *aci_fah = acid_fac + program_info.acid_fac_size; /* Register with FS and SM. */ - R_TRY(fsprRegisterProgram(static_cast(process_id), static_cast(location.program_id), static_cast(location.storage_id), aci_fah, program_info.aci_fah_size, acid_fac, program_info.acid_fac_size)); - R_TRY(sm::manager::RegisterProcess(process_id, location.program_id, override_status, acid_sac, program_info.acid_sac_size, aci_sac, program_info.aci_sac_size)); + R_TRY(fsprRegisterProgram(static_cast(process_id), static_cast(fixed_location.program_id), static_cast(fixed_location.storage_id), aci_fah, program_info.aci_fah_size, acid_fac, program_info.acid_fac_size)); + R_TRY(sm::manager::RegisterProcess(process_id, fixed_location.program_id, override_status, 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) { + if (ShouldSignalOnStart(flags) && allow_debug) { process_info->SetSignalOnStart(); } - if (ShouldSignalOnExit(args.flags)) { + if (ShouldSignalOnExit(flags)) { process_info->SetSignalOnExit(); } - if (ShouldSignalOnDebugEvent(args.flags) && allow_debug) { + if (ShouldSignalOnDebugEvent(flags) && allow_debug) { process_info->SetSignalOnDebugEvent(); } /* Process hooks/signaling. */ - if (location.program_id == g_program_id_hook) { + if (fixed_location.program_id == g_program_id_hook) { os::SignalSystemEvent(std::addressof(g_hook_to_create_process_event)); g_program_id_hook = ncm::InvalidProgramId; } else if (is_application && g_application_hook) { os::SignalSystemEvent(std::addressof(g_hook_to_create_application_process_event)); g_application_hook = false; - } else if (!ShouldStartSuspended(args.flags)) { + } else if (!ShouldStartSuspended(flags)) { R_TRY(StartProcess(process_info, std::addressof(program_info))); } - *args.out_process_id = process_id; + *out_process_id = process_id; + *out_process_info = process_info; R_SUCCEED(); } - void OnProcessSignaled(ProcessListAccessor &list, ProcessInfo *process_info) { - /* Reset the process's signal. */ - svc::ResetSignal(process_info->GetHandle()); - - /* Update the process's state. */ - const svc::ProcessState old_state = process_info->GetState(); - { - s64 tmp = 0; - R_ABORT_UNLESS(svc::GetProcessInfo(std::addressof(tmp), process_info->GetHandle(), svc::ProcessInfoType_ProcessState)); - process_info->SetState(static_cast(tmp)); - } - const svc::ProcessState new_state = process_info->GetState(); - - /* If we're transitioning away from crashed, clear waiting attached. */ - if (old_state == svc::ProcessState_Crashed && new_state != svc::ProcessState_Crashed) { - process_info->ClearExceptionWaitingAttach(); - } - - switch (new_state) { - case svc::ProcessState_Created: - case svc::ProcessState_CreatedAttached: - case svc::ProcessState_Terminating: - break; - case svc::ProcessState_Running: - if (process_info->ShouldSignalOnDebugEvent()) { - process_info->ClearSuspended(); - process_info->SetSuspendedStateChanged(); - os::SignalSystemEvent(std::addressof(g_process_event)); - } else if (hos::GetVersion() >= hos::Version_2_0_0 && process_info->ShouldSignalOnStart()) { - process_info->SetStartedStateChanged(); - process_info->ClearSignalOnStart(); - os::SignalSystemEvent(std::addressof(g_process_event)); - } - process_info->ClearUnhandledException(); - break; - case svc::ProcessState_Crashed: - if (!process_info->HasUnhandledException()) { - process_info->SetExceptionOccurred(); - os::SignalSystemEvent(std::addressof(g_process_event)); - } - process_info->SetExceptionWaitingAttach(); - break; - case svc::ProcessState_RunningAttached: - if (process_info->ShouldSignalOnDebugEvent()) { - process_info->ClearSuspended(); - process_info->SetSuspendedStateChanged(); - os::SignalSystemEvent(std::addressof(g_process_event)); - } - process_info->ClearUnhandledException(); - break; - case svc::ProcessState_Terminated: - /* Free process resources, unlink from multi wait. */ - process_info->Cleanup(); - - if (hos::GetVersion() < hos::Version_5_0_0 && process_info->ShouldSignalOnExit()) { - os::SignalSystemEvent(std::addressof(g_process_event)); - } else { - /* Handle the case where we need to keep the process alive some time longer. */ - if (hos::GetVersion() >= hos::Version_5_0_0 && process_info->ShouldSignalOnExit()) { - /* Remove from the living list. */ - list->Remove(process_info); - - /* Add the process to the list of dead processes. */ - { - ProcessListAccessor dead_list(g_dead_process_list); - dead_list->push_back(*process_info); - } - - /* Signal. */ - os::SignalSystemEvent(std::addressof(g_process_event)); - } else { - /* Actually delete process. */ - CleanupProcessInfo(list, process_info); - } - } - break; - case svc::ProcessState_DebugBreak: - if (process_info->ShouldSignalOnDebugEvent()) { - process_info->SetSuspended(); - process_info->SetSuspendedStateChanged(); - os::SignalSystemEvent(std::addressof(g_process_event)); - } - break; - } - } - } /* Initialization. */ Result InitializeProcessManager() { /* Create events. */ - R_ABORT_UNLESS(os::CreateSystemEvent(std::addressof(g_process_event), os::EventClearMode_AutoClear, true)); - R_ABORT_UNLESS(os::CreateSystemEvent(std::addressof(g_hook_to_create_process_event), os::EventClearMode_AutoClear, true)); - R_ABORT_UNLESS(os::CreateSystemEvent(std::addressof(g_hook_to_create_application_process_event), os::EventClearMode_AutoClear, true)); - R_ABORT_UNLESS(os::CreateSystemEvent(std::addressof(g_boot_finished_event), os::EventClearMode_AutoClear, true)); + CreateProcessEvent(); + CreateDebuggerEvent(); + R_ABORT_UNLESS(os::CreateSystemEvent(std::addressof(g_boot_finished_event), os::EventClearMode_AutoClear, true)); /* Initialize resource limits. */ - R_TRY(resource::InitializeResourceManager()); + R_TRY(InitializeSpec()); - /* Create thread. */ - R_ABORT_UNLESS(os::CreateThread(std::addressof(g_process_track_thread), ProcessTrackingMain, nullptr, g_process_track_thread_stack, sizeof(g_process_track_thread_stack), AMS_GET_SYSTEM_THREAD_PRIORITY(pm, ProcessTrack))); - os::SetThreadNamePointer(std::addressof(g_process_track_thread), AMS_GET_SYSTEM_THREAD_NAME(pm, ProcessTrack)); + /* Initialize the process tracker. */ + g_process_tracker.Initialize(g_process_track_thread_stack, sizeof(g_process_track_thread_stack)); - /* Start thread. */ - os::StartThread(std::addressof(g_process_track_thread)); + /* Start the process tracker thread. */ + g_process_tracker.StartThread(); R_SUCCEED(); } /* Process Management. */ Result LaunchProgram(os::ProcessId *out_process_id, const ncm::ProgramLocation &loc, u32 flags) { - /* Ensure we only try to launch one program at a time. */ - std::scoped_lock lk(g_launch_program_lock); + /* Launch the program. */ + ProcessInfo *process_info = nullptr; + R_TRY(LaunchProgramImpl(std::addressof(process_info), out_process_id, loc, flags)); - /* Set global arguments, signal, wait. */ - g_process_launch_args = { - .out_process_id = out_process_id, - .location = loc, - .flags = flags, - }; - g_process_launch_start_event.Signal(); - g_process_launch_finish_event.Wait(); - - R_RETURN(g_process_launch_result); + /* Register the process info with the tracker. */ + g_process_tracker.QueueEntry(process_info); + R_SUCCEED(); } Result StartProcess(os::ProcessId process_id) { - ProcessListAccessor list(g_process_list); + auto list = GetProcessList(); auto process_info = list->Find(process_id); R_UNLESS(process_info != nullptr, pm::ResultProcessNotFound()); @@ -467,7 +273,7 @@ } Result TerminateProcess(os::ProcessId process_id) { - ProcessListAccessor list(g_process_list); + auto list = GetProcessList(); auto process_info = list->Find(process_id); R_UNLESS(process_info != nullptr, pm::ResultProcessNotFound()); @@ -476,7 +282,7 @@ } Result TerminateProgram(ncm::ProgramId program_id) { - ProcessListAccessor list(g_process_list); + auto list = GetProcessList(); auto process_info = list->Find(program_id); R_UNLESS(process_info != nullptr, pm::ResultProcessNotFound()); @@ -484,15 +290,10 @@ R_RETURN(svc::TerminateProcess(process_info->GetHandle())); } - Result GetProcessEventHandle(os::NativeHandle *out) { - *out = os::GetReadableHandleOfSystemEvent(std::addressof(g_process_event)); - R_SUCCEED(); - } - Result GetProcessEventInfo(ProcessEventInfo *out) { /* Check for event from current process. */ { - ProcessListAccessor list(g_process_list); + auto list = GetProcessList(); for (auto &process : *list) { @@ -528,14 +329,14 @@ /* Check for event from exited process. */ if (hos::GetVersion() >= hos::Version_5_0_0) { - ProcessListAccessor dead_list(g_dead_process_list); + auto exit_list = GetExitList(); - if (!dead_list->empty()) { - auto &process_info = dead_list->front(); - out->event = GetProcessEventValue(ProcessEvent::Exited); + if (!exit_list->empty()) { + auto &process_info = exit_list->front(); + out->event = GetProcessEventValue(ProcessEvent::Exited); out->process_id = process_info.GetProcessId(); - CleanupProcessInfo(dead_list, std::addressof(process_info)); + CleanupProcessInfo(exit_list, std::addressof(process_info)); R_SUCCEED(); } } @@ -546,7 +347,7 @@ } Result CleanupProcess(os::ProcessId process_id) { - ProcessListAccessor list(g_process_list); + auto list = GetProcessList(); auto process_info = list->Find(process_id); R_UNLESS(process_info != nullptr, pm::ResultProcessNotFound()); @@ -557,7 +358,7 @@ } Result ClearExceptionOccurred(os::ProcessId process_id) { - ProcessListAccessor list(g_process_list); + auto list = GetProcessList(); auto process_info = list->Find(process_id); R_UNLESS(process_info != nullptr, pm::ResultProcessNotFound()); @@ -575,7 +376,7 @@ } Result GetExceptionProcessIdList(u32 *out_count, os::ProcessId *out_process_ids, size_t max_out_count) { - ProcessListAccessor list(g_process_list); + auto list = GetProcessList(); size_t count = 0; @@ -596,7 +397,7 @@ } Result GetProcessId(os::ProcessId *out, ncm::ProgramId program_id) { - ProcessListAccessor list(g_process_list); + auto list = GetProcessList(); auto process_info = list->Find(program_id); R_UNLESS(process_info != nullptr, pm::ResultProcessNotFound()); @@ -606,7 +407,7 @@ } Result GetProgramId(ncm::ProgramId *out, os::ProcessId process_id) { - ProcessListAccessor list(g_process_list); + auto list = GetProcessList(); auto process_info = list->Find(process_id); R_UNLESS(process_info != nullptr, pm::ResultProcessNotFound()); @@ -616,7 +417,7 @@ } Result GetApplicationProcessId(os::ProcessId *out_process_id) { - ProcessListAccessor list(g_process_list); + auto list = GetProcessList(); for (auto &process : *list) { if (process.IsApplication()) { @@ -629,7 +430,7 @@ } Result AtmosphereGetProcessInfo(os::NativeHandle *out_process_handle, ncm::ProgramLocation *out_loc, cfg::OverrideStatus *out_status, os::ProcessId process_id) { - ProcessListAccessor list(g_process_list); + auto list = GetProcessList(); auto process_info = list->Find(process_id); R_UNLESS(process_info != nullptr, pm::ResultProcessNotFound()); @@ -706,33 +507,4 @@ R_SUCCEED(); } - /* Resource Limit API. */ - Result BoostSystemMemoryResourceLimit(u64 boost_size) { - R_RETURN(resource::BoostSystemMemoryResourceLimit(boost_size)); - } - - Result BoostApplicationThreadResourceLimit() { - R_RETURN(resource::BoostApplicationThreadResourceLimit()); - } - - Result BoostSystemThreadResourceLimit() { - R_RETURN(resource::BoostSystemThreadResourceLimit()); - } - - Result GetAppletResourceLimitCurrentValue(pm::ResourceLimitValue *out) { - R_RETURN(resource::GetResourceLimitCurrentValue(ResourceLimitGroup_Applet, out)); - } - - Result GetAppletResourceLimitPeakValue(pm::ResourceLimitValue *out) { - R_RETURN(resource::GetResourceLimitPeakValue(ResourceLimitGroup_Applet, out)); - } - - Result AtmosphereGetCurrentLimitInfo(s64 *out_cur_val, s64 *out_lim_val, u32 group, u32 resource) { - R_RETURN(resource::GetResourceLimitValues(out_cur_val, out_lim_val, static_cast(group), static_cast(resource))); - } - - Result BoostSystemMemoryResourceLimitForMitm(u64 boost_size) { - R_RETURN(resource::BoostSystemMemoryResourceLimitForMitm(boost_size)); - } - } diff --git a/stratosphere/pm/source/impl/pm_process_manager.hpp b/stratosphere/pm/source/impl/pm_process_manager.hpp index b12e916..61acbe1 100644 --- a/stratosphere/pm/source/impl/pm_process_manager.hpp +++ b/stratosphere/pm/source/impl/pm_process_manager.hpp @@ -48,13 +48,4 @@ Result NotifyBootFinished(); Result GetBootFinishedEventHandle(os::NativeHandle *out); - /* Resource Limit API. */ - Result BoostSystemMemoryResourceLimit(u64 boost_size); - Result BoostApplicationThreadResourceLimit(); - Result BoostSystemThreadResourceLimit(); - Result GetAppletResourceLimitCurrentValue(pm::ResourceLimitValue *out); - Result GetAppletResourceLimitPeakValue(pm::ResourceLimitValue *out); - Result AtmosphereGetCurrentLimitInfo(s64 *out_cur_val, s64 *out_lim_val, u32 group, u32 resource); - Result BoostSystemMemoryResourceLimitForMitm(u64 boost_size); - } diff --git a/stratosphere/pm/source/impl/pm_process_tracker.cpp b/stratosphere/pm/source/impl/pm_process_tracker.cpp new file mode 100644 index 0000000..5d1c3b4 --- /dev/null +++ b/stratosphere/pm/source/impl/pm_process_tracker.cpp @@ -0,0 +1,193 @@ +/* + * Copyright (c) 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 . + */ +#include +#include "pm_process_tracker.hpp" +#include "pm_process_info.hpp" +#include "pm_spec.hpp" + +namespace ams::pm::impl { + + namespace { + + /* Global process event. */ + constinit os::SystemEventType g_process_event; + + } + + void ProcessTracker::Initialize(void *stack, size_t stack_size) { + /* Initialize our events. */ + os::InitializeEvent(std::addressof(m_request_event), false, os::EventClearMode_AutoClear); + os::InitializeEvent(std::addressof(m_reply_event), false, os::EventClearMode_AutoClear); + + /* Our process count should initially be zero. */ + m_process_count = 0; + + /* Create the process tracking thread. */ + R_ABORT_UNLESS(os::CreateThread(std::addressof(m_thread), ProcessTracker::ThreadFunction, this, stack, stack_size, AMS_GET_SYSTEM_THREAD_PRIORITY(pm, ProcessTrack))); + os::SetThreadNamePointer(std::addressof(m_thread), AMS_GET_SYSTEM_THREAD_NAME(pm, ProcessTrack)); + } + + void ProcessTracker::StartThread() { + /* Start thread. */ + os::StartThread(std::addressof(m_thread)); + } + + void ProcessTracker::ThreadBody() { + /* This is the main loop of the process tracking thread. */ + + /* Setup multi wait/holders. */ + os::MultiWaitType process_multi_wait; + os::MultiWaitHolderType enqueue_event_holder; + os::InitializeMultiWait(std::addressof(process_multi_wait)); + os::InitializeMultiWaitHolder(std::addressof(enqueue_event_holder), std::addressof(m_request_event)); + os::LinkMultiWaitHolder(std::addressof(process_multi_wait), std::addressof(enqueue_event_holder)); + + while (true) { + auto signaled_holder = os::WaitAny(std::addressof(process_multi_wait)); + if (signaled_holder == std::addressof(enqueue_event_holder)) { + /* TryWait will clear signaled, preventing duplicate notifications. */ + if (os::TryWaitEvent(std::addressof(m_request_event))) { + /* Link the process to our multi-wait. */ + os::LinkMultiWaitHolder(std::addressof(process_multi_wait), m_queued_process_info->GetMultiWaitHolder()); + m_queued_process_info = nullptr; + + /* Increment our process count. */ + ++m_process_count; + + /* Reply. */ + os::SignalEvent(std::addressof(m_reply_event)); + } + } else { + /* Some process was signaled. */ + this->OnProcessSignaled(reinterpret_cast(os::GetMultiWaitHolderUserData(signaled_holder))); + } + } + } + + void ProcessTracker::QueueEntry(ProcessInfo *process_info) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Request to enqueue the process info. */ + m_queued_process_info = process_info; + os::SignalEvent(std::addressof(m_request_event)); + + /* Wait for acknowledgement. */ + os::WaitEvent(std::addressof(m_reply_event)); + } + + void ProcessTracker::OnProcessSignaled(ProcessInfo *process_info) { + /* Get the process list. */ + auto list = GetProcessList(); + + /* Reset the process's signal. */ + svc::ResetSignal(process_info->GetHandle()); + + /* Update the process's state. */ + const svc::ProcessState old_state = process_info->GetState(); + { + s64 tmp = 0; + R_ABORT_UNLESS(svc::GetProcessInfo(std::addressof(tmp), process_info->GetHandle(), svc::ProcessInfoType_ProcessState)); + process_info->SetState(static_cast(tmp)); + } + const svc::ProcessState new_state = process_info->GetState(); + + /* If we're transitioning away from crashed, clear waiting attached. */ + if (old_state == svc::ProcessState_Crashed && new_state != svc::ProcessState_Crashed) { + process_info->ClearExceptionWaitingAttach(); + } + + switch (new_state) { + case svc::ProcessState_Created: + case svc::ProcessState_CreatedAttached: + case svc::ProcessState_Terminating: + break; + case svc::ProcessState_Running: + if (process_info->ShouldSignalOnDebugEvent()) { + process_info->ClearSuspended(); + process_info->SetSuspendedStateChanged(); + os::SignalSystemEvent(std::addressof(g_process_event)); + } else if (hos::GetVersion() >= hos::Version_2_0_0 && process_info->ShouldSignalOnStart()) { + process_info->SetStartedStateChanged(); + process_info->ClearSignalOnStart(); + os::SignalSystemEvent(std::addressof(g_process_event)); + } + process_info->ClearUnhandledException(); + break; + case svc::ProcessState_Crashed: + if (!process_info->HasUnhandledException()) { + process_info->SetExceptionOccurred(); + os::SignalSystemEvent(std::addressof(g_process_event)); + } + process_info->SetExceptionWaitingAttach(); + break; + case svc::ProcessState_RunningAttached: + if (process_info->ShouldSignalOnDebugEvent()) { + process_info->ClearSuspended(); + process_info->SetSuspendedStateChanged(); + os::SignalSystemEvent(std::addressof(g_process_event)); + } + process_info->ClearUnhandledException(); + break; + case svc::ProcessState_Terminated: + /* Unlink from multi wait. */ + os::UnlinkMultiWaitHolder(process_info->GetMultiWaitHolder()); + + /* Free process resources. */ + process_info->Cleanup(); + + if (hos::GetVersion() < hos::Version_5_0_0 && process_info->ShouldSignalOnExit()) { + os::SignalSystemEvent(std::addressof(g_process_event)); + } else { + /* Handle the case where we need to keep the process alive some time longer. */ + if (hos::GetVersion() >= hos::Version_5_0_0 && process_info->ShouldSignalOnExit()) { + /* Remove from the living list. */ + list->Remove(process_info); + + /* Add the process to the list of dead processes. */ + { + GetExitList()->push_back(*process_info); + } + + /* Signal. */ + os::SignalSystemEvent(std::addressof(g_process_event)); + } else { + /* Actually delete process. */ + CleanupProcessInfo(list, process_info); + } + } + break; + case svc::ProcessState_DebugBreak: + if (process_info->ShouldSignalOnDebugEvent()) { + process_info->SetSuspended(); + process_info->SetSuspendedStateChanged(); + os::SignalSystemEvent(std::addressof(g_process_event)); + } + break; + } + } + + void CreateProcessEvent() { + /* Create process event. */ + R_ABORT_UNLESS(os::CreateSystemEvent(std::addressof(g_process_event), os::EventClearMode_AutoClear, true)); + } + + Result GetProcessEventHandle(os::NativeHandle *out) { + *out = os::GetReadableHandleOfSystemEvent(std::addressof(g_process_event)); + R_SUCCEED(); + } + +} diff --git a/stratosphere/pm/source/impl/pm_process_tracker.hpp b/stratosphere/pm/source/impl/pm_process_tracker.hpp new file mode 100644 index 0000000..c1de7c2 --- /dev/null +++ b/stratosphere/pm/source/impl/pm_process_tracker.hpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 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 . + */ +#pragma once +#include +#include "pm_process_info.hpp" + +namespace ams::pm::impl { + + class ProcessTracker { + NON_COPYABLE(ProcessTracker); + NON_MOVEABLE(ProcessTracker); + private: + os::ThreadType m_thread; + os::EventType m_request_event; + os::EventType m_reply_event; + os::SdkMutex m_mutex; + ProcessInfo *m_queued_process_info; + util::Atomic m_process_count; + public: + constexpr ProcessTracker() : m_thread(), m_request_event(), m_reply_event(), m_mutex(), m_queued_process_info(nullptr), m_process_count(0) { + /* ... */ + } + + void Initialize(void *stack, size_t stack_size); + void StartThread(); + + void QueueEntry(ProcessInfo *process_info); + + u32 GetProcessCount() const { + return m_process_count; + } + private: + void OnProcessSignaled(ProcessInfo *process_info); + private: + static void ThreadFunction(void *_this) { + static_cast(_this)->ThreadBody(); + } + + void ThreadBody(); + }; + + void CreateProcessEvent(); + +} diff --git a/stratosphere/pm/source/impl/pm_resource_manager.cpp b/stratosphere/pm/source/impl/pm_resource_manager.cpp deleted file mode 100644 index f11a72d..0000000 --- a/stratosphere/pm/source/impl/pm_resource_manager.cpp +++ /dev/null @@ -1,486 +0,0 @@ -/* - * Copyright (c) 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 . - */ -#include -#include "pm_resource_manager.hpp" - -namespace ams::pm::resource { - - namespace { - - constexpr svc::LimitableResource LimitableResources[] = { - svc::LimitableResource_PhysicalMemoryMax, - svc::LimitableResource_ThreadCountMax, - svc::LimitableResource_EventCountMax, - svc::LimitableResource_TransferMemoryCountMax, - svc::LimitableResource_SessionCountMax, - }; - - /* Definitions for limit differences over time. */ - constexpr size_t ExtraSystemMemorySize400 = 10_MB; - constexpr size_t ReservedMemorySize600 = 5_MB; - - /* Atmosphere always allocates extra memory for system usage. */ - constexpr size_t ExtraSystemMemorySizeAtmosphere = 32_MB; - - /* Desired extra threads. */ - constexpr u64 BaseApplicationThreads = 96; - constexpr u64 BaseAppletThreads = 96; - constexpr u64 BaseSystemThreads = 800 - BaseAppletThreads - BaseApplicationThreads; - - constexpr s64 ExtraSystemThreads = 1024 - BaseSystemThreads; - constexpr s64 ExtraApplicationThreads = 256 - BaseApplicationThreads; - constexpr s64 ExtraAppletThreads = 256 - BaseAppletThreads; - - static_assert(ExtraSystemThreads >= 0); - static_assert(ExtraApplicationThreads >= 0); - static_assert(ExtraAppletThreads >= 0); - - /* Globals. */ - constinit os::SdkMutex g_resource_limit_lock; - constinit os::NativeHandle g_resource_limit_handles[ResourceLimitGroup_Count]; - constinit spl::MemoryArrangement g_memory_arrangement = spl::MemoryArrangement_Standard; - constinit u64 g_extra_threads_available[ResourceLimitGroup_Count]; - - constinit os::SdkMutex g_system_memory_boost_lock; - constinit u64 g_system_memory_boost_size = 0; - constinit u64 g_system_memory_boost_size_for_mitm = 0; - - ALWAYS_INLINE u64 GetCurrentSystemMemoryBoostSize() { - return g_system_memory_boost_size + g_system_memory_boost_size_for_mitm; - } - - constinit u64 g_resource_limits[ResourceLimitGroup_Count][svc::LimitableResource_Count] = { - [ResourceLimitGroup_System] = { - [svc::LimitableResource_PhysicalMemoryMax] = 0, /* Initialized dynamically later. */ - [svc::LimitableResource_ThreadCountMax] = BaseSystemThreads, - [svc::LimitableResource_EventCountMax] = 0, /* Initialized dynamically later. */ - [svc::LimitableResource_TransferMemoryCountMax] = 0, /* Initialized dynamically later. */ - [svc::LimitableResource_SessionCountMax] = 0, /* Initialized dynamically later. */ - }, - [ResourceLimitGroup_Application] = { - [svc::LimitableResource_PhysicalMemoryMax] = 0, /* Initialized dynamically later. */ - [svc::LimitableResource_ThreadCountMax] = BaseApplicationThreads, - [svc::LimitableResource_EventCountMax] = 0, - [svc::LimitableResource_TransferMemoryCountMax] = 32, - [svc::LimitableResource_SessionCountMax] = 1, - }, - [ResourceLimitGroup_Applet] = { - [svc::LimitableResource_PhysicalMemoryMax] = 0, /* Initialized dynamically later. */ - [svc::LimitableResource_ThreadCountMax] = BaseAppletThreads, - [svc::LimitableResource_EventCountMax] = 0, - [svc::LimitableResource_TransferMemoryCountMax] = 32, - [svc::LimitableResource_SessionCountMax] = 5, - }, - }; - - constinit u64 g_memory_resource_limits[spl::MemoryArrangement_Count][ResourceLimitGroup_Count] = { - [spl::MemoryArrangement_Standard] = { - [ResourceLimitGroup_System] = 269_MB, - [ResourceLimitGroup_Application] = 3285_MB, - [ResourceLimitGroup_Applet] = 535_MB, - }, - [spl::MemoryArrangement_StandardForAppletDev] = { - [ResourceLimitGroup_System] = 481_MB, - [ResourceLimitGroup_Application] = 2048_MB, - [ResourceLimitGroup_Applet] = 1560_MB, - }, - [spl::MemoryArrangement_StandardForSystemDev] = { - [ResourceLimitGroup_System] = 328_MB, - [ResourceLimitGroup_Application] = 3285_MB, - [ResourceLimitGroup_Applet] = 476_MB, - }, - [spl::MemoryArrangement_Expanded] = { - [ResourceLimitGroup_System] = 653_MB, - [ResourceLimitGroup_Application] = 4916_MB, - [ResourceLimitGroup_Applet] = 568_MB, - }, - [spl::MemoryArrangement_ExpandedForAppletDev] = { - [ResourceLimitGroup_System] = 653_MB, - [ResourceLimitGroup_Application] = 3285_MB, - [ResourceLimitGroup_Applet] = 2199_MB, - }, - }; - - /* Helpers. */ - Result SetMemoryResourceLimitLimitValue(ResourceLimitGroup group, u64 new_memory_limit) { - const u64 old_memory_limit = g_resource_limits[group][svc::LimitableResource_PhysicalMemoryMax]; - g_resource_limits[group][svc::LimitableResource_PhysicalMemoryMax] = new_memory_limit; - - /* Restore the old memory limit if we fail. */ - ON_RESULT_FAILURE { g_resource_limits[group][svc::LimitableResource_PhysicalMemoryMax] = old_memory_limit; }; - - /* Set the resource limit. */ - R_RETURN(svc::SetResourceLimitLimitValue(GetResourceLimitHandle(group), svc::LimitableResource_PhysicalMemoryMax, g_resource_limits[group][svc::LimitableResource_PhysicalMemoryMax])); - } - - 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 < svc::LimitableResource_Count; i++) { - const auto resource = LimitableResources[i]; - if (resource == svc::LimitableResource_PhysicalMemoryMax) { - continue; - } - R_TRY(svc::SetResourceLimitLimitValue(GetResourceLimitHandle(group), resource, g_resource_limits[group][resource])); - } - - R_SUCCEED(); - } - - 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 auto reslimit_hnd = GetResourceLimitHandle(group); - for (size_t i = 0; i < svc::LimitableResource_Count; i++) { - const auto resource = LimitableResources[i]; - - s64 value = 0; - while (true) { - R_ABORT_UNLESS(svc::GetResourceLimitCurrentValue(&value, reslimit_hnd, resource)); - if (value == 0) { - break; - } - os::SleepThread(TimeSpan::FromMilliSeconds(1)); - } - } - } - - void WaitApplicationMemoryAvailable() { - /* Get firmware version. */ - const auto fw_ver = hos::GetVersion(); - - /* On 15.0.0+, pm considers application memory to be available if there is exactly 96 MB outstanding. */ - /* This is probably because this corresponds to the gameplay-recording memory. */ - constexpr u64 AllowedUsedApplicationMemory = 96_MB; - - /* Wait for memory to be available. */ - u64 value = 0; - while (true) { - R_ABORT_UNLESS(svc::GetSystemInfo(&value, svc::SystemInfoType_UsedPhysicalMemorySize, svc::InvalidHandle, svc::PhysicalMemorySystemInfo_Application)); - if (value == 0 || (fw_ver >= hos::Version_15_0_0 && value == AllowedUsedApplicationMemory)) { - break; - } - os::SleepThread(TimeSpan::FromMilliSeconds(1)); - } - } - - bool IsKTraceEnabled() { - u64 value = 0; - R_ABORT_UNLESS(svc::GetInfo(std::addressof(value), svc::InfoType_MesosphereMeta, svc::InvalidHandle, svc::MesosphereMetaInfo_IsKTraceEnabled)); - - return value != 0; - } - - ALWAYS_INLINE Result BoostThreadResourceLimitLocked(ResourceLimitGroup group) { - AMS_ASSERT(g_resource_limit_lock.IsLockedByCurrentThread()); - - /* Set new limit. */ - const s64 new_thread_count = g_resource_limits[group][svc::LimitableResource_ThreadCountMax] + g_extra_threads_available[group]; - R_TRY(svc::SetResourceLimitLimitValue(GetResourceLimitHandle(group), svc::LimitableResource_ThreadCountMax, new_thread_count)); - - /* Record that we did so. */ - g_resource_limits[group][svc::LimitableResource_ThreadCountMax] = new_thread_count; - g_extra_threads_available[group] = 0; - - R_SUCCEED(); - } - - template - ALWAYS_INLINE Result GetResourceLimitValuesImpl(ResourceLimitGroup group, pm::ResourceLimitValue *out) { - /* Sanity check group. */ - AMS_ABORT_UNLESS(group < ResourceLimitGroup_Count); - - /* Get handle. */ - const auto handle = GetResourceLimitHandle(group); - - /* Get values. */ - int64_t values[svc::LimitableResource_Count]; - R_ABORT_UNLESS(GetResourceLimitValueImpl(std::addressof(values[svc::LimitableResource_PhysicalMemoryMax]), handle, svc::LimitableResource_PhysicalMemoryMax)); - R_ABORT_UNLESS(GetResourceLimitValueImpl(std::addressof(values[svc::LimitableResource_ThreadCountMax]), handle, svc::LimitableResource_ThreadCountMax)); - R_ABORT_UNLESS(GetResourceLimitValueImpl(std::addressof(values[svc::LimitableResource_EventCountMax]), handle, svc::LimitableResource_EventCountMax)); - R_ABORT_UNLESS(GetResourceLimitValueImpl(std::addressof(values[svc::LimitableResource_TransferMemoryCountMax]), handle, svc::LimitableResource_TransferMemoryCountMax)); - R_ABORT_UNLESS(GetResourceLimitValueImpl(std::addressof(values[svc::LimitableResource_SessionCountMax]), handle, svc::LimitableResource_SessionCountMax)); - - /* Set to output. */ - out->physical_memory = values[svc::LimitableResource_PhysicalMemoryMax]; - out->thread_count = values[svc::LimitableResource_ThreadCountMax]; - out->event_count = values[svc::LimitableResource_EventCountMax]; - out->transfer_memory_count = values[svc::LimitableResource_TransferMemoryCountMax]; - out->session_count = values[svc::LimitableResource_SessionCountMax]; - - R_SUCCEED(); - } - - Result BoostSystemMemoryResourceLimitLocked(u64 normal_boost, u64 mitm_boost) { - /* Check pre-conditions. */ - AMS_ASSERT(g_system_memory_boost_lock.IsLockedByCurrentThread()); - - /* Determine total boost. */ - const u64 boost_size = normal_boost + mitm_boost; - - /* Don't allow all application memory to be taken away. */ - R_UNLESS(boost_size <= g_memory_resource_limits[g_memory_arrangement][ResourceLimitGroup_Application], pm::ResultInvalidSize()); - - const u64 new_app_size = g_memory_resource_limits[g_memory_arrangement][ResourceLimitGroup_Application] - boost_size; - { - std::scoped_lock lk(g_resource_limit_lock); - - if (hos::GetVersion() >= hos::Version_5_0_0) { - /* Starting in 5.0.0, PM does not allow for only one of the sets to fail. */ - if (boost_size < GetCurrentSystemMemoryBoostSize()) { - R_TRY(svc::SetUnsafeLimit(boost_size)); - R_ABORT_UNLESS(SetMemoryResourceLimitLimitValue(ResourceLimitGroup_Application, new_app_size)); - } else { - R_TRY(SetMemoryResourceLimitLimitValue(ResourceLimitGroup_Application, new_app_size)); - R_ABORT_UNLESS(svc::SetUnsafeLimit(boost_size)); - } - } else { - const u64 new_sys_size = g_memory_resource_limits[g_memory_arrangement][ResourceLimitGroup_System] + boost_size; - if (boost_size < GetCurrentSystemMemoryBoostSize()) { - 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 = normal_boost; - g_system_memory_boost_size_for_mitm = mitm_boost; - } - - R_SUCCEED(); - } - - } - - /* 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_ABORT_UNLESS(svc::GetInfo(&value, svc::InfoType_ResourceLimit, svc::InvalidHandle, 0)); - g_resource_limit_handles[i] = static_cast(value); - } else { - R_ABORT_UNLESS(svc::CreateResourceLimit(g_resource_limit_handles + i)); - } - } - - /* Adjust memory limits based on hos firmware version. */ - const auto hos_version = hos::GetVersion(); - if (hos_version >= hos::Version_4_0_0) { - /* 4.0.0 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; - } - - /* Determine system resource counts. */ - { - /* Get the total resource counts. */ - s64 total_events, total_transfer_memories, total_sessions; - R_ABORT_UNLESS(svc::GetResourceLimitLimitValue(std::addressof(total_events), GetResourceLimitHandle(ResourceLimitGroup_System), svc::LimitableResource_EventCountMax)); - R_ABORT_UNLESS(svc::GetResourceLimitLimitValue(std::addressof(total_transfer_memories), GetResourceLimitHandle(ResourceLimitGroup_System), svc::LimitableResource_TransferMemoryCountMax)); - R_ABORT_UNLESS(svc::GetResourceLimitLimitValue(std::addressof(total_sessions), GetResourceLimitHandle(ResourceLimitGroup_System), svc::LimitableResource_SessionCountMax)); - - /* Determine system counts. */ - const s64 sys_events = total_events - (g_resource_limits[ResourceLimitGroup_Application][svc::LimitableResource_EventCountMax] + g_resource_limits[ResourceLimitGroup_Applet][svc::LimitableResource_EventCountMax]); - const s64 sys_transfer_memories = total_transfer_memories - (g_resource_limits[ResourceLimitGroup_Application][svc::LimitableResource_TransferMemoryCountMax] + g_resource_limits[ResourceLimitGroup_Applet][svc::LimitableResource_TransferMemoryCountMax]); - const s64 sys_sessions = total_sessions - (g_resource_limits[ResourceLimitGroup_Application][svc::LimitableResource_SessionCountMax] + g_resource_limits[ResourceLimitGroup_Applet][svc::LimitableResource_SessionCountMax]); - - /* Check system counts. */ - AMS_ABORT_UNLESS(sys_events >= 0); - AMS_ABORT_UNLESS(sys_transfer_memories >= 0); - AMS_ABORT_UNLESS(sys_sessions >= 0); - - /* Set system counts. */ - g_resource_limits[ResourceLimitGroup_System][svc::LimitableResource_EventCountMax] = sys_events; - g_resource_limits[ResourceLimitGroup_System][svc::LimitableResource_TransferMemoryCountMax] = sys_transfer_memories; - g_resource_limits[ResourceLimitGroup_System][svc::LimitableResource_SessionCountMax] = sys_sessions; - } - - /* Determine extra application threads. */ - { - /* Get total threads available. */ - s64 total_threads; - R_ABORT_UNLESS(svc::GetResourceLimitLimitValue(std::addressof(total_threads), GetResourceLimitHandle(ResourceLimitGroup_System), svc::LimitableResource_ThreadCountMax)); - - /* Check that we have enough threads. */ - const s64 required_threads = g_resource_limits[ResourceLimitGroup_System][svc::LimitableResource_ThreadCountMax] + g_resource_limits[ResourceLimitGroup_Application][svc::LimitableResource_ThreadCountMax] + g_resource_limits[ResourceLimitGroup_Applet][svc::LimitableResource_ThreadCountMax]; - AMS_ABORT_UNLESS(total_threads >= required_threads); - - /* Set the number of extra threads. */ - const s64 extra_threads = total_threads - required_threads; - if constexpr (true /* TODO: Should we expose the old "all extra threads are application" behavior? Seems pointless. */) { - if (extra_threads > 0) { - /* If we have any extra threads at all, require that we have enough. */ - AMS_ABORT_UNLESS(extra_threads >= (ExtraSystemThreads + ExtraApplicationThreads + ExtraAppletThreads)); - - g_extra_threads_available[ResourceLimitGroup_System] += ExtraSystemThreads; - g_extra_threads_available[ResourceLimitGroup_Application] += ExtraApplicationThreads; - g_extra_threads_available[ResourceLimitGroup_Applet] += ExtraAppletThreads; - } - } else { - g_extra_threads_available[ResourceLimitGroup_Application] = extra_threads; - } - } - - /* Choose and initialize memory arrangement. */ - const bool use_dynamic_memory_arrangement = (hos_version >= hos::Version_5_0_0); - if (use_dynamic_memory_arrangement) { - /* 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. */ - s64 total_memory = 0; - R_ABORT_UNLESS(svc::GetResourceLimitLimitValue(&total_memory, GetResourceLimitHandle(ResourceLimitGroup_System), svc::LimitableResource_PhysicalMemoryMax)); - - /* Get and save application + applet memory. */ - R_ABORT_UNLESS(svc::GetSystemInfo(std::addressof(g_memory_resource_limits[spl::MemoryArrangement_Dynamic][ResourceLimitGroup_Application]), svc::SystemInfoType_TotalPhysicalMemorySize, svc::InvalidHandle, svc::PhysicalMemorySystemInfo_Application)); - R_ABORT_UNLESS(svc::GetSystemInfo(std::addressof(g_memory_resource_limits[spl::MemoryArrangement_Dynamic][ResourceLimitGroup_Applet]), svc::SystemInfoType_TotalPhysicalMemorySize, svc::InvalidHandle, svc::PhysicalMemorySystemInfo_Applet)); - - const s64 application_size = g_memory_resource_limits[spl::MemoryArrangement_Dynamic][ResourceLimitGroup_Application]; - const s64 applet_size = g_memory_resource_limits[spl::MemoryArrangement_Dynamic][ResourceLimitGroup_Applet]; - const s64 reserved_non_system_size = (application_size + applet_size + ReservedMemorySize600); - - /* Ensure there's enough memory for the system region. */ - AMS_ABORT_UNLESS(reserved_non_system_size < total_memory); - - g_memory_resource_limits[spl::MemoryArrangement_Dynamic][ResourceLimitGroup_System] = total_memory - reserved_non_system_size; - } else { - /* Older system versions retrieve memory arrangement from spl, and use hardcoded profiles. */ - 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. */ - const size_t extra_memory_size = ExtraSystemMemorySizeAtmosphere; - const auto src_group = hos_version >= hos::Version_3_0_0 ? ResourceLimitGroup_Applet : ResourceLimitGroup_Application; - for (size_t i = 0; i < spl::MemoryArrangement_Count; i++) { - g_memory_resource_limits[i][ResourceLimitGroup_System] += extra_memory_size; - g_memory_resource_limits[i][src_group] -= extra_memory_size; - } - - /* If KTrace is enabled, account for that by subtracting the memory from the applet pool. */ - if (IsKTraceEnabled()) { - constexpr size_t KTraceBufferSize = 16_MB; - for (size_t i = 0; i < spl::MemoryArrangement_Count; i++) { - g_memory_resource_limits[i][ResourceLimitGroup_Applet] -= KTraceBufferSize; - } - } - } - - /* Actually set resource limits. */ - { - std::scoped_lock lk(g_resource_limit_lock); - - for (size_t group = 0; group < ResourceLimitGroup_Count; group++) { - R_ABORT_UNLESS(SetResourceLimitLimitValues(static_cast(group), g_memory_resource_limits[g_memory_arrangement][group])); - } - } - - R_SUCCEED(); - } - - Result BoostSystemMemoryResourceLimit(u64 boost_size) { - /* Ensure only one boost change happens at a time. */ - std::scoped_lock lk(g_system_memory_boost_lock); - - /* Boost to the appropriate total amount. */ - R_RETURN(BoostSystemMemoryResourceLimitLocked(boost_size, g_system_memory_boost_size_for_mitm)); - } - - Result BoostSystemMemoryResourceLimitForMitm(u64 boost_size) { - /* Ensure only one boost change happens at a time. */ - std::scoped_lock lk(g_system_memory_boost_lock); - - /* Boost to the appropriate total amount. */ - R_RETURN(BoostSystemMemoryResourceLimitLocked(g_system_memory_boost_size, boost_size)); - } - - Result BoostApplicationThreadResourceLimit() { - std::scoped_lock lk(g_resource_limit_lock); - - /* Boost the limit. */ - R_TRY(BoostThreadResourceLimitLocked(ResourceLimitGroup_Application)); - - R_SUCCEED(); - } - - Result BoostSystemThreadResourceLimit() { - std::scoped_lock lk(g_resource_limit_lock); - - /* Boost the limits. */ - R_TRY(BoostThreadResourceLimitLocked(ResourceLimitGroup_Applet)); - R_TRY(BoostThreadResourceLimitLocked(ResourceLimitGroup_System)); - - R_SUCCEED(); - } - - os::NativeHandle GetResourceLimitHandle(ResourceLimitGroup group) { - return g_resource_limit_handles[group]; - } - - os::NativeHandle GetResourceLimitHandle(const ldr::ProgramInfo *info) { - return GetResourceLimitHandle(GetResourceLimitGroup(info)); - } - - void WaitResourceAvailable(const ldr::ProgramInfo *info) { - if (GetResourceLimitGroup(info) == ResourceLimitGroup_Application) { - WaitResourceAvailable(ResourceLimitGroup_Application); - if (hos::GetVersion() >= hos::Version_5_0_0) { - WaitApplicationMemoryAvailable(); - } - } - } - - Result GetResourceLimitCurrentValue(ResourceLimitGroup group, pm::ResourceLimitValue *out) { - R_RETURN(GetResourceLimitValuesImpl<::ams::svc::GetResourceLimitCurrentValue>(group, out)); - } - - Result GetResourceLimitPeakValue(ResourceLimitGroup group, pm::ResourceLimitValue *out) { - R_RETURN(GetResourceLimitValuesImpl<::ams::svc::GetResourceLimitPeakValue>(group, out)); - } - - Result GetResourceLimitLimitValue(ResourceLimitGroup group, pm::ResourceLimitValue *out) { - R_RETURN(GetResourceLimitValuesImpl<::ams::svc::GetResourceLimitLimitValue>(group, out)); - } - - Result GetResourceLimitValues(s64 *out_cur, s64 *out_lim, ResourceLimitGroup group, svc::LimitableResource resource) { - /* Do not allow out of bounds access. */ - AMS_ABORT_UNLESS(group < ResourceLimitGroup_Count); - AMS_ABORT_UNLESS(resource < svc::LimitableResource_Count); - - const auto reslimit_hnd = GetResourceLimitHandle(group); - R_TRY(svc::GetResourceLimitCurrentValue(out_cur, reslimit_hnd, resource)); - R_TRY(svc::GetResourceLimitLimitValue(out_lim, reslimit_hnd, resource)); - - R_SUCCEED(); - } - -} diff --git a/stratosphere/pm/source/impl/pm_resource_manager.hpp b/stratosphere/pm/source/impl/pm_resource_manager.hpp deleted file mode 100644 index b6f5605..0000000 --- a/stratosphere/pm/source/impl/pm_resource_manager.hpp +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 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 . - */ -#pragma once -#include - -namespace ams::pm::resource { - - /* Resource API. */ - Result InitializeResourceManager(); - Result BoostSystemMemoryResourceLimit(u64 boost_size); - Result BoostApplicationThreadResourceLimit(); - Result BoostSystemThreadResourceLimit(); - - Result BoostSystemMemoryResourceLimitForMitm(u64 boost_size); - - os::NativeHandle GetResourceLimitHandle(ResourceLimitGroup group); - os::NativeHandle GetResourceLimitHandle(const ldr::ProgramInfo *info); - - void WaitResourceAvailable(const ldr::ProgramInfo *info); - - Result GetResourceLimitCurrentValue(ResourceLimitGroup group, pm::ResourceLimitValue *out); - Result GetResourceLimitPeakValue(ResourceLimitGroup group, pm::ResourceLimitValue *out); - Result GetResourceLimitLimitValue(ResourceLimitGroup group, pm::ResourceLimitValue *out); - - Result GetResourceLimitValues(s64 *out_cur, s64 *out_lim, ResourceLimitGroup group, svc::LimitableResource resource); - -} diff --git a/stratosphere/pm/source/impl/pm_spec.cpp b/stratosphere/pm/source/impl/pm_spec.cpp new file mode 100644 index 0000000..a395b02 --- /dev/null +++ b/stratosphere/pm/source/impl/pm_spec.cpp @@ -0,0 +1,485 @@ +/* + * Copyright (c) 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 . + */ +#include +#include "pm_spec.hpp" + +namespace ams::pm::impl { + + namespace { + + constexpr svc::LimitableResource LimitableResources[] = { + svc::LimitableResource_PhysicalMemoryMax, + svc::LimitableResource_ThreadCountMax, + svc::LimitableResource_EventCountMax, + svc::LimitableResource_TransferMemoryCountMax, + svc::LimitableResource_SessionCountMax, + }; + + /* Definitions for limit differences over time. */ + constexpr size_t ExtraSystemMemorySize400 = 10_MB; + constexpr size_t ReservedMemorySize600 = 5_MB; + + /* Atmosphere always allocates extra memory for system usage. */ + constexpr size_t ExtraSystemMemorySizeAtmosphere = 32_MB; + + /* Desired extra threads. */ + constexpr u64 BaseApplicationThreads = 96; + constexpr u64 BaseAppletThreads = 96; + constexpr u64 BaseSystemThreads = 800 - BaseAppletThreads - BaseApplicationThreads; + + constexpr s64 ExtraSystemThreads = 1024 - BaseSystemThreads; + constexpr s64 ExtraApplicationThreads = 256 - BaseApplicationThreads; + constexpr s64 ExtraAppletThreads = 256 - BaseAppletThreads; + + static_assert(ExtraSystemThreads >= 0); + static_assert(ExtraApplicationThreads >= 0); + static_assert(ExtraAppletThreads >= 0); + + /* Globals. */ + constinit os::SdkMutex g_resource_limit_lock; + constinit os::NativeHandle g_resource_limit_handles[ResourceLimitGroup_Count]; + constinit spl::MemoryArrangement g_memory_arrangement = spl::MemoryArrangement_Standard; + constinit u64 g_extra_threads_available[ResourceLimitGroup_Count]; + + constinit os::SdkMutex g_system_memory_boost_lock; + constinit u64 g_system_memory_boost_size = 0; + constinit u64 g_system_memory_boost_size_for_mitm = 0; + + ALWAYS_INLINE u64 GetCurrentSystemMemoryBoostSize() { + return g_system_memory_boost_size + g_system_memory_boost_size_for_mitm; + } + + constinit u64 g_resource_limits[ResourceLimitGroup_Count][svc::LimitableResource_Count] = { + [ResourceLimitGroup_System] = { + [svc::LimitableResource_PhysicalMemoryMax] = 0, /* Initialized dynamically later. */ + [svc::LimitableResource_ThreadCountMax] = BaseSystemThreads, + [svc::LimitableResource_EventCountMax] = 0, /* Initialized dynamically later. */ + [svc::LimitableResource_TransferMemoryCountMax] = 0, /* Initialized dynamically later. */ + [svc::LimitableResource_SessionCountMax] = 0, /* Initialized dynamically later. */ + }, + [ResourceLimitGroup_Application] = { + [svc::LimitableResource_PhysicalMemoryMax] = 0, /* Initialized dynamically later. */ + [svc::LimitableResource_ThreadCountMax] = BaseApplicationThreads, + [svc::LimitableResource_EventCountMax] = 0, + [svc::LimitableResource_TransferMemoryCountMax] = 32, + [svc::LimitableResource_SessionCountMax] = 1, + }, + [ResourceLimitGroup_Applet] = { + [svc::LimitableResource_PhysicalMemoryMax] = 0, /* Initialized dynamically later. */ + [svc::LimitableResource_ThreadCountMax] = BaseAppletThreads, + [svc::LimitableResource_EventCountMax] = 0, + [svc::LimitableResource_TransferMemoryCountMax] = 32, + [svc::LimitableResource_SessionCountMax] = 5, + }, + }; + + constinit u64 g_memory_resource_limits[spl::MemoryArrangement_Count][ResourceLimitGroup_Count] = { + [spl::MemoryArrangement_Standard] = { + [ResourceLimitGroup_System] = 269_MB, + [ResourceLimitGroup_Application] = 3285_MB, + [ResourceLimitGroup_Applet] = 535_MB, + }, + [spl::MemoryArrangement_StandardForAppletDev] = { + [ResourceLimitGroup_System] = 481_MB, + [ResourceLimitGroup_Application] = 2048_MB, + [ResourceLimitGroup_Applet] = 1560_MB, + }, + [spl::MemoryArrangement_StandardForSystemDev] = { + [ResourceLimitGroup_System] = 328_MB, + [ResourceLimitGroup_Application] = 3285_MB, + [ResourceLimitGroup_Applet] = 476_MB, + }, + [spl::MemoryArrangement_Expanded] = { + [ResourceLimitGroup_System] = 653_MB, + [ResourceLimitGroup_Application] = 4916_MB, + [ResourceLimitGroup_Applet] = 568_MB, + }, + [spl::MemoryArrangement_ExpandedForAppletDev] = { + [ResourceLimitGroup_System] = 653_MB, + [ResourceLimitGroup_Application] = 3285_MB, + [ResourceLimitGroup_Applet] = 2199_MB, + }, + }; + + /* Helpers. */ + Result SetMemoryResourceLimitLimitValue(ResourceLimitGroup group, u64 new_memory_limit) { + const u64 old_memory_limit = g_resource_limits[group][svc::LimitableResource_PhysicalMemoryMax]; + g_resource_limits[group][svc::LimitableResource_PhysicalMemoryMax] = new_memory_limit; + + /* Restore the old memory limit if we fail. */ + ON_RESULT_FAILURE { g_resource_limits[group][svc::LimitableResource_PhysicalMemoryMax] = old_memory_limit; }; + + /* Set the resource limit. */ + R_RETURN(svc::SetResourceLimitLimitValue(GetResourceLimitHandle(group), svc::LimitableResource_PhysicalMemoryMax, g_resource_limits[group][svc::LimitableResource_PhysicalMemoryMax])); + } + + 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 < svc::LimitableResource_Count; i++) { + const auto resource = LimitableResources[i]; + if (resource == svc::LimitableResource_PhysicalMemoryMax) { + continue; + } + R_TRY(svc::SetResourceLimitLimitValue(GetResourceLimitHandle(group), resource, g_resource_limits[group][resource])); + } + + R_SUCCEED(); + } + + 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 auto reslimit_hnd = GetResourceLimitHandle(group); + for (size_t i = 0; i < svc::LimitableResource_Count; i++) { + const auto resource = LimitableResources[i]; + + s64 value = 0; + while (true) { + R_ABORT_UNLESS(svc::GetResourceLimitCurrentValue(&value, reslimit_hnd, resource)); + if (value == 0) { + break; + } + os::SleepThread(TimeSpan::FromMilliSeconds(1)); + } + } + } + + void WaitApplicationMemoryAvailable() { + /* Get firmware version. */ + const auto fw_ver = hos::GetVersion(); + + /* On 15.0.0+, pm considers application memory to be available if there is exactly 96 MB outstanding. */ + /* This is probably because this corresponds to the gameplay-recording memory. */ + constexpr u64 AllowedUsedApplicationMemory = 96_MB; + + /* Wait for memory to be available. */ + u64 value = 0; + while (true) { + R_ABORT_UNLESS(svc::GetSystemInfo(&value, svc::SystemInfoType_UsedPhysicalMemorySize, svc::InvalidHandle, svc::PhysicalMemorySystemInfo_Application)); + if (value == 0 || (fw_ver >= hos::Version_15_0_0 && value == AllowedUsedApplicationMemory)) { + break; + } + os::SleepThread(TimeSpan::FromMilliSeconds(1)); + } + } + + bool IsKTraceEnabled() { + u64 value = 0; + R_ABORT_UNLESS(svc::GetInfo(std::addressof(value), svc::InfoType_MesosphereMeta, svc::InvalidHandle, svc::MesosphereMetaInfo_IsKTraceEnabled)); + + return value != 0; + } + + ALWAYS_INLINE Result BoostThreadResourceLimitLocked(ResourceLimitGroup group) { + AMS_ASSERT(g_resource_limit_lock.IsLockedByCurrentThread()); + + /* Set new limit. */ + const s64 new_thread_count = g_resource_limits[group][svc::LimitableResource_ThreadCountMax] + g_extra_threads_available[group]; + R_TRY(svc::SetResourceLimitLimitValue(GetResourceLimitHandle(group), svc::LimitableResource_ThreadCountMax, new_thread_count)); + + /* Record that we did so. */ + g_resource_limits[group][svc::LimitableResource_ThreadCountMax] = new_thread_count; + g_extra_threads_available[group] = 0; + + R_SUCCEED(); + } + + template + ALWAYS_INLINE Result GetResourceLimitValueImpl(pm::ResourceLimitValue *out, ResourceLimitGroup group) { + /* Sanity check group. */ + AMS_ABORT_UNLESS(group < ResourceLimitGroup_Count); + + /* Get handle. */ + const auto handle = GetResourceLimitHandle(group); + + /* Get values. */ + int64_t values[svc::LimitableResource_Count]; + R_ABORT_UNLESS(ImplFunction(std::addressof(values[svc::LimitableResource_PhysicalMemoryMax]), handle, svc::LimitableResource_PhysicalMemoryMax)); + R_ABORT_UNLESS(ImplFunction(std::addressof(values[svc::LimitableResource_ThreadCountMax]), handle, svc::LimitableResource_ThreadCountMax)); + R_ABORT_UNLESS(ImplFunction(std::addressof(values[svc::LimitableResource_EventCountMax]), handle, svc::LimitableResource_EventCountMax)); + R_ABORT_UNLESS(ImplFunction(std::addressof(values[svc::LimitableResource_TransferMemoryCountMax]), handle, svc::LimitableResource_TransferMemoryCountMax)); + R_ABORT_UNLESS(ImplFunction(std::addressof(values[svc::LimitableResource_SessionCountMax]), handle, svc::LimitableResource_SessionCountMax)); + + /* Set to output. */ + out->physical_memory = values[svc::LimitableResource_PhysicalMemoryMax]; + out->thread_count = values[svc::LimitableResource_ThreadCountMax]; + out->event_count = values[svc::LimitableResource_EventCountMax]; + out->transfer_memory_count = values[svc::LimitableResource_TransferMemoryCountMax]; + out->session_count = values[svc::LimitableResource_SessionCountMax]; + + R_SUCCEED(); + } + + Result BoostSystemMemoryResourceLimitLocked(u64 normal_boost, u64 mitm_boost) { + /* Check pre-conditions. */ + AMS_ASSERT(g_system_memory_boost_lock.IsLockedByCurrentThread()); + + /* Determine total boost. */ + const u64 boost_size = normal_boost + mitm_boost; + + /* Don't allow all application memory to be taken away. */ + R_UNLESS(boost_size <= g_memory_resource_limits[g_memory_arrangement][ResourceLimitGroup_Application], pm::ResultInvalidSize()); + + const u64 new_app_size = g_memory_resource_limits[g_memory_arrangement][ResourceLimitGroup_Application] - boost_size; + { + std::scoped_lock lk(g_resource_limit_lock); + + if (hos::GetVersion() >= hos::Version_5_0_0) { + /* Starting in 5.0.0, PM does not allow for only one of the sets to fail. */ + if (boost_size < GetCurrentSystemMemoryBoostSize()) { + R_TRY(svc::SetUnsafeLimit(boost_size)); + R_ABORT_UNLESS(SetMemoryResourceLimitLimitValue(ResourceLimitGroup_Application, new_app_size)); + } else { + R_TRY(SetMemoryResourceLimitLimitValue(ResourceLimitGroup_Application, new_app_size)); + R_ABORT_UNLESS(svc::SetUnsafeLimit(boost_size)); + } + } else { + const u64 new_sys_size = g_memory_resource_limits[g_memory_arrangement][ResourceLimitGroup_System] + boost_size; + if (boost_size < GetCurrentSystemMemoryBoostSize()) { + 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 = normal_boost; + g_system_memory_boost_size_for_mitm = mitm_boost; + } + + R_SUCCEED(); + } + + } + + Result InitializeSpec() { + /* Create resource limit handles. */ + for (size_t i = 0; i < ResourceLimitGroup_Count; i++) { + if (i == ResourceLimitGroup_System) { + u64 value = 0; + R_ABORT_UNLESS(svc::GetInfo(&value, svc::InfoType_ResourceLimit, svc::InvalidHandle, 0)); + g_resource_limit_handles[i] = static_cast(value); + } else { + R_ABORT_UNLESS(svc::CreateResourceLimit(g_resource_limit_handles + i)); + } + } + + /* Adjust memory limits based on hos firmware version. */ + const auto hos_version = hos::GetVersion(); + if (hos_version >= hos::Version_4_0_0) { + /* 4.0.0 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; + } + + /* Determine system resource counts. */ + { + /* Get the total resource counts. */ + s64 total_events, total_transfer_memories, total_sessions; + R_ABORT_UNLESS(svc::GetResourceLimitLimitValue(std::addressof(total_events), GetResourceLimitHandle(ResourceLimitGroup_System), svc::LimitableResource_EventCountMax)); + R_ABORT_UNLESS(svc::GetResourceLimitLimitValue(std::addressof(total_transfer_memories), GetResourceLimitHandle(ResourceLimitGroup_System), svc::LimitableResource_TransferMemoryCountMax)); + R_ABORT_UNLESS(svc::GetResourceLimitLimitValue(std::addressof(total_sessions), GetResourceLimitHandle(ResourceLimitGroup_System), svc::LimitableResource_SessionCountMax)); + + /* Determine system counts. */ + const s64 sys_events = total_events - (g_resource_limits[ResourceLimitGroup_Application][svc::LimitableResource_EventCountMax] + g_resource_limits[ResourceLimitGroup_Applet][svc::LimitableResource_EventCountMax]); + const s64 sys_transfer_memories = total_transfer_memories - (g_resource_limits[ResourceLimitGroup_Application][svc::LimitableResource_TransferMemoryCountMax] + g_resource_limits[ResourceLimitGroup_Applet][svc::LimitableResource_TransferMemoryCountMax]); + const s64 sys_sessions = total_sessions - (g_resource_limits[ResourceLimitGroup_Application][svc::LimitableResource_SessionCountMax] + g_resource_limits[ResourceLimitGroup_Applet][svc::LimitableResource_SessionCountMax]); + + /* Check system counts. */ + AMS_ABORT_UNLESS(sys_events >= 0); + AMS_ABORT_UNLESS(sys_transfer_memories >= 0); + AMS_ABORT_UNLESS(sys_sessions >= 0); + + /* Set system counts. */ + g_resource_limits[ResourceLimitGroup_System][svc::LimitableResource_EventCountMax] = sys_events; + g_resource_limits[ResourceLimitGroup_System][svc::LimitableResource_TransferMemoryCountMax] = sys_transfer_memories; + g_resource_limits[ResourceLimitGroup_System][svc::LimitableResource_SessionCountMax] = sys_sessions; + } + + /* Determine extra application threads. */ + { + /* Get total threads available. */ + s64 total_threads; + R_ABORT_UNLESS(svc::GetResourceLimitLimitValue(std::addressof(total_threads), GetResourceLimitHandle(ResourceLimitGroup_System), svc::LimitableResource_ThreadCountMax)); + + /* Check that we have enough threads. */ + const s64 required_threads = g_resource_limits[ResourceLimitGroup_System][svc::LimitableResource_ThreadCountMax] + g_resource_limits[ResourceLimitGroup_Application][svc::LimitableResource_ThreadCountMax] + g_resource_limits[ResourceLimitGroup_Applet][svc::LimitableResource_ThreadCountMax]; + AMS_ABORT_UNLESS(total_threads >= required_threads); + + /* Set the number of extra threads. */ + const s64 extra_threads = total_threads - required_threads; + if constexpr (true /* TODO: Should we expose the old "all extra threads are application" behavior? Seems pointless. */) { + if (extra_threads > 0) { + /* If we have any extra threads at all, require that we have enough. */ + AMS_ABORT_UNLESS(extra_threads >= (ExtraSystemThreads + ExtraApplicationThreads + ExtraAppletThreads)); + + g_extra_threads_available[ResourceLimitGroup_System] += ExtraSystemThreads; + g_extra_threads_available[ResourceLimitGroup_Application] += ExtraApplicationThreads; + g_extra_threads_available[ResourceLimitGroup_Applet] += ExtraAppletThreads; + } + } else { + g_extra_threads_available[ResourceLimitGroup_Application] = extra_threads; + } + } + + /* Choose and initialize memory arrangement. */ + const bool use_dynamic_memory_arrangement = (hos_version >= hos::Version_5_0_0); + if (use_dynamic_memory_arrangement) { + /* 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. */ + s64 total_memory = 0; + R_ABORT_UNLESS(svc::GetResourceLimitLimitValue(&total_memory, GetResourceLimitHandle(ResourceLimitGroup_System), svc::LimitableResource_PhysicalMemoryMax)); + + /* Get and save application + applet memory. */ + R_ABORT_UNLESS(svc::GetSystemInfo(std::addressof(g_memory_resource_limits[spl::MemoryArrangement_Dynamic][ResourceLimitGroup_Application]), svc::SystemInfoType_TotalPhysicalMemorySize, svc::InvalidHandle, svc::PhysicalMemorySystemInfo_Application)); + R_ABORT_UNLESS(svc::GetSystemInfo(std::addressof(g_memory_resource_limits[spl::MemoryArrangement_Dynamic][ResourceLimitGroup_Applet]), svc::SystemInfoType_TotalPhysicalMemorySize, svc::InvalidHandle, svc::PhysicalMemorySystemInfo_Applet)); + + const s64 application_size = g_memory_resource_limits[spl::MemoryArrangement_Dynamic][ResourceLimitGroup_Application]; + const s64 applet_size = g_memory_resource_limits[spl::MemoryArrangement_Dynamic][ResourceLimitGroup_Applet]; + const s64 reserved_non_system_size = (application_size + applet_size + ReservedMemorySize600); + + /* Ensure there's enough memory for the system region. */ + AMS_ABORT_UNLESS(reserved_non_system_size < total_memory); + + g_memory_resource_limits[spl::MemoryArrangement_Dynamic][ResourceLimitGroup_System] = total_memory - reserved_non_system_size; + } else { + /* Older system versions retrieve memory arrangement from spl, and use hardcoded profiles. */ + 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. */ + const size_t extra_memory_size = ExtraSystemMemorySizeAtmosphere; + const auto src_group = hos_version >= hos::Version_3_0_0 ? ResourceLimitGroup_Applet : ResourceLimitGroup_Application; + for (size_t i = 0; i < spl::MemoryArrangement_Count; i++) { + g_memory_resource_limits[i][ResourceLimitGroup_System] += extra_memory_size; + g_memory_resource_limits[i][src_group] -= extra_memory_size; + } + + /* If KTrace is enabled, account for that by subtracting the memory from the applet pool. */ + if (IsKTraceEnabled()) { + constexpr size_t KTraceBufferSize = 16_MB; + for (size_t i = 0; i < spl::MemoryArrangement_Count; i++) { + g_memory_resource_limits[i][ResourceLimitGroup_Applet] -= KTraceBufferSize; + } + } + } + + /* Actually set resource limits. */ + { + std::scoped_lock lk(g_resource_limit_lock); + + for (size_t group = 0; group < ResourceLimitGroup_Count; group++) { + R_ABORT_UNLESS(SetResourceLimitLimitValues(static_cast(group), g_memory_resource_limits[g_memory_arrangement][group])); + } + } + + R_SUCCEED(); + } + + Result BoostSystemMemoryResourceLimit(u64 boost_size) { + /* Ensure only one boost change happens at a time. */ + std::scoped_lock lk(g_system_memory_boost_lock); + + /* Boost to the appropriate total amount. */ + R_RETURN(BoostSystemMemoryResourceLimitLocked(boost_size, g_system_memory_boost_size_for_mitm)); + } + + Result BoostSystemMemoryResourceLimitForMitm(u64 boost_size) { + /* Ensure only one boost change happens at a time. */ + std::scoped_lock lk(g_system_memory_boost_lock); + + /* Boost to the appropriate total amount. */ + R_RETURN(BoostSystemMemoryResourceLimitLocked(g_system_memory_boost_size, boost_size)); + } + + Result BoostApplicationThreadResourceLimit() { + std::scoped_lock lk(g_resource_limit_lock); + + /* Boost the limit. */ + R_TRY(BoostThreadResourceLimitLocked(ResourceLimitGroup_Application)); + + R_SUCCEED(); + } + + Result BoostSystemThreadResourceLimit() { + std::scoped_lock lk(g_resource_limit_lock); + + /* Boost the limits. */ + R_TRY(BoostThreadResourceLimitLocked(ResourceLimitGroup_Applet)); + R_TRY(BoostThreadResourceLimitLocked(ResourceLimitGroup_System)); + + R_SUCCEED(); + } + + os::NativeHandle GetResourceLimitHandle(ResourceLimitGroup group) { + return g_resource_limit_handles[group]; + } + + os::NativeHandle GetResourceLimitHandle(const ldr::ProgramInfo *info) { + return GetResourceLimitHandle(GetResourceLimitGroup(info)); + } + + void WaitResourceAvailable(const ldr::ProgramInfo *info) { + if (GetResourceLimitGroup(info) == ResourceLimitGroup_Application) { + WaitResourceAvailable(ResourceLimitGroup_Application); + if (hos::GetVersion() >= hos::Version_5_0_0) { + WaitApplicationMemoryAvailable(); + } + } + } + + Result GetResourceLimitCurrentValue(pm::ResourceLimitValue *out, ResourceLimitGroup group) { + R_RETURN(GetResourceLimitValueImpl<::ams::svc::GetResourceLimitCurrentValue>(out, group)); + } + + Result GetResourceLimitPeakValue(pm::ResourceLimitValue *out, ResourceLimitGroup group) { + R_RETURN(GetResourceLimitValueImpl<::ams::svc::GetResourceLimitPeakValue>(out, group)); + } + + Result GetResourceLimitLimitValue(pm::ResourceLimitValue *out, ResourceLimitGroup group) { + R_RETURN(GetResourceLimitValueImpl<::ams::svc::GetResourceLimitLimitValue>(out, group)); + } + + Result GetResourceLimitValues(s64 *out_cur, s64 *out_lim, ResourceLimitGroup group, svc::LimitableResource resource) { + /* Do not allow out of bounds access. */ + AMS_ABORT_UNLESS(group < ResourceLimitGroup_Count); + AMS_ABORT_UNLESS(resource < svc::LimitableResource_Count); + + const auto reslimit_hnd = GetResourceLimitHandle(group); + R_TRY(svc::GetResourceLimitCurrentValue(out_cur, reslimit_hnd, resource)); + R_TRY(svc::GetResourceLimitLimitValue(out_lim, reslimit_hnd, resource)); + + R_SUCCEED(); + } + +} diff --git a/stratosphere/pm/source/impl/pm_spec.hpp b/stratosphere/pm/source/impl/pm_spec.hpp new file mode 100644 index 0000000..1f49282 --- /dev/null +++ b/stratosphere/pm/source/impl/pm_spec.hpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) 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 . + */ +#pragma once +#include + +namespace ams::pm::impl { + + Result InitializeSpec(); + + Result BoostSystemMemoryResourceLimit(u64 boost_size); + Result BoostApplicationThreadResourceLimit(); + Result BoostSystemThreadResourceLimit(); + + Result BoostSystemMemoryResourceLimitForMitm(u64 boost_size); + + os::NativeHandle GetResourceLimitHandle(ResourceLimitGroup group); + os::NativeHandle GetResourceLimitHandle(const ldr::ProgramInfo *info); + + void WaitResourceAvailable(const ldr::ProgramInfo *info); + + Result GetResourceLimitCurrentValue(pm::ResourceLimitValue *out, ResourceLimitGroup group); + Result GetResourceLimitPeakValue(pm::ResourceLimitValue *outm, ResourceLimitGroup group); + Result GetResourceLimitLimitValue(pm::ResourceLimitValue *out, ResourceLimitGroup group); + + Result GetResourceLimitValues(s64 *out_cur, s64 *out_lim, ResourceLimitGroup group, svc::LimitableResource resource); + +} diff --git a/stratosphere/pm/source/pm_boot_mode_service.cpp b/stratosphere/pm/source/pm_boot_mode_service.cpp index 719fff5..ff389fe 100644 --- a/stratosphere/pm/source/pm_boot_mode_service.cpp +++ b/stratosphere/pm/source/pm_boot_mode_service.cpp @@ -21,7 +21,7 @@ namespace { /* Global bootmode. */ - BootMode g_boot_mode = BootMode::Normal; + constinit BootMode g_boot_mode = BootMode::Normal; } diff --git a/stratosphere/pm/source/pm_debug_monitor_service.cpp b/stratosphere/pm/source/pm_debug_monitor_service.cpp index dd5433c..d9c3b16 100644 --- a/stratosphere/pm/source/pm_debug_monitor_service.cpp +++ b/stratosphere/pm/source/pm_debug_monitor_service.cpp @@ -16,6 +16,7 @@ #include #include "pm_debug_monitor_service.hpp" #include "impl/pm_process_manager.hpp" +#include "impl/pm_spec.hpp" namespace ams::pm { @@ -76,7 +77,7 @@ } Result DebugMonitorService::AtmosphereGetCurrentLimitInfo(sf::Out out_cur_val, sf::Out out_lim_val, u32 group, u32 resource) { - R_RETURN(impl::AtmosphereGetCurrentLimitInfo(out_cur_val.GetPointer(), out_lim_val.GetPointer(), group, resource)); + R_RETURN(impl::GetResourceLimitValues(out_cur_val.GetPointer(), out_lim_val.GetPointer(), static_cast(group), static_cast(resource))); } } diff --git a/stratosphere/pm/source/pm_info_service.cpp b/stratosphere/pm/source/pm_info_service.cpp index 55b308f..29ae868 100644 --- a/stratosphere/pm/source/pm_info_service.cpp +++ b/stratosphere/pm/source/pm_info_service.cpp @@ -16,6 +16,7 @@ #include #include "pm_info_service.hpp" #include "impl/pm_process_manager.hpp" +#include "impl/pm_spec.hpp" namespace ams::pm { @@ -34,11 +35,11 @@ } Result InformationService::GetAppletResourceLimitCurrentValue(sf::Out out) { - R_RETURN(impl::GetAppletResourceLimitCurrentValue(out.GetPointer())); + R_RETURN(impl::GetResourceLimitCurrentValue(out.GetPointer(), ResourceLimitGroup_Applet)); } Result InformationService::GetAppletResourceLimitPeakValue(sf::Out out) { - R_RETURN(impl::GetAppletResourceLimitPeakValue(out.GetPointer())); + R_RETURN(impl::GetResourceLimitPeakValue(out.GetPointer(), ResourceLimitGroup_Applet)); } /* Atmosphere extension commands. */ diff --git a/stratosphere/pm/source/pm_shell_service.cpp b/stratosphere/pm/source/pm_shell_service.cpp index 7248bc0..db69c6f 100644 --- a/stratosphere/pm/source/pm_shell_service.cpp +++ b/stratosphere/pm/source/pm_shell_service.cpp @@ -16,6 +16,7 @@ #include #include "pm_shell_service.hpp" #include "impl/pm_process_manager.hpp" +#include "impl/pm_spec.hpp" namespace ams::pm {