From 7d239df065dc344c29d8c277835db9fef81e37d9 Mon Sep 17 00:00:00 2001 From: lizzie Date: Sun, 16 Nov 2025 22:15:09 +0100 Subject: [PATCH] [hle, kernel] Add support for FW21 and kernel changes (#3004) - Adapts kernel changes from atmosphere for firmware 21.0.0. - Fixes launch error of firmware 21.0.0 applets. - Adds new commands for `prepo` (New `SaveSystemReport` & `SaveSystemReportWithUser`). - Adds new commands for `IReadOnlyApplicationControlDataInterface` (cmd 19; incomplete!) - Adds `{12010, nullptr, "SetButtonConfigLeft"},` undocumented IHidServer. - Adds new commands for `ngc:u` (`Mask2` and `Check2`) - Adds new commands for system settings server (GetHttpAuthConfig) for webapplet - Removes incompatible firmware popup warning. Signed-off-by: lizzie lizzie@eden-emu.dev Co-authored by: maufeat sahyno1996@gmail.com Co-authored-by: crueter Co-authored-by: JPikachu Co-authored-by: Maufeat Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3004 Reviewed-by: Maufeat Reviewed-by: crueter Reviewed-by: MaranBr Co-authored-by: lizzie Co-committed-by: lizzie --- src/core/file_sys/savedata_factory.cpp | 1 + src/core/hle/api_version.h | 12 +- src/core/hle/kernel/k_auto_object.cpp | 4 + src/core/hle/kernel/k_auto_object.h | 10 +- src/core/hle/kernel/k_condition_variable.cpp | 25 +- src/core/hle/kernel/k_handle_table.cpp | 7 +- src/core/hle/kernel/k_handle_table.h | 2 +- src/core/hle/kernel/k_scheduler.cpp | 6 + src/core/hle/kernel/k_thread.cpp | 2156 +++++++++-------- src/core/hle/kernel/k_thread.h | 25 +- src/core/hle/kernel/svc/svc_event.cpp | 9 +- .../am/service/application_functions.cpp | 5 + .../service/am/service/audio_controller.cpp | 4 + .../hle/service/audio/audio_controller.cpp | 4 + src/core/hle/service/audio/audio_device.cpp | 6 +- .../hle/service/filesystem/fsp/fsp_srv.cpp | 37 +- src/core/hle/service/hid/hid_debug_server.cpp | 9 + src/core/hle/service/hid/hid_server.cpp | 51 + .../hle/service/hid/hid_system_server.cpp | 11 + src/core/hle/service/ngc/ngc.cpp | 5 + src/core/hle/service/nifm/nifm.cpp | 1 + .../ns/application_manager_interface.cpp | 4 +- ...nly_application_control_data_interface.cpp | 24 +- ..._only_application_control_data_interface.h | 2 +- .../hle/service/nvdrv/devices/nvhost_gpu.cpp | 39 +- .../olsc/remote_storage_controller.cpp | 13 +- src/core/hle/service/prepo/prepo.cpp | 74 +- .../set/firmware_debug_settings_server.cpp | 5 + .../service/set/system_settings_server.cpp | 19 + .../hle/service/set/system_settings_server.h | 4 + src/frontend_common/firmware_manager.cpp | 6 - src/frontend_common/firmware_manager.h | 21 - src/qt_common/qt_string_lookup.h | 46 +- src/yuzu/data_dialog.cpp | 32 +- src/yuzu/main_window.cpp | 28 +- 35 files changed, 1484 insertions(+), 1223 deletions(-) diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp index dda8d526d3..4dfb0ded51 100644 --- a/src/core/file_sys/savedata_factory.cpp +++ b/src/core/file_sys/savedata_factory.cpp @@ -95,6 +95,7 @@ std::string SaveDataFactory::GetSaveDataSpaceIdPath(SaveDataSpaceId space) { case SaveDataSpaceId::System: return "/system/"; case SaveDataSpaceId::User: + case SaveDataSpaceId::SdUser: return "/user/"; case SaveDataSpaceId::Temporary: return "/temp/"; diff --git a/src/core/hle/api_version.h b/src/core/hle/api_version.h index e18397cc04..0317fa1c9f 100644 --- a/src/core/hle/api_version.h +++ b/src/core/hle/api_version.h @@ -14,9 +14,9 @@ namespace HLE::ApiVersion { // Horizon OS version constants. -constexpr u8 HOS_VERSION_MAJOR = 20; -constexpr u8 HOS_VERSION_MINOR = 1; -constexpr u8 HOS_VERSION_MICRO = 1; +constexpr u8 HOS_VERSION_MAJOR = 21; +constexpr u8 HOS_VERSION_MINOR = 0; +constexpr u8 HOS_VERSION_MICRO = 0; // NintendoSDK version constants. @@ -24,9 +24,9 @@ constexpr u8 SDK_REVISION_MAJOR = 1; constexpr u8 SDK_REVISION_MINOR = 0; constexpr char PLATFORM_STRING[] = "NX"; -constexpr char VERSION_HASH[] = "9ffad64d79dd150490201461bdf66c8db963f57d"; -constexpr char DISPLAY_VERSION[] = "20.1.1"; -constexpr char DISPLAY_TITLE[] = "NintendoSDK Firmware for NX 20.1.1-1.0"; +constexpr char VERSION_HASH[] = "f6b2425b6888a66590db104fc734891696e0ecb3"; +constexpr char DISPLAY_VERSION[] = "21.0.0"; +constexpr char DISPLAY_TITLE[] = "NintendoSDK Firmware for NX 21.0.0-1.0"; // Atmosphere version constants. diff --git a/src/core/hle/kernel/k_auto_object.cpp b/src/core/hle/kernel/k_auto_object.cpp index 9cd7a9fd51..a7a46d29bb 100644 --- a/src/core/hle/kernel/k_auto_object.cpp +++ b/src/core/hle/kernel/k_auto_object.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -8,6 +11,7 @@ namespace Kernel { KAutoObject* KAutoObject::Create(KAutoObject* obj) { obj->m_ref_count = 1; + obj->m_class_token = obj->GetTypeObj().GetClassToken(); return obj; } diff --git a/src/core/hle/kernel/k_auto_object.h b/src/core/hle/kernel/k_auto_object.h index c61b10625a..5e8c0030ce 100644 --- a/src/core/hle/kernel/k_auto_object.h +++ b/src/core/hle/kernel/k_auto_object.h @@ -74,6 +74,10 @@ protected: return (this->GetClassToken() | rhs.GetClassToken()) == this->GetClassToken(); } + static constexpr bool IsClassTokenDerivedFrom(ClassTokenType self, ClassTokenType base) { + return (self | base) == self; + } + private: const char* m_name; ClassTokenType m_class_token; @@ -84,6 +88,7 @@ private: public: explicit KAutoObject(KernelCore& kernel) : m_kernel(kernel) { + m_class_token = GetStaticTypeObj().GetClassToken(); RegisterWithKernel(); } virtual ~KAutoObject() = default; @@ -107,11 +112,11 @@ public: } bool IsDerivedFrom(const TypeObj& rhs) const { - return this->GetTypeObj().IsDerivedFrom(rhs); + return TypeObj::IsClassTokenDerivedFrom(m_class_token, rhs.GetClassToken()); } bool IsDerivedFrom(const KAutoObject& rhs) const { - return this->IsDerivedFrom(rhs.GetTypeObj()); + return TypeObj::IsClassTokenDerivedFrom(m_class_token, rhs.m_class_token); } template @@ -180,6 +185,7 @@ protected: private: std::atomic m_ref_count{}; + ClassTokenType m_class_token{}; }; class KAutoObjectWithListContainer; diff --git a/src/core/hle/kernel/k_condition_variable.cpp b/src/core/hle/kernel/k_condition_variable.cpp index 94ea3527ab..1a7431df07 100644 --- a/src/core/hle/kernel/k_condition_variable.cpp +++ b/src/core/hle/kernel/k_condition_variable.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -23,8 +26,8 @@ bool ReadFromUser(KernelCore& kernel, u32* out, KProcessAddress address) { return true; } -bool WriteToUser(KernelCore& kernel, KProcessAddress address, const u32* p) { - GetCurrentMemory(kernel).Write32(GetInteger(address), *p); +bool WriteToUser(KernelCore& kernel, KProcessAddress address, u32 val) { + GetCurrentMemory(kernel).Write32(GetInteger(address), val); return true; } @@ -133,7 +136,7 @@ Result KConditionVariable::SignalToAddress(KernelCore& kernel, KProcessAddress a // Write the value to userspace. Result result{ResultSuccess}; - if (WriteToUser(kernel, addr, std::addressof(next_value))) [[likely]] { + if (WriteToUser(kernel, addr, next_value)) { result = ResultSuccess; } else { result = ResultInvalidCurrentMemory; @@ -207,13 +210,13 @@ void KConditionVariable::SignalImpl(KThread* thread) { // TODO(bunnei): We should call CanAccessAtomic(..) here. can_access = true; - if (can_access) [[likely]] { + if (can_access) { UpdateLockAtomic(m_kernel, std::addressof(prev_tag), address, own_tag, Svc::HandleWaitMask); } } - if (can_access) [[likely]] { + if (can_access) { if (prev_tag == Svc::InvalidHandle) { // If nobody held the lock previously, we're all good. thread->EndWait(ResultSuccess); @@ -225,7 +228,7 @@ void KConditionVariable::SignalImpl(KThread* thread) { static_cast(prev_tag & ~Svc::HandleWaitMask)) .ReleasePointerUnsafe(); - if (owner_thread) [[likely]] { + if (owner_thread) { // Add the thread as a waiter on the owner. owner_thread->AddWaiter(thread); owner_thread->Close(); @@ -261,8 +264,8 @@ void KConditionVariable::Signal(u64 cv_key, s32 count) { // If we have no waiters, clear the has waiter flag. if (it == m_tree.end() || it->GetConditionVariableKey() != cv_key) { - const u32 has_waiter_flag{}; - WriteToUser(m_kernel, cv_key, std::addressof(has_waiter_flag)); + constexpr u32 HasNoWaiterFlag = 0; + WriteToUser(m_kernel, cv_key, HasNoWaiterFlag); } } } @@ -305,13 +308,13 @@ Result KConditionVariable::Wait(KProcessAddress addr, u64 key, u32 value, s64 ti // Write to the cv key. { - const u32 has_waiter_flag = 1; - WriteToUser(m_kernel, key, std::addressof(has_waiter_flag)); + constexpr u32 HasWaiterFlag = 1; + WriteToUser(m_kernel, key, HasWaiterFlag); std::atomic_thread_fence(std::memory_order_seq_cst); } // Write the value to userspace. - if (!WriteToUser(m_kernel, addr, std::addressof(next_value))) { + if (!WriteToUser(m_kernel, addr, next_value)) { slp.CancelSleep(); R_THROW(ResultInvalidCurrentMemory); } diff --git a/src/core/hle/kernel/k_handle_table.cpp b/src/core/hle/kernel/k_handle_table.cpp index 3535ddc0ca..04ca235344 100644 --- a/src/core/hle/kernel/k_handle_table.cpp +++ b/src/core/hle/kernel/k_handle_table.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -6,7 +9,7 @@ namespace Kernel { -Result KHandleTable::Finalize() { +void KHandleTable::Finalize() { // Get the table and clear our record of it. u16 saved_table_size = 0; { @@ -22,8 +25,6 @@ Result KHandleTable::Finalize() { obj->Close(); } } - - R_SUCCEED(); } bool KHandleTable::Remove(Handle handle) { diff --git a/src/core/hle/kernel/k_handle_table.h b/src/core/hle/kernel/k_handle_table.h index 7381e951f5..731a5284dc 100644 --- a/src/core/hle/kernel/k_handle_table.h +++ b/src/core/hle/kernel/k_handle_table.h @@ -68,7 +68,7 @@ public: return m_max_count; } - Result Finalize(); + void Finalize(); bool Remove(Handle handle); template diff --git a/src/core/hle/kernel/k_scheduler.cpp b/src/core/hle/kernel/k_scheduler.cpp index 27d1c38466..676e19190a 100644 --- a/src/core/hle/kernel/k_scheduler.cpp +++ b/src/core/hle/kernel/k_scheduler.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -381,6 +384,9 @@ void KScheduler::SwitchThread(KThread* next_thread) { // Set the new Thread Local region. // cpu::SwitchThreadLocalRegion(GetInteger(next_thread->GetThreadLocalRegionAddress())); + + // Update the thread's cpu time differential in TLS, if relevant. + next_thread->UpdateTlsThreadCpuTime(cur_tick); } void KScheduler::ScheduleImpl() { diff --git a/src/core/hle/kernel/k_thread.cpp b/src/core/hle/kernel/k_thread.cpp index 6b68a56ab4..bfc8691e95 100644 --- a/src/core/hle/kernel/k_thread.cpp +++ b/src/core/hle/kernel/k_thread.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include "common/assert.h" #include "common/bit_util.h" @@ -67,1385 +68,1402 @@ static void ResetThreadContext64(Kernel::Svc::ThreadContext& ctx, u64 stack_top, } // namespace namespace Kernel { + namespace { + + struct ThreadLocalRegion { + static constexpr std::size_t MessageBufferSize = 0x100; + std::array message_buffer; + std::atomic_uint16_t disable_count; + std::atomic_uint16_t interrupt_flag; + std::atomic_uint8_t cache_maintenance_flag; + std::atomic_int64_t thread_cpu_time; + }; + static_assert(offsetof(ThreadLocalRegion, disable_count) == 0x100); + static_assert(offsetof(ThreadLocalRegion, interrupt_flag) == 0x102); + static_assert(offsetof(ThreadLocalRegion, cache_maintenance_flag) == 0x104); + static_assert(offsetof(ThreadLocalRegion, thread_cpu_time) == 0x108); + + class ThreadQueueImplForKThreadSleep final : public KThreadQueueWithoutEndWait { + public: + explicit ThreadQueueImplForKThreadSleep(KernelCore& kernel) + : KThreadQueueWithoutEndWait(kernel) {} + }; + + class ThreadQueueImplForKThreadSetProperty final : public KThreadQueue { + public: + explicit ThreadQueueImplForKThreadSetProperty(KernelCore& kernel, KThread::WaiterList* wl) + : KThreadQueue(kernel), m_wait_list(wl) {} + + void CancelWait(KThread* waiting_thread, Result wait_result, bool cancel_timer_task) override { + // Remove the thread from the wait list. + m_wait_list->erase(m_wait_list->iterator_to(*waiting_thread)); + + // Invoke the base cancel wait handler. + KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task); + } -namespace { + private: + KThread::WaiterList* m_wait_list{}; + }; + + } // namespace + + KThread::KThread(KernelCore& kernel) + : KAutoObjectWithSlabHeapAndContainer{kernel}, m_activity_pause_lock{kernel} {} + KThread::~KThread() = default; + + Result KThread::Initialize(KThreadFunction func, uintptr_t arg, KProcessAddress user_stack_top, + s32 prio, s32 virt_core, KProcess* owner, ThreadType type) { + // Assert parameters are valid. + ASSERT((type == ThreadType::Main) || (type == ThreadType::Dummy) || + (Svc::HighestThreadPriority <= prio && prio <= Svc::LowestThreadPriority)); + ASSERT((owner != nullptr) || (type != ThreadType::User)); + ASSERT(0 <= virt_core && virt_core < static_cast(Common::BitSize())); + + // Convert the virtual core to a physical core. + const s32 phys_core = Core::Hardware::VirtualToPhysicalCoreMap[virt_core]; + ASSERT(0 <= phys_core && phys_core < static_cast(Core::Hardware::NUM_CPU_CORES)); + + // First, clear the TLS address. + m_tls_address = {}; + + // Next, assert things based on the type. + switch (type) { + case ThreadType::Main: + ASSERT(arg == 0); + [[fallthrough]]; + case ThreadType::User: + ASSERT(((owner == nullptr) || + (owner->GetCoreMask() | (1ULL << virt_core)) == owner->GetCoreMask())); + ASSERT(((owner == nullptr) || (prio > Svc::LowestThreadPriority) || + (owner->GetPriorityMask() | (1ULL << prio)) == owner->GetPriorityMask())); + break; + case ThreadType::HighPriority: + case ThreadType::Dummy: + break; + case ThreadType::Kernel: + UNIMPLEMENTED(); + break; + default: + ASSERT_MSG(false, "KThread::Initialize: Unknown ThreadType {}", static_cast(type)); + break; + } + m_thread_type = type; -struct ThreadLocalRegion { - static constexpr std::size_t MessageBufferSize = 0x100; - std::array message_buffer; - std::atomic_uint16_t disable_count; - std::atomic_uint16_t interrupt_flag; -}; + // Set the ideal core ID and affinity mask. + m_virtual_ideal_core_id = virt_core; + m_physical_ideal_core_id = phys_core; + m_virtual_affinity_mask = 1ULL << virt_core; + m_physical_affinity_mask.SetAffinity(phys_core, true); -class ThreadQueueImplForKThreadSleep final : public KThreadQueueWithoutEndWait { -public: - explicit ThreadQueueImplForKThreadSleep(KernelCore& kernel) - : KThreadQueueWithoutEndWait(kernel) {} -}; + // Set the thread state. + m_thread_state = (type == ThreadType::Main || type == ThreadType::Dummy) + ? ThreadState::Runnable + : ThreadState::Initialized; -class ThreadQueueImplForKThreadSetProperty final : public KThreadQueue { -public: - explicit ThreadQueueImplForKThreadSetProperty(KernelCore& kernel, KThread::WaiterList* wl) - : KThreadQueue(kernel), m_wait_list(wl) {} + // Set TLS address. + m_tls_address = 0; - void CancelWait(KThread* waiting_thread, Result wait_result, bool cancel_timer_task) override { - // Remove the thread from the wait list. - m_wait_list->erase(m_wait_list->iterator_to(*waiting_thread)); + // Set parent and condvar tree. + m_parent = nullptr; + m_condvar_tree = nullptr; - // Invoke the base cancel wait handler. - KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task); - } + // Set sync booleans. + m_signaled = false; + m_termination_requested = false; + m_wait_cancelled = false; + m_cancellable = false; -private: - KThread::WaiterList* m_wait_list{}; -}; + // Set core ID and wait result. + m_core_id = phys_core; + m_wait_result = ResultNoSynchronizationObject; -} // namespace + // Set priorities. + m_priority = prio; + m_base_priority = prio; -KThread::KThread(KernelCore& kernel) - : KAutoObjectWithSlabHeapAndContainer{kernel}, m_activity_pause_lock{kernel} {} -KThread::~KThread() = default; - -Result KThread::Initialize(KThreadFunction func, uintptr_t arg, KProcessAddress user_stack_top, - s32 prio, s32 virt_core, KProcess* owner, ThreadType type) { - // Assert parameters are valid. - ASSERT((type == ThreadType::Main) || (type == ThreadType::Dummy) || - (Svc::HighestThreadPriority <= prio && prio <= Svc::LowestThreadPriority)); - ASSERT((owner != nullptr) || (type != ThreadType::User)); - ASSERT(0 <= virt_core && virt_core < static_cast(Common::BitSize())); - - // Convert the virtual core to a physical core. - const s32 phys_core = Core::Hardware::VirtualToPhysicalCoreMap[virt_core]; - ASSERT(0 <= phys_core && phys_core < static_cast(Core::Hardware::NUM_CPU_CORES)); - - // First, clear the TLS address. - m_tls_address = {}; - - // Next, assert things based on the type. - switch (type) { - case ThreadType::Main: - ASSERT(arg == 0); - [[fallthrough]]; - case ThreadType::User: - ASSERT(((owner == nullptr) || - (owner->GetCoreMask() | (1ULL << virt_core)) == owner->GetCoreMask())); - ASSERT(((owner == nullptr) || (prio > Svc::LowestThreadPriority) || - (owner->GetPriorityMask() | (1ULL << prio)) == owner->GetPriorityMask())); - break; - case ThreadType::HighPriority: - case ThreadType::Dummy: - break; - case ThreadType::Kernel: - UNIMPLEMENTED(); - break; - default: - ASSERT_MSG(false, "KThread::Initialize: Unknown ThreadType {}", static_cast(type)); - break; - } - m_thread_type = type; - - // Set the ideal core ID and affinity mask. - m_virtual_ideal_core_id = virt_core; - m_physical_ideal_core_id = phys_core; - m_virtual_affinity_mask = 1ULL << virt_core; - m_physical_affinity_mask.SetAffinity(phys_core, true); - - // Set the thread state. - m_thread_state = (type == ThreadType::Main || type == ThreadType::Dummy) - ? ThreadState::Runnable - : ThreadState::Initialized; - - // Set TLS address. - m_tls_address = 0; - - // Set parent and condvar tree. - m_parent = nullptr; - m_condvar_tree = nullptr; - - // Set sync booleans. - m_signaled = false; - m_termination_requested = false; - m_wait_cancelled = false; - m_cancellable = false; - - // Set core ID and wait result. - m_core_id = phys_core; - m_wait_result = ResultNoSynchronizationObject; - - // Set priorities. - m_priority = prio; - m_base_priority = prio; - - // Initialize sleeping queue. - m_wait_queue = nullptr; - - // Set suspend flags. - m_suspend_request_flags = 0; - m_suspend_allowed_flags = static_cast(ThreadState::SuspendFlagMask); - - // We're neither debug attached, nor are we nesting our priority inheritance. - m_debug_attached = false; - m_priority_inheritance_count = 0; - - // We haven't been scheduled, and we have done no light IPC. - m_schedule_count = -1; - m_last_scheduled_tick = 0; - m_light_ipc_data = nullptr; - - // We're not waiting for a lock, and we haven't disabled migration. - m_waiting_lock_info = nullptr; - m_num_core_migration_disables = 0; - - // We have no waiters, but we do have an entrypoint. - m_num_kernel_waiters = 0; - - // Set our current core id. - m_current_core_id = phys_core; - - // We haven't released our resource limit hint, and we've spent no time on the cpu. - m_resource_limit_release_hint = false; - m_cpu_time = 0; - - // Set debug context. - m_stack_top = user_stack_top; - m_argument = arg; - - // Clear our stack parameters. - std::memset(static_cast(std::addressof(this->GetStackParameters())), 0, - sizeof(StackParameters)); - - // Set parent, if relevant. - if (owner != nullptr) { - // Setup the TLS, if needed. - if (type == ThreadType::User) { - R_TRY(owner->CreateThreadLocalRegion(std::addressof(m_tls_address))); - owner->GetMemory().ZeroBlock(m_tls_address, Svc::ThreadLocalRegionSize); - } + // Initialize sleeping queue. + m_wait_queue = nullptr; - m_parent = owner; - m_parent->Open(); - } + // Set suspend flags. + m_suspend_request_flags = 0; + m_suspend_allowed_flags = static_cast(ThreadState::SuspendFlagMask); - // Initialize thread context. - if (m_parent != nullptr && !m_parent->Is64Bit()) { - ResetThreadContext32(m_thread_context, GetInteger(user_stack_top), GetInteger(func), arg); - } else { - ResetThreadContext64(m_thread_context, GetInteger(user_stack_top), GetInteger(func), arg); - } + // We're neither debug attached, nor are we nesting our priority inheritance. + m_debug_attached = false; + m_priority_inheritance_count = 0; - // Setup the stack parameters. - StackParameters& sp = this->GetStackParameters(); - sp.cur_thread = this; - sp.disable_count = 1; - this->SetInExceptionHandler(); + // We haven't been scheduled, and we have done no light IPC. + m_schedule_count = -1; + m_last_scheduled_tick = 0; + m_light_ipc_data = nullptr; - // Set thread ID. - m_thread_id = m_kernel.CreateNewThreadID(); + // We're not waiting for a lock, and we haven't disabled migration. + m_waiting_lock_info = nullptr; + m_num_core_migration_disables = 0; - // We initialized! - m_initialized = true; + // We have no waiters, but we do have an entrypoint. + m_num_kernel_waiters = 0; - // Register ourselves with our parent process. - if (m_parent != nullptr) { - m_parent->RegisterThread(this); - if (m_parent->IsSuspended()) { - RequestSuspend(SuspendType::Process); - } - } + // Set our current core id. + m_current_core_id = phys_core; - R_SUCCEED(); -} + // We haven't released our resource limit hint, and we've spent no time on the cpu. + m_resource_limit_release_hint = false; + m_cpu_time = 0; -Result KThread::InitializeThread(KThread* thread, KThreadFunction func, uintptr_t arg, - KProcessAddress user_stack_top, s32 prio, s32 core, - KProcess* owner, ThreadType type, - std::function&& init_func) { - // Initialize the thread. - R_TRY(thread->Initialize(func, arg, user_stack_top, prio, core, owner, type)); + // Set debug context. + m_stack_top = user_stack_top; + m_argument = arg; - // Initialize emulation parameters. - thread->m_host_context = std::make_shared(std::move(init_func)); + // Clear our stack parameters. + std::memset(static_cast(std::addressof(this->GetStackParameters())), 0, + sizeof(StackParameters)); - R_SUCCEED(); -} + // Set parent, if relevant. + if (owner != nullptr) { + // Setup the TLS, if needed. + if (type == ThreadType::User) { + R_TRY(owner->CreateThreadLocalRegion(std::addressof(m_tls_address))); + owner->GetMemory().ZeroBlock(m_tls_address, Svc::ThreadLocalRegionSize); + } -Result KThread::InitializeDummyThread(KThread* thread, KProcess* owner) { - // Initialize the thread. - R_TRY(thread->Initialize({}, {}, {}, DummyThreadPriority, 3, owner, ThreadType::Dummy)); + m_parent = owner; + m_parent->Open(); + } - // Initialize emulation parameters. - thread->m_stack_parameters.disable_count = 0; + // Initialize thread context. + if (m_parent != nullptr && !m_parent->Is64Bit()) { + ResetThreadContext32(m_thread_context, GetInteger(user_stack_top), GetInteger(func), arg); + } else { + ResetThreadContext64(m_thread_context, GetInteger(user_stack_top), GetInteger(func), arg); + } - R_SUCCEED(); -} + // Setup the stack parameters. + StackParameters& sp = this->GetStackParameters(); + sp.cur_thread = this; + sp.disable_count = 1; + this->SetInExceptionHandler(); -Result KThread::InitializeMainThread(Core::System& system, KThread* thread, s32 virt_core) { - R_RETURN(InitializeThread(thread, {}, {}, {}, IdleThreadPriority, virt_core, {}, - ThreadType::Main, system.GetCpuManager().GetGuestActivateFunc())); -} + // Set thread ID. + m_thread_id = m_kernel.CreateNewThreadID(); -Result KThread::InitializeIdleThread(Core::System& system, KThread* thread, s32 virt_core) { - R_RETURN(InitializeThread(thread, {}, {}, {}, IdleThreadPriority, virt_core, {}, - ThreadType::Main, system.GetCpuManager().GetIdleThreadStartFunc())); -} + // We initialized! + m_initialized = true; -Result KThread::InitializeHighPriorityThread(Core::System& system, KThread* thread, - KThreadFunction func, uintptr_t arg, s32 virt_core) { - R_RETURN(InitializeThread(thread, func, arg, {}, {}, virt_core, nullptr, - ThreadType::HighPriority, - system.GetCpuManager().GetShutdownThreadStartFunc())); -} + // Register ourselves with our parent process. + if (m_parent != nullptr) { + m_parent->RegisterThread(this); + if (m_parent->IsSuspended()) { + RequestSuspend(SuspendType::Process); + } + } -Result KThread::InitializeUserThread(Core::System& system, KThread* thread, KThreadFunction func, - uintptr_t arg, KProcessAddress user_stack_top, s32 prio, - s32 virt_core, KProcess* owner) { - system.Kernel().GlobalSchedulerContext().AddThread(thread); - R_RETURN(InitializeThread(thread, func, arg, user_stack_top, prio, virt_core, owner, - ThreadType::User, system.GetCpuManager().GetGuestThreadFunc())); -} + R_SUCCEED(); + } -Result KThread::InitializeServiceThread(Core::System& system, KThread* thread, - std::function&& func, s32 prio, s32 virt_core, - KProcess* owner) { - system.Kernel().GlobalSchedulerContext().AddThread(thread); - std::function func2{[&system, func_{std::move(func)}] { - // Similar to UserModeThreadStarter. - system.Kernel().CurrentScheduler()->OnThreadStart(); + Result KThread::InitializeThread(KThread* thread, KThreadFunction func, uintptr_t arg, + KProcessAddress user_stack_top, s32 prio, s32 core, + KProcess* owner, ThreadType type, + std::function&& init_func) { + // Initialize the thread. + R_TRY(thread->Initialize(func, arg, user_stack_top, prio, core, owner, type)); - // Run the guest function. - func_(); + // Initialize emulation parameters. + thread->m_host_context = std::make_shared(std::move(init_func)); - // Exit. - Svc::ExitThread(system); - }}; + R_SUCCEED(); + } - R_RETURN(InitializeThread(thread, {}, {}, {}, prio, virt_core, owner, ThreadType::HighPriority, - std::move(func2))); -} + Result KThread::InitializeDummyThread(KThread* thread, KProcess* owner) { + // Initialize the thread. + R_TRY(thread->Initialize({}, {}, {}, DummyThreadPriority, 3, owner, ThreadType::Dummy)); + + // Initialize emulation parameters. + thread->m_stack_parameters.disable_count = 0; -void KThread::PostDestroy(uintptr_t arg) { - KProcess* owner = reinterpret_cast(arg & ~1ULL); - const bool resource_limit_release_hint = (arg & 1); - const s64 hint_value = (resource_limit_release_hint ? 0 : 1); - if (owner != nullptr) { - owner->GetResourceLimit()->Release(LimitableResource::ThreadCountMax, 1, hint_value); - owner->Close(); + R_SUCCEED(); } -} -void KThread::Finalize() { - // If the thread has an owner process, unregister it. - if (m_parent != nullptr) { - m_parent->UnregisterThread(this); + Result KThread::InitializeMainThread(Core::System& system, KThread* thread, s32 virt_core) { + R_RETURN(InitializeThread(thread, {}, {}, {}, IdleThreadPriority, virt_core, {}, + ThreadType::Main, system.GetCpuManager().GetGuestActivateFunc())); } - // If the thread has a local region, delete it. - if (m_tls_address != 0) { - ASSERT(m_parent->DeleteThreadLocalRegion(m_tls_address).IsSuccess()); + Result KThread::InitializeIdleThread(Core::System& system, KThread* thread, s32 virt_core) { + R_RETURN(InitializeThread(thread, {}, {}, {}, IdleThreadPriority, virt_core, {}, + ThreadType::Main, system.GetCpuManager().GetIdleThreadStartFunc())); } - // Release any waiters. - { - ASSERT(m_waiting_lock_info == nullptr); - KScopedSchedulerLock sl{m_kernel}; + Result KThread::InitializeHighPriorityThread(Core::System& system, KThread* thread, + KThreadFunction func, uintptr_t arg, s32 virt_core) { + R_RETURN(InitializeThread(thread, func, arg, {}, {}, virt_core, nullptr, + ThreadType::HighPriority, + system.GetCpuManager().GetShutdownThreadStartFunc())); + } - // Check that we have no kernel waiters. - ASSERT(m_num_kernel_waiters == 0); + Result KThread::InitializeUserThread(Core::System& system, KThread* thread, KThreadFunction func, + uintptr_t arg, KProcessAddress user_stack_top, s32 prio, + s32 virt_core, KProcess* owner) { + system.Kernel().GlobalSchedulerContext().AddThread(thread); + R_RETURN(InitializeThread(thread, func, arg, user_stack_top, prio, virt_core, owner, + ThreadType::User, system.GetCpuManager().GetGuestThreadFunc())); + } - auto it = m_held_lock_info_list.begin(); - while (it != m_held_lock_info_list.end()) { - // Get the lock info. - auto* const lock_info = std::addressof(*it); + Result KThread::InitializeServiceThread(Core::System& system, KThread* thread, + std::function&& func, s32 prio, s32 virt_core, + KProcess* owner) { + system.Kernel().GlobalSchedulerContext().AddThread(thread); + std::function func2{[&system, func_{std::move(func)}] { + // Similar to UserModeThreadStarter. + system.Kernel().CurrentScheduler()->OnThreadStart(); - // The lock shouldn't have a kernel waiter. - ASSERT(!lock_info->GetIsKernelAddressKey()); + // Run the guest function. + func_(); - // Remove all waiters. - while (lock_info->GetWaiterCount() != 0) { - // Get the front waiter. - KThread* const waiter = lock_info->GetHighestPriorityWaiter(); + // Exit. + Svc::ExitThread(system); + }}; - // Remove it from the lock. - if (lock_info->RemoveWaiter(waiter)) { - ASSERT(lock_info->GetWaiterCount() == 0); - } + R_RETURN(InitializeThread(thread, {}, {}, {}, prio, virt_core, owner, ThreadType::HighPriority, + std::move(func2))); + } - // Cancel the thread's wait. - waiter->CancelWait(ResultInvalidState, true); - } + void KThread::PostDestroy(uintptr_t arg) { + KProcess* owner = reinterpret_cast(arg & ~1ULL); + const bool resource_limit_release_hint = (arg & 1); + const s64 hint_value = (resource_limit_release_hint ? 0 : 1); + if (owner != nullptr) { + owner->GetResourceLimit()->Release(LimitableResource::ThreadCountMax, 1, hint_value); + owner->Close(); + } + } - // Remove the held lock from our list. - it = m_held_lock_info_list.erase(it); + void KThread::Finalize() { + // If the thread has an owner process, unregister it. + if (m_parent != nullptr) { + m_parent->UnregisterThread(this); + } - // Free the lock info. - LockWithPriorityInheritanceInfo::Free(m_kernel, lock_info); + // If the thread has a local region, delete it. + if (m_tls_address != 0) { + ASSERT(m_parent->DeleteThreadLocalRegion(m_tls_address).IsSuccess()); } - } - // Release host emulation members. - m_host_context.reset(); + // Release any waiters. + { + ASSERT(m_waiting_lock_info == nullptr); + KScopedSchedulerLock sl{m_kernel}; - // Perform inherited finalization. - KSynchronizationObject::Finalize(); -} + // Check that we have no kernel waiters. + ASSERT(m_num_kernel_waiters == 0); -bool KThread::IsSignaled() const { - return m_signaled; -} + auto it = m_held_lock_info_list.begin(); + while (it != m_held_lock_info_list.end()) { + // Get the lock info. + auto* const lock_info = std::addressof(*it); -void KThread::OnTimer() { - ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); + // The lock shouldn't have a kernel waiter. + ASSERT(!lock_info->GetIsKernelAddressKey()); - // If we're waiting, cancel the wait. - if (this->GetState() == ThreadState::Waiting) { - m_wait_queue->CancelWait(this, ResultTimedOut, false); - } -} + // Remove all waiters. + while (lock_info->GetWaiterCount() != 0) { + // Get the front waiter. + KThread* const waiter = lock_info->GetHighestPriorityWaiter(); + + // Remove it from the lock. + if (lock_info->RemoveWaiter(waiter)) { + ASSERT(lock_info->GetWaiterCount() == 0); + } + + // Cancel the thread's wait. + waiter->CancelWait(ResultInvalidState, true); + } -void KThread::StartTermination() { - ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); + // Remove the held lock from our list. + it = m_held_lock_info_list.erase(it); - // Release user exception and unpin, if relevant. - if (m_parent != nullptr) { - m_parent->ReleaseUserException(this); - if (m_parent->GetPinnedThread(GetCurrentCoreId(m_kernel)) == this) { - m_parent->UnpinCurrentThread(); + // Free the lock info. + LockWithPriorityInheritanceInfo::Free(m_kernel, lock_info); + } } - } - // Set state to terminated. - this->SetState(ThreadState::Terminated); + // Release host emulation members. + m_host_context.reset(); - // Clear the thread's status as running in parent. - if (m_parent != nullptr) { - m_parent->ClearRunningThread(this); + // Perform inherited finalization. + KSynchronizationObject::Finalize(); } - // Clear previous thread in KScheduler. - KScheduler::ClearPreviousThread(m_kernel, this); + bool KThread::IsSignaled() const { + return m_signaled; + } - // Register terminated dpc flag. - this->RegisterDpc(DpcFlag::Terminated); -} + void KThread::OnTimer() { + ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); -void KThread::FinishTermination() { - // Ensure that the thread is not executing on any core. - if (m_parent != nullptr) { - for (std::size_t i = 0; i < static_cast(Core::Hardware::NUM_CPU_CORES); ++i) { - KThread* core_thread{}; - do { - core_thread = m_kernel.Scheduler(i).GetSchedulerCurrentThread(); - } while (core_thread == this); + // If we're waiting, cancel the wait. + if (this->GetState() == ThreadState::Waiting) { + m_wait_queue->CancelWait(this, ResultTimedOut, false); } } - // Acquire the scheduler lock. - KScopedSchedulerLock sl{m_kernel}; + void KThread::StartTermination() { + ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); - // Signal. - m_signaled = true; - KSynchronizationObject::NotifyAvailable(); + // Release user exception and unpin, if relevant. + if (m_parent != nullptr) { + m_parent->ReleaseUserException(this); + if (m_parent->GetPinnedThread(GetCurrentCoreId(m_kernel)) == this) { + m_parent->UnpinCurrentThread(); + } + } - // Close the thread. - this->Close(); -} + // Set state to terminated. + this->SetState(ThreadState::Terminated); -void KThread::DoWorkerTaskImpl() { - // Finish the termination that was begun by Exit(). - this->FinishTermination(); -} + // Clear the thread's status as running in parent. + if (m_parent != nullptr) { + m_parent->ClearRunningThread(this); + } -void KThread::Pin(s32 current_core) { - ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); + // Clear previous thread in KScheduler. + KScheduler::ClearPreviousThread(m_kernel, this); - // Set ourselves as pinned. - GetStackParameters().is_pinned = true; + // Register terminated dpc flag. + this->RegisterDpc(DpcFlag::Terminated); + } - // Disable core migration. - ASSERT(m_num_core_migration_disables == 0); - { - ++m_num_core_migration_disables; + void KThread::FinishTermination() { + // Ensure that the thread is not executing on any core. + if (m_parent != nullptr) { + for (std::size_t i = 0; i < static_cast(Core::Hardware::NUM_CPU_CORES); ++i) { + KThread* core_thread{}; + do { + core_thread = m_kernel.Scheduler(i).GetSchedulerCurrentThread(); + } while (core_thread == this); + } + } - // Save our ideal state to restore when we're unpinned. - m_original_physical_ideal_core_id = m_physical_ideal_core_id; - m_original_physical_affinity_mask = m_physical_affinity_mask; + // Acquire the scheduler lock. + KScopedSchedulerLock sl{m_kernel}; - // Bind ourselves to this core. - const s32 active_core = this->GetActiveCore(); + // Signal. + m_signaled = true; + KSynchronizationObject::NotifyAvailable(); - this->SetActiveCore(current_core); - m_physical_ideal_core_id = current_core; - m_physical_affinity_mask.SetAffinityMask(1ULL << current_core); + // Close the thread. + this->Close(); + } - if (active_core != current_core || - m_physical_affinity_mask.GetAffinityMask() != - m_original_physical_affinity_mask.GetAffinityMask()) { - KScheduler::OnThreadAffinityMaskChanged(m_kernel, this, - m_original_physical_affinity_mask, active_core); - } + void KThread::DoWorkerTaskImpl() { + // Finish the termination that was begun by Exit(). + this->FinishTermination(); } - // Disallow performing thread suspension. - { - // Update our allow flags. - m_suspend_allowed_flags &= ~(1 << (static_cast(SuspendType::Thread) + - static_cast(ThreadState::SuspendShift))); + void KThread::Pin(s32 current_core) { + ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); - // Update our state. - this->UpdateState(); - } + // Set ourselves as pinned. + GetStackParameters().is_pinned = true; - // TODO(bunnei): Update our SVC access permissions. - ASSERT(m_parent != nullptr); -} + // Disable core migration. + ASSERT(m_num_core_migration_disables == 0); + { + ++m_num_core_migration_disables; -void KThread::Unpin() { - ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); + // Save our ideal state to restore when we're unpinned. + m_original_physical_ideal_core_id = m_physical_ideal_core_id; + m_original_physical_affinity_mask = m_physical_affinity_mask; - // Set ourselves as unpinned. - this->GetStackParameters().is_pinned = false; + // Bind ourselves to this core. + const s32 active_core = this->GetActiveCore(); - // Enable core migration. - ASSERT(m_num_core_migration_disables == 1); - { - m_num_core_migration_disables--; + this->SetActiveCore(current_core); + m_physical_ideal_core_id = current_core; + m_physical_affinity_mask.SetAffinityMask(1ULL << current_core); - // Restore our original state. - const KAffinityMask old_mask = m_physical_affinity_mask; + if (active_core != current_core || + m_physical_affinity_mask.GetAffinityMask() != + m_original_physical_affinity_mask.GetAffinityMask()) { + KScheduler::OnThreadAffinityMaskChanged(m_kernel, this, + m_original_physical_affinity_mask, active_core); + } + } - m_physical_ideal_core_id = m_original_physical_ideal_core_id; - m_physical_affinity_mask = m_original_physical_affinity_mask; + // Disallow performing thread suspension. + { + // Update our allow flags. + m_suspend_allowed_flags &= ~(1 << (static_cast(SuspendType::Thread) + + static_cast(ThreadState::SuspendShift))); - if (m_physical_affinity_mask.GetAffinityMask() != old_mask.GetAffinityMask()) { - const s32 active_core = this->GetActiveCore(); + // Update our state. + this->UpdateState(); + } - if (!m_physical_affinity_mask.GetAffinity(active_core)) { - if (m_physical_ideal_core_id >= 0) { - this->SetActiveCore(m_physical_ideal_core_id); - } else { - this->SetActiveCore(static_cast( - Common::BitSize() - 1 - - std::countl_zero(m_physical_affinity_mask.GetAffinityMask()))); + // TODO(bunnei): Update our SVC access permissions. + ASSERT(m_parent != nullptr); + } + + void KThread::Unpin() { + ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); + + // Set ourselves as unpinned. + this->GetStackParameters().is_pinned = false; + + // Enable core migration. + ASSERT(m_num_core_migration_disables == 1); + { + m_num_core_migration_disables--; + + // Restore our original state. + const KAffinityMask old_mask = m_physical_affinity_mask; + + m_physical_ideal_core_id = m_original_physical_ideal_core_id; + m_physical_affinity_mask = m_original_physical_affinity_mask; + + if (m_physical_affinity_mask.GetAffinityMask() != old_mask.GetAffinityMask()) { + const s32 active_core = this->GetActiveCore(); + + if (!m_physical_affinity_mask.GetAffinity(active_core)) { + if (m_physical_ideal_core_id >= 0) { + this->SetActiveCore(m_physical_ideal_core_id); + } else { + this->SetActiveCore(static_cast( + Common::BitSize() - 1 - + std::countl_zero(m_physical_affinity_mask.GetAffinityMask()))); + } } + KScheduler::OnThreadAffinityMaskChanged(m_kernel, this, old_mask, active_core); } - KScheduler::OnThreadAffinityMaskChanged(m_kernel, this, old_mask, active_core); } - } - // Allow performing thread suspension (if termination hasn't been requested). - if (!this->IsTerminationRequested()) { - // Update our allow flags. - m_suspend_allowed_flags |= (1 << (static_cast(SuspendType::Thread) + - static_cast(ThreadState::SuspendShift))); + // Allow performing thread suspension (if termination hasn't been requested). + if (!this->IsTerminationRequested()) { + // Update our allow flags. + m_suspend_allowed_flags |= (1 << (static_cast(SuspendType::Thread) + + static_cast(ThreadState::SuspendShift))); - // Update our state. - this->UpdateState(); - } + // Update our state. + this->UpdateState(); + } - // TODO(bunnei): Update our SVC access permissions. - ASSERT(m_parent != nullptr); + // TODO(bunnei): Update our SVC access permissions. + ASSERT(m_parent != nullptr); - // Resume any threads that began waiting on us while we were pinned. - for (auto it = m_pinned_waiter_list.begin(); it != m_pinned_waiter_list.end(); - it = m_pinned_waiter_list.erase(it)) { - it->EndWait(ResultSuccess); + // Resume any threads that began waiting on us while we were pinned. + for (auto it = m_pinned_waiter_list.begin(); it != m_pinned_waiter_list.end(); + it = m_pinned_waiter_list.erase(it)) { + it->EndWait(ResultSuccess); + } } -} -u16 KThread::GetUserDisableCount() const { - if (!this->IsUserThread()) { - // We only emulate TLS for user threads - return {}; + u16 KThread::GetUserDisableCount() const { + if (!this->IsUserThread()) { + // We only emulate TLS for user threads + return {}; + } + + auto& memory = this->GetOwnerProcess()->GetMemory(); + return memory.Read16(m_tls_address + offsetof(ThreadLocalRegion, disable_count)); } - auto& memory = this->GetOwnerProcess()->GetMemory(); - return memory.Read16(m_tls_address + offsetof(ThreadLocalRegion, disable_count)); -} + void KThread::SetInterruptFlag() { + if (!this->IsUserThread()) { + // We only emulate TLS for user threads + return; + } -void KThread::SetInterruptFlag() { - if (!this->IsUserThread()) { - // We only emulate TLS for user threads - return; + auto& memory = this->GetOwnerProcess()->GetMemory(); + memory.Write16(m_tls_address + offsetof(ThreadLocalRegion, interrupt_flag), 1); } - auto& memory = this->GetOwnerProcess()->GetMemory(); - memory.Write16(m_tls_address + offsetof(ThreadLocalRegion, interrupt_flag), 1); -} + void KThread::ClearInterruptFlag() { + if (!this->IsUserThread()) { + // We only emulate TLS for user threads + return; + } -void KThread::ClearInterruptFlag() { - if (!this->IsUserThread()) { - // We only emulate TLS for user threads - return; + auto& memory = this->GetOwnerProcess()->GetMemory(); + memory.Write16(m_tls_address + offsetof(ThreadLocalRegion, interrupt_flag), 0); } - auto& memory = this->GetOwnerProcess()->GetMemory(); - memory.Write16(m_tls_address + offsetof(ThreadLocalRegion, interrupt_flag), 0); -} - -Result KThread::GetCoreMask(s32* out_ideal_core, u64* out_affinity_mask) { - KScopedSchedulerLock sl{m_kernel}; + void KThread::UpdateTlsThreadCpuTime(s64 switch_tick) { + if (!this->IsUserThread()) { + return; + } + if (m_tls_address == 0) { + return; + } - // Get the virtual mask. - *out_ideal_core = m_virtual_ideal_core_id; - *out_affinity_mask = m_virtual_affinity_mask; + const s64 value = this->GetCpuTime() - switch_tick; + auto& memory = this->GetOwnerProcess()->GetMemory(); + memory.Write64(m_tls_address + offsetof(ThreadLocalRegion, thread_cpu_time), static_cast(value)); + } - R_SUCCEED(); -} + Result KThread::GetCoreMask(s32* out_ideal_core, u64* out_affinity_mask) { + KScopedSchedulerLock sl{m_kernel}; -Result KThread::GetPhysicalCoreMask(s32* out_ideal_core, u64* out_affinity_mask) { - KScopedSchedulerLock sl{m_kernel}; - ASSERT(m_num_core_migration_disables >= 0); + // Get the virtual mask. + *out_ideal_core = m_virtual_ideal_core_id; + *out_affinity_mask = m_virtual_affinity_mask; - // Select between core mask and original core mask. - if (m_num_core_migration_disables == 0) { - *out_ideal_core = m_physical_ideal_core_id; - *out_affinity_mask = m_physical_affinity_mask.GetAffinityMask(); - } else { - *out_ideal_core = m_original_physical_ideal_core_id; - *out_affinity_mask = m_original_physical_affinity_mask.GetAffinityMask(); + R_SUCCEED(); } - R_SUCCEED(); -} - -Result KThread::SetCoreMask(s32 core_id, u64 v_affinity_mask) { - ASSERT(m_parent != nullptr); - ASSERT(v_affinity_mask != 0); - KScopedLightLock lk(m_activity_pause_lock); - - // Set the core mask. - u64 p_affinity_mask = 0; - { - KScopedSchedulerLock sl(m_kernel); + Result KThread::GetPhysicalCoreMask(s32* out_ideal_core, u64* out_affinity_mask) { + KScopedSchedulerLock sl{m_kernel}; ASSERT(m_num_core_migration_disables >= 0); - // If we're updating, set our ideal virtual core. - if (core_id != Svc::IdealCoreNoUpdate) { - m_virtual_ideal_core_id = core_id; + // Select between core mask and original core mask. + if (m_num_core_migration_disables == 0) { + *out_ideal_core = m_physical_ideal_core_id; + *out_affinity_mask = m_physical_affinity_mask.GetAffinityMask(); } else { - // Preserve our ideal core id. - core_id = m_virtual_ideal_core_id; - R_UNLESS(((1ULL << core_id) & v_affinity_mask) != 0, ResultInvalidCombination); + *out_ideal_core = m_original_physical_ideal_core_id; + *out_affinity_mask = m_original_physical_affinity_mask.GetAffinityMask(); } - // Set our affinity mask. - m_virtual_affinity_mask = v_affinity_mask; + R_SUCCEED(); + } - // Translate the virtual core to a physical core. - if (core_id >= 0) { - core_id = Core::Hardware::VirtualToPhysicalCoreMap[core_id]; - } + Result KThread::SetCoreMask(s32 core_id, u64 v_affinity_mask) { + ASSERT(m_parent != nullptr); + ASSERT(v_affinity_mask != 0); + KScopedLightLock lk(m_activity_pause_lock); - // Translate the virtual affinity mask to a physical one. - while (v_affinity_mask != 0) { - const u64 next = std::countr_zero(v_affinity_mask); - v_affinity_mask &= ~(1ULL << next); - p_affinity_mask |= (1ULL << Core::Hardware::VirtualToPhysicalCoreMap[next]); - } + // Set the core mask. + u64 p_affinity_mask = 0; + { + KScopedSchedulerLock sl(m_kernel); + ASSERT(m_num_core_migration_disables >= 0); - // If we haven't disabled migration, perform an affinity change. - if (m_num_core_migration_disables == 0) { - const KAffinityMask old_mask = m_physical_affinity_mask; + // If we're updating, set our ideal virtual core. + if (core_id != Svc::IdealCoreNoUpdate) { + m_virtual_ideal_core_id = core_id; + } else { + // Preserve our ideal core id. + core_id = m_virtual_ideal_core_id; + R_UNLESS(((1ULL << core_id) & v_affinity_mask) != 0, ResultInvalidCombination); + } - // Set our new ideals. - m_physical_ideal_core_id = core_id; - m_physical_affinity_mask.SetAffinityMask(p_affinity_mask); + // Set our affinity mask. + m_virtual_affinity_mask = v_affinity_mask; - if (m_physical_affinity_mask.GetAffinityMask() != old_mask.GetAffinityMask()) { - const s32 active_core = GetActiveCore(); - - if (active_core >= 0 && !m_physical_affinity_mask.GetAffinity(active_core)) { - const s32 new_core = static_cast( - m_physical_ideal_core_id >= 0 - ? m_physical_ideal_core_id - : Common::BitSize() - 1 - - std::countl_zero(m_physical_affinity_mask.GetAffinityMask())); - SetActiveCore(new_core); - } - KScheduler::OnThreadAffinityMaskChanged(m_kernel, this, old_mask, active_core); + // Translate the virtual core to a physical core. + if (core_id >= 0) { + core_id = Core::Hardware::VirtualToPhysicalCoreMap[core_id]; } - } else { - // Otherwise, we edit the original affinity for restoration later. - m_original_physical_ideal_core_id = core_id; - m_original_physical_affinity_mask.SetAffinityMask(p_affinity_mask); - } - } - - // Update the pinned waiter list. - ThreadQueueImplForKThreadSetProperty wait_queue(m_kernel, std::addressof(m_pinned_waiter_list)); - { - bool retry_update{}; - do { - // Lock the scheduler. - KScopedSchedulerLock sl(m_kernel); - // Don't do any further management if our termination has been requested. - R_SUCCEED_IF(this->IsTerminationRequested()); + // Translate the virtual affinity mask to a physical one. + while (v_affinity_mask != 0) { + const u64 next = std::countr_zero(v_affinity_mask); + v_affinity_mask &= ~(1ULL << next); + p_affinity_mask |= (1ULL << Core::Hardware::VirtualToPhysicalCoreMap[next]); + } - // By default, we won't need to retry. - retry_update = false; + // If we haven't disabled migration, perform an affinity change. + if (m_num_core_migration_disables == 0) { + const KAffinityMask old_mask = m_physical_affinity_mask; - // Check if the thread is currently running. - bool thread_is_current{}; - s32 thread_core; - for (thread_core = 0; thread_core < static_cast(Core::Hardware::NUM_CPU_CORES); - ++thread_core) { - if (m_kernel.Scheduler(thread_core).GetSchedulerCurrentThread() == this) { - thread_is_current = true; - break; - } - } + // Set our new ideals. + m_physical_ideal_core_id = core_id; + m_physical_affinity_mask.SetAffinityMask(p_affinity_mask); - // If the thread is currently running, check whether it's no longer allowed under the - // new mask. - if (thread_is_current && ((1ULL << thread_core) & p_affinity_mask) == 0) { - // If the thread is pinned, we want to wait until it's not pinned. - if (this->GetStackParameters().is_pinned) { - // Verify that the current thread isn't terminating. - R_UNLESS(!GetCurrentThread(m_kernel).IsTerminationRequested(), - ResultTerminationRequested); + if (m_physical_affinity_mask.GetAffinityMask() != old_mask.GetAffinityMask()) { + const s32 active_core = GetActiveCore(); - // Wait until the thread isn't pinned any more. - m_pinned_waiter_list.push_back(GetCurrentThread(m_kernel)); - GetCurrentThread(m_kernel).BeginWait(std::addressof(wait_queue)); - } else { - // If the thread isn't pinned, release the scheduler lock and retry until it's - // not current. - retry_update = true; + if (active_core >= 0 && !m_physical_affinity_mask.GetAffinity(active_core)) { + const s32 new_core = static_cast( + m_physical_ideal_core_id >= 0 + ? m_physical_ideal_core_id + : Common::BitSize() - 1 - + std::countl_zero(m_physical_affinity_mask.GetAffinityMask())); + SetActiveCore(new_core); + } + KScheduler::OnThreadAffinityMaskChanged(m_kernel, this, old_mask, active_core); } + } else { + // Otherwise, we edit the original affinity for restoration later. + m_original_physical_ideal_core_id = core_id; + m_original_physical_affinity_mask.SetAffinityMask(p_affinity_mask); } - } while (retry_update); - } + } - R_SUCCEED(); -} + // Update the pinned waiter list. + ThreadQueueImplForKThreadSetProperty wait_queue(m_kernel, std::addressof(m_pinned_waiter_list)); + { + bool retry_update{}; + do { + // Lock the scheduler. + KScopedSchedulerLock sl(m_kernel); -void KThread::SetBasePriority(s32 value) { - ASSERT(Svc::HighestThreadPriority <= value && value <= Svc::LowestThreadPriority); + // Don't do any further management if our termination has been requested. + R_SUCCEED_IF(this->IsTerminationRequested()); - KScopedSchedulerLock sl{m_kernel}; + // By default, we won't need to retry. + retry_update = false; - // Change our base priority. - m_base_priority = value; + // Check if the thread is currently running. + bool thread_is_current{}; + s32 thread_core; + for (thread_core = 0; thread_core < static_cast(Core::Hardware::NUM_CPU_CORES); + ++thread_core) { + if (m_kernel.Scheduler(thread_core).GetSchedulerCurrentThread() == this) { + thread_is_current = true; + break; + } + } + + // If the thread is currently running, check whether it's no longer allowed under the + // new mask. + if (thread_is_current && ((1ULL << thread_core) & p_affinity_mask) == 0) { + // If the thread is pinned, we want to wait until it's not pinned. + if (this->GetStackParameters().is_pinned) { + // Verify that the current thread isn't terminating. + R_UNLESS(!GetCurrentThread(m_kernel).IsTerminationRequested(), + ResultTerminationRequested); + + // Wait until the thread isn't pinned any more. + m_pinned_waiter_list.push_back(GetCurrentThread(m_kernel)); + GetCurrentThread(m_kernel).BeginWait(std::addressof(wait_queue)); + } else { + // If the thread isn't pinned, release the scheduler lock and retry until it's + // not current. + retry_update = true; + } + } + } while (retry_update); + } - // Perform a priority restoration. - RestorePriority(m_kernel, this); -} + R_SUCCEED(); + } -KThread* KThread::GetLockOwner() const { - return m_waiting_lock_info != nullptr ? m_waiting_lock_info->GetOwner() : nullptr; -} + void KThread::SetBasePriority(s32 value) { + ASSERT(Svc::HighestThreadPriority <= value && value <= Svc::LowestThreadPriority); -void KThread::IncreaseBasePriority(s32 priority) { - ASSERT(Svc::HighestThreadPriority <= priority && priority <= Svc::LowestThreadPriority); - ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); - ASSERT(!this->GetStackParameters().is_pinned); + KScopedSchedulerLock sl{m_kernel}; - // Set our base priority. - if (m_base_priority > priority) { - m_base_priority = priority; + // Change our base priority. + m_base_priority = value; // Perform a priority restoration. RestorePriority(m_kernel, this); } -} -void KThread::RequestSuspend(SuspendType type) { - KScopedSchedulerLock sl{m_kernel}; + KThread* KThread::GetLockOwner() const { + return m_waiting_lock_info != nullptr ? m_waiting_lock_info->GetOwner() : nullptr; + } - // Note the request in our flags. - m_suspend_request_flags |= - (1U << (static_cast(ThreadState::SuspendShift) + static_cast(type))); + void KThread::IncreaseBasePriority(s32 priority) { + ASSERT(Svc::HighestThreadPriority <= priority && priority <= Svc::LowestThreadPriority); + ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); + ASSERT(!this->GetStackParameters().is_pinned); - // Try to perform the suspend. - this->TrySuspend(); -} + // Set our base priority. + if (m_base_priority > priority) { + m_base_priority = priority; -void KThread::Resume(SuspendType type) { - KScopedSchedulerLock sl{m_kernel}; + // Perform a priority restoration. + RestorePriority(m_kernel, this); + } + } - // Clear the request in our flags. - m_suspend_request_flags &= - ~(1U << (static_cast(ThreadState::SuspendShift) + static_cast(type))); + void KThread::RequestSuspend(SuspendType type) { + KScopedSchedulerLock sl{m_kernel}; - // Update our state. - this->UpdateState(); -} + // Note the request in our flags. + m_suspend_request_flags |= + (1U << (static_cast(ThreadState::SuspendShift) + static_cast(type))); -void KThread::WaitCancel() { - KScopedSchedulerLock sl{m_kernel}; + // Try to perform the suspend. + this->TrySuspend(); + } - // Check if we're waiting and cancellable. - if (this->GetState() == ThreadState::Waiting && m_cancellable) { - m_wait_cancelled = false; - m_wait_queue->CancelWait(this, ResultCancelled, true); - } else { - // Otherwise, note that we cancelled a wait. - m_wait_cancelled = true; + void KThread::Resume(SuspendType type) { + KScopedSchedulerLock sl{m_kernel}; + + // Clear the request in our flags. + m_suspend_request_flags &= + ~(1U << (static_cast(ThreadState::SuspendShift) + static_cast(type))); + + // Update our state. + this->UpdateState(); } -} -void KThread::TrySuspend() { - ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); - ASSERT(this->IsSuspendRequested()); + void KThread::WaitCancel() { + KScopedSchedulerLock sl{m_kernel}; - // Ensure that we have no waiters. - if (this->GetNumKernelWaiters() > 0) { - return; + // Check if we're waiting and cancellable. + if (this->GetState() == ThreadState::Waiting && m_cancellable) { + m_wait_cancelled = false; + m_wait_queue->CancelWait(this, ResultCancelled, true); + } else { + // Otherwise, note that we cancelled a wait. + m_wait_cancelled = true; + } } - ASSERT(this->GetNumKernelWaiters() == 0); - // Perform the suspend. - this->UpdateState(); -} + void KThread::TrySuspend() { + ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); + ASSERT(this->IsSuspendRequested()); -void KThread::UpdateState() { - ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); + // Ensure that we have no waiters. + if (this->GetNumKernelWaiters() > 0) { + return; + } + ASSERT(this->GetNumKernelWaiters() == 0); + + // Perform the suspend. + this->UpdateState(); + } - // Set our suspend flags in state. - const ThreadState old_state = m_thread_state.load(std::memory_order_relaxed); - const auto new_state = - static_cast(this->GetSuspendFlags()) | (old_state & ThreadState::Mask); - m_thread_state.store(new_state, std::memory_order_relaxed); + void KThread::UpdateState() { + ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); - // Note the state change in scheduler. - if (new_state != old_state) { - KScheduler::OnThreadStateChanged(m_kernel, this, old_state); + // Set our suspend flags in state. + const ThreadState old_state = m_thread_state.load(std::memory_order_relaxed); + const auto new_state = + static_cast(this->GetSuspendFlags()) | (old_state & ThreadState::Mask); + m_thread_state.store(new_state, std::memory_order_relaxed); + + // Note the state change in scheduler. + if (new_state != old_state) { + KScheduler::OnThreadStateChanged(m_kernel, this, old_state); + } } -} -void KThread::Continue() { - ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); + void KThread::Continue() { + ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); - // Clear our suspend flags in state. - const ThreadState old_state = m_thread_state.load(std::memory_order_relaxed); - m_thread_state.store(old_state & ThreadState::Mask, std::memory_order_relaxed); + // Clear our suspend flags in state. + const ThreadState old_state = m_thread_state.load(std::memory_order_relaxed); + m_thread_state.store(old_state & ThreadState::Mask, std::memory_order_relaxed); - // Note the state change in scheduler. - KScheduler::OnThreadStateChanged(m_kernel, this, old_state); -} + // Note the state change in scheduler. + KScheduler::OnThreadStateChanged(m_kernel, this, old_state); + } -void KThread::CloneFpuStatus() { - // We shouldn't reach here when starting kernel threads. - ASSERT(this->GetOwnerProcess() != nullptr); - ASSERT(this->GetOwnerProcess() == GetCurrentProcessPointer(m_kernel)); + void KThread::CloneFpuStatus() { + // We shouldn't reach here when starting kernel threads. + ASSERT(this->GetOwnerProcess() != nullptr); + ASSERT(this->GetOwnerProcess() == GetCurrentProcessPointer(m_kernel)); - m_kernel.CurrentPhysicalCore().CloneFpuStatus(this); -} + m_kernel.CurrentPhysicalCore().CloneFpuStatus(this); + } -Result KThread::SetActivity(Svc::ThreadActivity activity) { - // Lock ourselves. - KScopedLightLock lk(m_activity_pause_lock); + Result KThread::SetActivity(Svc::ThreadActivity activity) { + // Lock ourselves. + KScopedLightLock lk(m_activity_pause_lock); - // Set the activity. - { - // Lock the scheduler. - KScopedSchedulerLock sl(m_kernel); + // Set the activity. + { + // Lock the scheduler. + KScopedSchedulerLock sl(m_kernel); - // Verify our state. - const auto cur_state = this->GetState(); - R_UNLESS((cur_state == ThreadState::Waiting || cur_state == ThreadState::Runnable), - ResultInvalidState); + // Verify our state. + const auto cur_state = this->GetState(); + R_UNLESS((cur_state == ThreadState::Waiting || cur_state == ThreadState::Runnable), + ResultInvalidState); - // Either pause or resume. - if (activity == Svc::ThreadActivity::Paused) { - // Verify that we're not suspended. - R_UNLESS(!this->IsSuspendRequested(SuspendType::Thread), ResultInvalidState); + // Either pause or resume. + if (activity == Svc::ThreadActivity::Paused) { + // Verify that we're not suspended. + R_UNLESS(!this->IsSuspendRequested(SuspendType::Thread), ResultInvalidState); - // Suspend. - this->RequestSuspend(SuspendType::Thread); - } else { - ASSERT(activity == Svc::ThreadActivity::Runnable); + // Suspend. + this->RequestSuspend(SuspendType::Thread); + } else { + ASSERT(activity == Svc::ThreadActivity::Runnable); - // Verify that we're suspended. - R_UNLESS(this->IsSuspendRequested(SuspendType::Thread), ResultInvalidState); + // Verify that we're suspended. + R_UNLESS(this->IsSuspendRequested(SuspendType::Thread), ResultInvalidState); - // Resume. - this->Resume(SuspendType::Thread); + // Resume. + this->Resume(SuspendType::Thread); + } } - } - // If the thread is now paused, update the pinned waiter list. - if (activity == Svc::ThreadActivity::Paused) { - ThreadQueueImplForKThreadSetProperty wait_queue(m_kernel, - std::addressof(m_pinned_waiter_list)); + // If the thread is now paused, update the pinned waiter list. + if (activity == Svc::ThreadActivity::Paused) { + ThreadQueueImplForKThreadSetProperty wait_queue(m_kernel, + std::addressof(m_pinned_waiter_list)); - bool thread_is_current{}; - do { - // Lock the scheduler. - KScopedSchedulerLock sl(m_kernel); + bool thread_is_current{}; + do { + // Lock the scheduler. + KScopedSchedulerLock sl(m_kernel); - // Don't do any further management if our termination has been requested. - R_SUCCEED_IF(this->IsTerminationRequested()); + // Don't do any further management if our termination has been requested. + R_SUCCEED_IF(this->IsTerminationRequested()); - // By default, treat the thread as not current. - thread_is_current = false; + // By default, treat the thread as not current. + thread_is_current = false; - // Check whether the thread is pinned. - if (this->GetStackParameters().is_pinned) { - // Verify that the current thread isn't terminating. - R_UNLESS(!GetCurrentThread(m_kernel).IsTerminationRequested(), - ResultTerminationRequested); + // Check whether the thread is pinned. + if (this->GetStackParameters().is_pinned) { + // Verify that the current thread isn't terminating. + R_UNLESS(!GetCurrentThread(m_kernel).IsTerminationRequested(), + ResultTerminationRequested); - // Wait until the thread isn't pinned any more. - m_pinned_waiter_list.push_back(GetCurrentThread(m_kernel)); - GetCurrentThread(m_kernel).BeginWait(std::addressof(wait_queue)); - } else { - // Check if the thread is currently running. - // If it is, we'll need to retry. - for (auto i = 0; i < static_cast(Core::Hardware::NUM_CPU_CORES); ++i) { - if (m_kernel.Scheduler(i).GetSchedulerCurrentThread() == this) { - thread_is_current = true; - break; + // Wait until the thread isn't pinned any more. + m_pinned_waiter_list.push_back(GetCurrentThread(m_kernel)); + GetCurrentThread(m_kernel).BeginWait(std::addressof(wait_queue)); + } else { + // Check if the thread is currently running. + // If it is, we'll need to retry. + for (auto i = 0; i < static_cast(Core::Hardware::NUM_CPU_CORES); ++i) { + if (m_kernel.Scheduler(i).GetSchedulerCurrentThread() == this) { + thread_is_current = true; + break; + } } } - } - } while (thread_is_current); - } + } while (thread_is_current); + } - R_SUCCEED(); -} + R_SUCCEED(); + } -Result KThread::GetThreadContext3(Svc::ThreadContext* out) { - // Lock ourselves. - KScopedLightLock lk{m_activity_pause_lock}; + Result KThread::GetThreadContext3(Svc::ThreadContext* out) { + // Lock ourselves. + KScopedLightLock lk{m_activity_pause_lock}; - // Get the context. - { - // Lock the scheduler. - KScopedSchedulerLock sl{m_kernel}; + // Get the context. + { + // Lock the scheduler. + KScopedSchedulerLock sl{m_kernel}; - // Verify that we're suspended. - R_UNLESS(this->IsSuspendRequested(SuspendType::Thread), ResultInvalidState); + // Verify that we're suspended. + R_UNLESS(this->IsSuspendRequested(SuspendType::Thread), ResultInvalidState); - // If we're not terminating, get the thread's user context. - if (!this->IsTerminationRequested()) { - *out = m_thread_context; + // If we're not terminating, get the thread's user context. + if (!this->IsTerminationRequested()) { + *out = m_thread_context; - // Mask away mode bits, interrupt bits, IL bit, and other reserved bits. - constexpr u32 El0Aarch64PsrMask = 0xF0000000; - constexpr u32 El0Aarch32PsrMask = 0xFE0FFE20; + // Mask away mode bits, interrupt bits, IL bit, and other reserved bits. + constexpr u32 El0Aarch64PsrMask = 0xF0000000; + constexpr u32 El0Aarch32PsrMask = 0xFE0FFE20; - if (m_parent->Is64Bit()) { - out->pstate &= El0Aarch64PsrMask; - } else { - out->pstate &= El0Aarch32PsrMask; + if (m_parent->Is64Bit()) { + out->pstate &= El0Aarch64PsrMask; + } else { + out->pstate &= El0Aarch32PsrMask; + } } } - } - R_SUCCEED(); -} + R_SUCCEED(); + } -void KThread::AddHeldLock(LockWithPriorityInheritanceInfo* lock_info) { - ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); + void KThread::AddHeldLock(LockWithPriorityInheritanceInfo* lock_info) { + ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); - // Set ourselves as the lock's owner. - lock_info->SetOwner(this); + // Set ourselves as the lock's owner. + lock_info->SetOwner(this); - // Add the lock to our held list. - m_held_lock_info_list.push_front(*lock_info); -} + // Add the lock to our held list. + m_held_lock_info_list.push_front(*lock_info); + } -KThread::LockWithPriorityInheritanceInfo* KThread::FindHeldLock(KProcessAddress address_key, - bool is_kernel_address_key) { - ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); + KThread::LockWithPriorityInheritanceInfo* KThread::FindHeldLock(KProcessAddress address_key, + bool is_kernel_address_key) { + ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); - // Try to find an existing held lock. - for (auto& held_lock : m_held_lock_info_list) { - if (held_lock.GetAddressKey() == address_key && - held_lock.GetIsKernelAddressKey() == is_kernel_address_key) { - return std::addressof(held_lock); + // Try to find an existing held lock. + for (auto& held_lock : m_held_lock_info_list) { + if (held_lock.GetAddressKey() == address_key && + held_lock.GetIsKernelAddressKey() == is_kernel_address_key) { + return std::addressof(held_lock); + } } + + return nullptr; } - return nullptr; -} + void KThread::AddWaiterImpl(KThread* thread) { + ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); + ASSERT(thread->GetConditionVariableTree() == nullptr); -void KThread::AddWaiterImpl(KThread* thread) { - ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); - ASSERT(thread->GetConditionVariableTree() == nullptr); + // Get the thread's address key. + const auto address_key = thread->GetAddressKey(); + const auto is_kernel_address_key = thread->GetIsKernelAddressKey(); - // Get the thread's address key. - const auto address_key = thread->GetAddressKey(); - const auto is_kernel_address_key = thread->GetIsKernelAddressKey(); + // Keep track of how many kernel waiters we have. + if (is_kernel_address_key) { + ASSERT((m_num_kernel_waiters++) >= 0); + KScheduler::SetSchedulerUpdateNeeded(m_kernel); + } - // Keep track of how many kernel waiters we have. - if (is_kernel_address_key) { - ASSERT((m_num_kernel_waiters++) >= 0); - KScheduler::SetSchedulerUpdateNeeded(m_kernel); - } + // Get the relevant lock info. + auto* lock_info = this->FindHeldLock(address_key, is_kernel_address_key); + if (lock_info == nullptr) { + // Create a new lock for the address key. + lock_info = + LockWithPriorityInheritanceInfo::Create(m_kernel, address_key, is_kernel_address_key); - // Get the relevant lock info. - auto* lock_info = this->FindHeldLock(address_key, is_kernel_address_key); - if (lock_info == nullptr) { - // Create a new lock for the address key. - lock_info = - LockWithPriorityInheritanceInfo::Create(m_kernel, address_key, is_kernel_address_key); + // Add the new lock to our list. + this->AddHeldLock(lock_info); + } - // Add the new lock to our list. - this->AddHeldLock(lock_info); + // Add the thread as waiter to the lock info. + lock_info->AddWaiter(thread); } - // Add the thread as waiter to the lock info. - lock_info->AddWaiter(thread); -} - -void KThread::RemoveWaiterImpl(KThread* thread) { - ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); + void KThread::RemoveWaiterImpl(KThread* thread) { + ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); - // Keep track of how many kernel waiters we have. - if (thread->GetIsKernelAddressKey()) { - ASSERT((m_num_kernel_waiters--) > 0); - KScheduler::SetSchedulerUpdateNeeded(m_kernel); - } + // Keep track of how many kernel waiters we have. + if (thread->GetIsKernelAddressKey()) { + ASSERT((m_num_kernel_waiters--) > 0); + KScheduler::SetSchedulerUpdateNeeded(m_kernel); + } - // Get the info for the lock the thread is waiting on. - auto* lock_info = thread->GetWaitingLockInfo(); - ASSERT(lock_info->GetOwner() == this); + // Get the info for the lock the thread is waiting on. + auto* lock_info = thread->GetWaitingLockInfo(); + ASSERT(lock_info->GetOwner() == this); - // Remove the waiter. - if (lock_info->RemoveWaiter(thread)) { - m_held_lock_info_list.erase(m_held_lock_info_list.iterator_to(*lock_info)); - LockWithPriorityInheritanceInfo::Free(m_kernel, lock_info); + // Remove the waiter. + if (lock_info->RemoveWaiter(thread)) { + m_held_lock_info_list.erase(m_held_lock_info_list.iterator_to(*lock_info)); + LockWithPriorityInheritanceInfo::Free(m_kernel, lock_info); + } } -} -void KThread::RestorePriority(KernelCore& kernel, KThread* thread) { - ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(kernel)); + void KThread::RestorePriority(KernelCore& kernel, KThread* thread) { + ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(kernel)); - while (thread != nullptr) { - // We want to inherit priority where possible. - s32 new_priority = thread->GetBasePriority(); - for (const auto& held_lock : thread->m_held_lock_info_list) { - new_priority = - (std::min)(new_priority, held_lock.GetHighestPriorityWaiter()->GetPriority()); - } + while (thread != nullptr) { + // We want to inherit priority where possible. + s32 new_priority = thread->GetBasePriority(); + for (const auto& held_lock : thread->m_held_lock_info_list) { + new_priority = + (std::min)(new_priority, held_lock.GetHighestPriorityWaiter()->GetPriority()); + } - // If the priority we would inherit is not different from ours, don't do anything. - if (new_priority == thread->GetPriority()) { - return; - } + // If the priority we would inherit is not different from ours, don't do anything. + if (new_priority == thread->GetPriority()) { + return; + } - // Get the owner of whatever lock this thread is waiting on. - KThread* const lock_owner = thread->GetLockOwner(); + // Get the owner of whatever lock this thread is waiting on. + KThread* const lock_owner = thread->GetLockOwner(); - // If the thread is waiting on some lock, remove it as a waiter to prevent violating red - // black tree invariants. - if (lock_owner != nullptr) { - lock_owner->RemoveWaiterImpl(thread); - } + // If the thread is waiting on some lock, remove it as a waiter to prevent violating red + // black tree invariants. + if (lock_owner != nullptr) { + lock_owner->RemoveWaiterImpl(thread); + } - // Ensure we don't violate condition variable red black tree invariants. - if (auto* cv_tree = thread->GetConditionVariableTree(); cv_tree != nullptr) { - BeforeUpdatePriority(kernel, cv_tree, thread); - } + // Ensure we don't violate condition variable red black tree invariants. + if (auto* cv_tree = thread->GetConditionVariableTree(); cv_tree != nullptr) { + BeforeUpdatePriority(kernel, cv_tree, thread); + } - // Change the priority. - const s32 old_priority = thread->GetPriority(); - thread->SetPriority(new_priority); + // Change the priority. + const s32 old_priority = thread->GetPriority(); + thread->SetPriority(new_priority); - // Restore the condition variable, if relevant. - if (auto* cv_tree = thread->GetConditionVariableTree(); cv_tree != nullptr) { - AfterUpdatePriority(kernel, cv_tree, thread); - } + // Restore the condition variable, if relevant. + if (auto* cv_tree = thread->GetConditionVariableTree(); cv_tree != nullptr) { + AfterUpdatePriority(kernel, cv_tree, thread); + } - // If we removed the thread from some lock's waiting list, add it back. - if (lock_owner != nullptr) { - lock_owner->AddWaiterImpl(thread); - } + // If we removed the thread from some lock's waiting list, add it back. + if (lock_owner != nullptr) { + lock_owner->AddWaiterImpl(thread); + } - // Update the scheduler. - KScheduler::OnThreadPriorityChanged(kernel, thread, old_priority); + // Update the scheduler. + KScheduler::OnThreadPriorityChanged(kernel, thread, old_priority); - // Continue inheriting priority. - thread = lock_owner; + // Continue inheriting priority. + thread = lock_owner; + } } -} -void KThread::AddWaiter(KThread* thread) { - this->AddWaiterImpl(thread); + void KThread::AddWaiter(KThread* thread) { + this->AddWaiterImpl(thread); - // If the thread has a higher priority than us, we should inherit. - if (thread->GetPriority() < this->GetPriority()) { - RestorePriority(m_kernel, this); + // If the thread has a higher priority than us, we should inherit. + if (thread->GetPriority() < this->GetPriority()) { + RestorePriority(m_kernel, this); + } } -} -void KThread::RemoveWaiter(KThread* thread) { - this->RemoveWaiterImpl(thread); + void KThread::RemoveWaiter(KThread* thread) { + this->RemoveWaiterImpl(thread); - // If our priority is the same as the thread's (and we've inherited), we may need to restore to - // lower priority. - if (this->GetPriority() == thread->GetPriority() && - this->GetPriority() < this->GetBasePriority()) { - RestorePriority(m_kernel, this); + // If our priority is the same as the thread's (and we've inherited), we may need to restore to + // lower priority. + if (this->GetPriority() == thread->GetPriority() && + this->GetPriority() < this->GetBasePriority()) { + RestorePriority(m_kernel, this); + } } -} -KThread* KThread::RemoveWaiterByKey(bool* out_has_waiters, KProcessAddress key, - bool is_kernel_address_key_) { - ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); + KThread* KThread::RemoveWaiterByKey(bool* out_has_waiters, KProcessAddress key, + bool is_kernel_address_key_) { + ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); - // Get the relevant lock info. - auto* lock_info = this->FindHeldLock(key, is_kernel_address_key_); - if (lock_info == nullptr) { - *out_has_waiters = false; - return nullptr; - } + // Get the relevant lock info. + auto* lock_info = this->FindHeldLock(key, is_kernel_address_key_); + if (lock_info == nullptr) { + *out_has_waiters = false; + return nullptr; + } - // Remove the lock info from our held list. - m_held_lock_info_list.erase(m_held_lock_info_list.iterator_to(*lock_info)); + // Remove the lock info from our held list. + m_held_lock_info_list.erase(m_held_lock_info_list.iterator_to(*lock_info)); - // Keep track of how many kernel waiters we have. - if (lock_info->GetIsKernelAddressKey()) { - m_num_kernel_waiters -= lock_info->GetWaiterCount(); - ASSERT(m_num_kernel_waiters >= 0); - KScheduler::SetSchedulerUpdateNeeded(m_kernel); - } + // Keep track of how many kernel waiters we have. + if (lock_info->GetIsKernelAddressKey()) { + m_num_kernel_waiters -= lock_info->GetWaiterCount(); + ASSERT(m_num_kernel_waiters >= 0); + KScheduler::SetSchedulerUpdateNeeded(m_kernel); + } - ASSERT(lock_info->GetWaiterCount() > 0); + ASSERT(lock_info->GetWaiterCount() > 0); - // Remove the highest priority waiter from the lock to be the next owner. - KThread* next_lock_owner = lock_info->GetHighestPriorityWaiter(); - if (lock_info->RemoveWaiter(next_lock_owner)) { - // The new owner was the only waiter. - *out_has_waiters = false; + // Remove the highest priority waiter from the lock to be the next owner. + KThread* next_lock_owner = lock_info->GetHighestPriorityWaiter(); + if (lock_info->RemoveWaiter(next_lock_owner)) { + // The new owner was the only waiter. + *out_has_waiters = false; - // Free the lock info, since it has no waiters. - LockWithPriorityInheritanceInfo::Free(m_kernel, lock_info); - } else { - // There are additional waiters on the lock. - *out_has_waiters = true; + // Free the lock info, since it has no waiters. + LockWithPriorityInheritanceInfo::Free(m_kernel, lock_info); + } else { + // There are additional waiters on the lock. + *out_has_waiters = true; - // Add the lock to the new owner's held list. - next_lock_owner->AddHeldLock(lock_info); + // Add the lock to the new owner's held list. + next_lock_owner->AddHeldLock(lock_info); - // Keep track of any kernel waiters for the new owner. - if (lock_info->GetIsKernelAddressKey()) { - next_lock_owner->m_num_kernel_waiters += lock_info->GetWaiterCount(); - ASSERT(next_lock_owner->m_num_kernel_waiters > 0); + // Keep track of any kernel waiters for the new owner. + if (lock_info->GetIsKernelAddressKey()) { + next_lock_owner->m_num_kernel_waiters += lock_info->GetWaiterCount(); + ASSERT(next_lock_owner->m_num_kernel_waiters > 0); - // NOTE: No need to set scheduler update needed, because we will have already done so - // when removing earlier. + // NOTE: No need to set scheduler update needed, because we will have already done so + // when removing earlier. + } } - } - // If our priority is the same as the next owner's (and we've inherited), we may need to restore - // to lower priority. - if (this->GetPriority() == next_lock_owner->GetPriority() && - this->GetPriority() < this->GetBasePriority()) { - RestorePriority(m_kernel, this); - // NOTE: No need to restore priority on the next lock owner, because it was already the - // highest priority waiter on the lock. - } + // If our priority is the same as the next owner's (and we've inherited), we may need to restore + // to lower priority. + if (this->GetPriority() == next_lock_owner->GetPriority() && + this->GetPriority() < this->GetBasePriority()) { + RestorePriority(m_kernel, this); + // NOTE: No need to restore priority on the next lock owner, because it was already the + // highest priority waiter on the lock. + } - // Return the next lock owner. - return next_lock_owner; -} + // Return the next lock owner. + return next_lock_owner; + } -Result KThread::Run() { - while (true) { - KScopedSchedulerLock lk{m_kernel}; + Result KThread::Run() { + while (true) { + KScopedSchedulerLock lk{m_kernel}; - // If either this thread or the current thread are requesting termination, note it. - R_UNLESS(!this->IsTerminationRequested(), ResultTerminationRequested); - R_UNLESS(!GetCurrentThread(m_kernel).IsTerminationRequested(), ResultTerminationRequested); + // If either this thread or the current thread are requesting termination, note it. + R_UNLESS(!this->IsTerminationRequested(), ResultTerminationRequested); + R_UNLESS(!GetCurrentThread(m_kernel).IsTerminationRequested(), ResultTerminationRequested); - // Ensure our thread state is correct. - R_UNLESS(this->GetState() == ThreadState::Initialized, ResultInvalidState); + // Ensure our thread state is correct. + R_UNLESS(this->GetState() == ThreadState::Initialized, ResultInvalidState); - // If the current thread has been asked to suspend, suspend it and retry. - if (GetCurrentThread(m_kernel).IsSuspended()) { - GetCurrentThread(m_kernel).UpdateState(); - continue; - } + // If the current thread has been asked to suspend, suspend it and retry. + if (GetCurrentThread(m_kernel).IsSuspended()) { + GetCurrentThread(m_kernel).UpdateState(); + continue; + } - // If we're not a kernel thread and we've been asked to suspend, suspend ourselves. - if (KProcess* owner = this->GetOwnerProcess(); owner != nullptr) { - if (this->IsUserThread() && this->IsSuspended()) { - this->UpdateState(); + // If we're not a kernel thread and we've been asked to suspend, suspend ourselves. + if (KProcess* owner = this->GetOwnerProcess(); owner != nullptr) { + if (this->IsUserThread() && this->IsSuspended()) { + this->UpdateState(); + } + owner->IncrementRunningThreadCount(); } - owner->IncrementRunningThreadCount(); - } - // Open a reference, now that we're running. - this->Open(); + // Open a reference, now that we're running. + this->Open(); - // Set our state and finish. - this->SetState(ThreadState::Runnable); + // Set our state and finish. + this->SetState(ThreadState::Runnable); - R_SUCCEED(); + R_SUCCEED(); + } } -} -void KThread::Exit() { - ASSERT(this == GetCurrentThreadPointer(m_kernel)); + void KThread::Exit() { + ASSERT(this == GetCurrentThreadPointer(m_kernel)); - // Release the thread resource hint, running thread count from parent. - if (m_parent != nullptr) { - m_parent->GetResourceLimit()->Release(Kernel::LimitableResource::ThreadCountMax, 0, 1); - m_resource_limit_release_hint = true; - m_parent->DecrementRunningThreadCount(); - } + // Release the thread resource hint, running thread count from parent. + if (m_parent != nullptr) { + m_parent->GetResourceLimit()->Release(Kernel::LimitableResource::ThreadCountMax, 0, 1); + m_resource_limit_release_hint = true; + m_parent->DecrementRunningThreadCount(); + } - // Perform termination. - { - KScopedSchedulerLock sl{m_kernel}; + // Perform termination. + { + KScopedSchedulerLock sl{m_kernel}; - // Disallow all suspension. - m_suspend_allowed_flags = 0; - this->UpdateState(); + // Disallow all suspension. + m_suspend_allowed_flags = 0; + this->UpdateState(); - // Disallow all suspension. - m_suspend_allowed_flags = 0; + // Disallow all suspension. + m_suspend_allowed_flags = 0; + + // Start termination. + this->StartTermination(); - // Start termination. - this->StartTermination(); + // Register the thread as a work task. + KWorkerTaskManager::AddTask(m_kernel, KWorkerTaskManager::WorkerType::Exit, this); + } - // Register the thread as a work task. - KWorkerTaskManager::AddTask(m_kernel, KWorkerTaskManager::WorkerType::Exit, this); + UNREACHABLE_MSG("KThread::Exit() would return"); } - UNREACHABLE_MSG("KThread::Exit() would return"); -} + Result KThread::Terminate() { + ASSERT(this != GetCurrentThreadPointer(m_kernel)); -Result KThread::Terminate() { - ASSERT(this != GetCurrentThreadPointer(m_kernel)); + // Request the thread terminate if it hasn't already. + if (const auto new_state = this->RequestTerminate(); new_state != ThreadState::Terminated) { + // If the thread isn't terminated, wait for it to terminate. + s32 index; + KSynchronizationObject* objects[] = {this}; + R_TRY(KSynchronizationObject::Wait(m_kernel, std::addressof(index), objects, 1, + Svc::WaitInfinite)); + } - // Request the thread terminate if it hasn't already. - if (const auto new_state = this->RequestTerminate(); new_state != ThreadState::Terminated) { - // If the thread isn't terminated, wait for it to terminate. - s32 index; - KSynchronizationObject* objects[] = {this}; - R_TRY(KSynchronizationObject::Wait(m_kernel, std::addressof(index), objects, 1, - Svc::WaitInfinite)); + R_SUCCEED(); } - R_SUCCEED(); -} - -ThreadState KThread::RequestTerminate() { - ASSERT(this != GetCurrentThreadPointer(m_kernel)); + ThreadState KThread::RequestTerminate() { + ASSERT(this != GetCurrentThreadPointer(m_kernel)); - KScopedSchedulerLock sl{m_kernel}; + KScopedSchedulerLock sl{m_kernel}; - // Determine if this is the first termination request. - const bool first_request = [&]() -> bool { - // Perform an atomic compare-and-swap from false to true. - bool expected = false; - return m_termination_requested.compare_exchange_strong(expected, true); - }(); + // Determine if this is the first termination request. + const bool first_request = [&]() -> bool { + // Perform an atomic compare-and-swap from false to true. + bool expected = false; + return m_termination_requested.compare_exchange_strong(expected, true); + }(); + + // If this is the first request, start termination procedure. + if (first_request) { + // If the thread is in initialized state, just change state to terminated. + if (this->GetState() == ThreadState::Initialized) { + m_thread_state = ThreadState::Terminated; + return ThreadState::Terminated; + } - // If this is the first request, start termination procedure. - if (first_request) { - // If the thread is in initialized state, just change state to terminated. - if (this->GetState() == ThreadState::Initialized) { - m_thread_state = ThreadState::Terminated; - return ThreadState::Terminated; - } + // Register the terminating dpc. + this->RegisterDpc(DpcFlag::Terminating); - // Register the terminating dpc. - this->RegisterDpc(DpcFlag::Terminating); + // If the thread is pinned, unpin it. + if (this->GetStackParameters().is_pinned) { + this->GetOwnerProcess()->UnpinThread(this); + } - // If the thread is pinned, unpin it. - if (this->GetStackParameters().is_pinned) { - this->GetOwnerProcess()->UnpinThread(this); - } + // If the thread is suspended, continue it. + if (this->IsSuspended()) { + m_suspend_allowed_flags = 0; + this->UpdateState(); + } - // If the thread is suspended, continue it. - if (this->IsSuspended()) { - m_suspend_allowed_flags = 0; - this->UpdateState(); - } + // Change the thread's priority to be higher than any system thread's. + this->IncreaseBasePriority(TerminatingThreadPriority); - // Change the thread's priority to be higher than any system thread's. - this->IncreaseBasePriority(TerminatingThreadPriority); + // If the thread is runnable, send a termination interrupt to cores it may be running on. + if (this->GetState() == ThreadState::Runnable) { + // NOTE: We do not mask the "current core", because this code may not actually be + // executing from the thread representing the "current core". + if (const u64 core_mask = m_physical_affinity_mask.GetAffinityMask(); core_mask != 0) { + Kernel::KInterruptManager::SendInterProcessorInterrupt(m_kernel, core_mask); + } + } - // If the thread is runnable, send a termination interrupt to cores it may be running on. - if (this->GetState() == ThreadState::Runnable) { - // NOTE: We do not mask the "current core", because this code may not actually be - // executing from the thread representing the "current core". - if (const u64 core_mask = m_physical_affinity_mask.GetAffinityMask(); core_mask != 0) { - Kernel::KInterruptManager::SendInterProcessorInterrupt(m_kernel, core_mask); + // Wake up the thread. + if (this->GetState() == ThreadState::Waiting) { + m_wait_queue->CancelWait(this, ResultTerminationRequested, true); } } - // Wake up the thread. - if (this->GetState() == ThreadState::Waiting) { - m_wait_queue->CancelWait(this, ResultTerminationRequested, true); - } + return this->GetState(); } - return this->GetState(); -} + Result KThread::Sleep(s64 timeout) { + ASSERT(!KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); + ASSERT(this == GetCurrentThreadPointer(m_kernel)); + ASSERT(timeout > 0); + + ThreadQueueImplForKThreadSleep wait_queue(m_kernel); + KHardwareTimer* timer{}; + { + // Setup the scheduling lock and sleep. + KScopedSchedulerLockAndSleep slp(m_kernel, std::addressof(timer), this, timeout); + + // Check if the thread should terminate. + if (this->IsTerminationRequested()) { + slp.CancelSleep(); + R_THROW(ResultTerminationRequested); + } -Result KThread::Sleep(s64 timeout) { - ASSERT(!KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); - ASSERT(this == GetCurrentThreadPointer(m_kernel)); - ASSERT(timeout > 0); - - ThreadQueueImplForKThreadSleep wait_queue(m_kernel); - KHardwareTimer* timer{}; - { - // Setup the scheduling lock and sleep. - KScopedSchedulerLockAndSleep slp(m_kernel, std::addressof(timer), this, timeout); - - // Check if the thread should terminate. - if (this->IsTerminationRequested()) { - slp.CancelSleep(); - R_THROW(ResultTerminationRequested); + // Wait for the sleep to end. + wait_queue.SetHardwareTimer(timer); + this->BeginWait(std::addressof(wait_queue)); + this->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::Sleep); } - // Wait for the sleep to end. - wait_queue.SetHardwareTimer(timer); - this->BeginWait(std::addressof(wait_queue)); - this->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::Sleep); + R_SUCCEED(); } - R_SUCCEED(); -} + void KThread::RequestDummyThreadWait() { + ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); + ASSERT(this->IsDummyThread()); -void KThread::RequestDummyThreadWait() { - ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); - ASSERT(this->IsDummyThread()); + // We will block when the scheduler lock is released. + std::scoped_lock lock{m_dummy_thread_mutex}; + m_dummy_thread_runnable = false; + } - // We will block when the scheduler lock is released. - std::scoped_lock lock{m_dummy_thread_mutex}; - m_dummy_thread_runnable = false; -} + void KThread::DummyThreadBeginWait() { + if (!this->IsDummyThread() || m_kernel.IsPhantomModeForSingleCore()) { + // Occurs in single core mode. + return; + } -void KThread::DummyThreadBeginWait() { - if (!this->IsDummyThread() || m_kernel.IsPhantomModeForSingleCore()) { - // Occurs in single core mode. - return; + // Block until runnable is no longer false. + std::unique_lock lock{m_dummy_thread_mutex}; + m_dummy_thread_cv.wait(lock, [this] { return m_dummy_thread_runnable; }); } - // Block until runnable is no longer false. - std::unique_lock lock{m_dummy_thread_mutex}; - m_dummy_thread_cv.wait(lock, [this] { return m_dummy_thread_runnable; }); -} - -void KThread::DummyThreadEndWait() { - ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); - ASSERT(this->IsDummyThread()); + void KThread::DummyThreadEndWait() { + ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); + ASSERT(this->IsDummyThread()); - // Wake up the waiting thread. - { - std::scoped_lock lock{m_dummy_thread_mutex}; - m_dummy_thread_runnable = true; + // Wake up the waiting thread. + { + std::scoped_lock lock{m_dummy_thread_mutex}; + m_dummy_thread_runnable = true; + } + m_dummy_thread_cv.notify_one(); } - m_dummy_thread_cv.notify_one(); -} -void KThread::BeginWait(KThreadQueue* queue) { - // Set our state as waiting. - this->SetState(ThreadState::Waiting); + void KThread::BeginWait(KThreadQueue* queue) { + // Set our state as waiting. + this->SetState(ThreadState::Waiting); - // Set our wait queue. - m_wait_queue = queue; -} + // Set our wait queue. + m_wait_queue = queue; + } -void KThread::NotifyAvailable(KSynchronizationObject* signaled_object, Result wait_result) { - // Lock the scheduler. - KScopedSchedulerLock sl(m_kernel); + void KThread::NotifyAvailable(KSynchronizationObject* signaled_object, Result wait_result) { + // Lock the scheduler. + KScopedSchedulerLock sl(m_kernel); - // If we're waiting, notify our queue that we're available. - if (this->GetState() == ThreadState::Waiting) { - m_wait_queue->NotifyAvailable(this, signaled_object, wait_result); + // If we're waiting, notify our queue that we're available. + if (this->GetState() == ThreadState::Waiting) { + m_wait_queue->NotifyAvailable(this, signaled_object, wait_result); + } } -} -void KThread::EndWait(Result wait_result) { - // Lock the scheduler. - KScopedSchedulerLock sl(m_kernel); + void KThread::EndWait(Result wait_result) { + // Lock the scheduler. + KScopedSchedulerLock sl(m_kernel); - // If we're waiting, notify our queue that we're available. - if (this->GetState() == ThreadState::Waiting) { - if (m_wait_queue == nullptr) { - // This should never happen, but avoid a hard crash below to get this logged. - ASSERT_MSG(false, "wait_queue is nullptr!"); - return; - } + // If we're waiting, notify our queue that we're available. + if (this->GetState() == ThreadState::Waiting) { + if (m_wait_queue == nullptr) { + // This should never happen, but avoid a hard crash below to get this logged. + ASSERT_MSG(false, "wait_queue is nullptr!"); + return; + } - m_wait_queue->EndWait(this, wait_result); + m_wait_queue->EndWait(this, wait_result); + } } -} -void KThread::CancelWait(Result wait_result, bool cancel_timer_task) { - // Lock the scheduler. - KScopedSchedulerLock sl(m_kernel); + void KThread::CancelWait(Result wait_result, bool cancel_timer_task) { + // Lock the scheduler. + KScopedSchedulerLock sl(m_kernel); - // If we're waiting, notify our queue that we're available. - if (this->GetState() == ThreadState::Waiting) { - m_wait_queue->CancelWait(this, wait_result, cancel_timer_task); + // If we're waiting, notify our queue that we're available. + if (this->GetState() == ThreadState::Waiting) { + m_wait_queue->CancelWait(this, wait_result, cancel_timer_task); + } } -} -void KThread::SetState(ThreadState state) { - KScopedSchedulerLock sl{m_kernel}; + void KThread::SetState(ThreadState state) { + KScopedSchedulerLock sl{m_kernel}; - // Clear debugging state - this->SetWaitReasonForDebugging({}); + // Clear debugging state + this->SetWaitReasonForDebugging({}); - const ThreadState old_state = m_thread_state.load(std::memory_order_relaxed); - m_thread_state.store( - static_cast((old_state & ~ThreadState::Mask) | (state & ThreadState::Mask)), - std::memory_order_relaxed); - if (m_thread_state.load(std::memory_order_relaxed) != old_state) { - KScheduler::OnThreadStateChanged(m_kernel, this, old_state); + const ThreadState old_state = m_thread_state.load(std::memory_order_relaxed); + m_thread_state.store( + static_cast((old_state & ~ThreadState::Mask) | (state & ThreadState::Mask)), + std::memory_order_relaxed); + if (m_thread_state.load(std::memory_order_relaxed) != old_state) { + KScheduler::OnThreadStateChanged(m_kernel, this, old_state); + } } -} - -std::shared_ptr& KThread::GetHostContext() { - return m_host_context; -} -void SetCurrentThread(KernelCore& kernel, KThread* thread) { - kernel.SetCurrentEmuThread(thread); -} + std::shared_ptr& KThread::GetHostContext() { + return m_host_context; + } -KThread* GetCurrentThreadPointer(KernelCore& kernel) { - return kernel.GetCurrentEmuThread(); -} + void SetCurrentThread(KernelCore& kernel, KThread* thread) { + kernel.SetCurrentEmuThread(thread); + } -KThread& GetCurrentThread(KernelCore& kernel) { - return *GetCurrentThreadPointer(kernel); -} + KThread* GetCurrentThreadPointer(KernelCore& kernel) { + return kernel.GetCurrentEmuThread(); + } -KProcess* GetCurrentProcessPointer(KernelCore& kernel) { - return GetCurrentThread(kernel).GetOwnerProcess(); -} + KThread& GetCurrentThread(KernelCore& kernel) { + return *GetCurrentThreadPointer(kernel); + } -KProcess& GetCurrentProcess(KernelCore& kernel) { - return *GetCurrentProcessPointer(kernel); -} + KProcess* GetCurrentProcessPointer(KernelCore& kernel) { + return GetCurrentThread(kernel).GetOwnerProcess(); + } -s32 GetCurrentCoreId(KernelCore& kernel) { - return GetCurrentThread(kernel).GetCurrentCore(); -} + KProcess& GetCurrentProcess(KernelCore& kernel) { + return *GetCurrentProcessPointer(kernel); + } -Core::Memory::Memory& GetCurrentMemory(KernelCore& kernel) { - return GetCurrentProcess(kernel).GetMemory(); -} + s32 GetCurrentCoreId(KernelCore& kernel) { + return GetCurrentThread(kernel).GetCurrentCore(); + } -KScopedDisableDispatch::~KScopedDisableDispatch() { - // If we are shutting down the kernel, none of this is relevant anymore. - if (m_kernel.IsShuttingDown()) { - return; + Core::Memory::Memory& GetCurrentMemory(KernelCore& kernel) { + return GetCurrentProcess(kernel).GetMemory(); } - if (GetCurrentThread(m_kernel).GetDisableDispatchCount() <= 1) { - auto* scheduler = m_kernel.CurrentScheduler(); + KScopedDisableDispatch::~KScopedDisableDispatch() { + // If we are shutting down the kernel, none of this is relevant anymore. + if (m_kernel.IsShuttingDown()) { + return; + } + + if (GetCurrentThread(m_kernel).GetDisableDispatchCount() <= 1) { + auto* scheduler = m_kernel.CurrentScheduler(); - if (scheduler && !m_kernel.IsPhantomModeForSingleCore()) { - scheduler->RescheduleCurrentCore(); + if (scheduler && !m_kernel.IsPhantomModeForSingleCore()) { + scheduler->RescheduleCurrentCore(); + } else { + KScheduler::RescheduleCurrentHLEThread(m_kernel); + } } else { - KScheduler::RescheduleCurrentHLEThread(m_kernel); + GetCurrentThread(m_kernel).EnableDispatch(); } - } else { - GetCurrentThread(m_kernel).EnableDispatch(); } -} - -} // namespace Kernel +} \ No newline at end of file diff --git a/src/core/hle/kernel/k_thread.h b/src/core/hle/kernel/k_thread.h index 6bcb21b4a1..e79704ce4b 100644 --- a/src/core/hle/kernel/k_thread.h +++ b/src/core/hle/kernel/k_thread.h @@ -99,6 +99,12 @@ enum class DpcFlag : u32 { Terminated = (1 << 1), }; +enum class ExceptionFlag : u8 { + IsCallingSvc = 1 << 0, + InExceptionHandler = 1 << 1, +}; +DECLARE_ENUM_FLAG_OPERATORS(ExceptionFlag); + enum class ThreadWaitReasonForDebugging : u32 { None, ///< Thread is not waiting Sleep, ///< Thread is waiting due to a SleepThread SVC @@ -153,7 +159,7 @@ public: /** * Sets the thread's current priority. - * @param priority The new priority. + * @param value The new priority. */ void SetPriority(s32 value) { m_priority = value; @@ -340,6 +346,8 @@ public: void SetInterruptFlag(); void ClearInterruptFlag(); + void UpdateTlsThreadCpuTime(s64 switch_tick); + KThread* GetLockOwner() const; const KAffinityMask& GetAffinityMask() const { @@ -446,6 +454,7 @@ public: bool is_pinned; s32 disable_count; KThread* cur_thread; + std::atomic exception_flags{0}; }; StackParameters& GetStackParameters() { @@ -456,6 +465,16 @@ public: return m_stack_parameters; } + void SetExceptionFlag(ExceptionFlag flag) { + GetStackParameters().exception_flags.fetch_or(static_cast(flag), std::memory_order_relaxed); + } + void ClearExceptionFlag(ExceptionFlag flag) { + GetStackParameters().exception_flags.fetch_and(static_cast(~static_cast(flag)), std::memory_order_relaxed); + } + bool IsExceptionFlagSet(ExceptionFlag flag) const { + return (GetStackParameters().exception_flags.load(std::memory_order_relaxed) & static_cast(flag)) != 0; + } + class QueueEntry { public: constexpr QueueEntry() = default; @@ -511,10 +530,12 @@ public: void SetInExceptionHandler() { this->GetStackParameters().is_in_exception_handler = true; + SetExceptionFlag(ExceptionFlag::InExceptionHandler); } void ClearInExceptionHandler() { this->GetStackParameters().is_in_exception_handler = false; + ClearExceptionFlag(ExceptionFlag::InExceptionHandler); } bool IsInExceptionHandler() const { @@ -523,10 +544,12 @@ public: void SetIsCallingSvc() { this->GetStackParameters().is_calling_svc = true; + SetExceptionFlag(ExceptionFlag::IsCallingSvc); } void ClearIsCallingSvc() { this->GetStackParameters().is_calling_svc = false; + ClearExceptionFlag(ExceptionFlag::IsCallingSvc); } bool IsCallingSvc() const { diff --git a/src/core/hle/kernel/svc/svc_event.cpp b/src/core/hle/kernel/svc/svc_event.cpp index 8e4beb3965..807e604d2f 100644 --- a/src/core/hle/kernel/svc/svc_event.cpp +++ b/src/core/hle/kernel/svc/svc_event.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -34,7 +37,8 @@ Result ClearEvent(Core::System& system, Handle event_handle) { { KScopedAutoObject event = handle_table.GetObject(event_handle); if (event.IsNotNull()) { - R_RETURN(event->Clear()); + event->Clear(); + R_SUCCEED(); } } @@ -42,7 +46,8 @@ Result ClearEvent(Core::System& system, Handle event_handle) { { KScopedAutoObject readable_event = handle_table.GetObject(event_handle); if (readable_event.IsNotNull()) { - R_RETURN(readable_event->Clear()); + readable_event->Clear(); + R_SUCCEED(); } } diff --git a/src/core/hle/service/am/service/application_functions.cpp b/src/core/hle/service/am/service/application_functions.cpp index eacc345e15..e70ed2cccd 100644 --- a/src/core/hle/service/am/service/application_functions.cpp +++ b/src/core/hle/service/am/service/application_functions.cpp @@ -89,6 +89,11 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_, std::shared_ {190, nullptr, "SendServerMaintenanceOverlayNotification"}, {200, nullptr, "GetLastApplicationExitReason"}, {210, D<&IApplicationFunctions::GetUnknownEvent210>, "Unknown210"}, + {220, nullptr, "Unknown220"}, // [20.0.0+] + {300, nullptr, "Unknown300"}, // [20.0.0+] + {310, nullptr, "Unknown310"}, // [20.0.0+] + {320, nullptr, "Unknown320"}, // [20.0.0+] + {330, nullptr, "Unknown330"}, // [20.0.0+] {500, nullptr, "StartContinuousRecordingFlushForDebug"}, {1000, nullptr, "CreateMovieMaker"}, {1001, D<&IApplicationFunctions::PrepareForJit>, "PrepareForJit"}, diff --git a/src/core/hle/service/am/service/audio_controller.cpp b/src/core/hle/service/am/service/audio_controller.cpp index ad731c7bd3..76c1566153 100644 --- a/src/core/hle/service/am/service/audio_controller.cpp +++ b/src/core/hle/service/am/service/audio_controller.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -15,6 +18,7 @@ IAudioController::IAudioController(Core::System& system_) {2, D<&IAudioController::GetLibraryAppletExpectedMasterVolume>, "GetLibraryAppletExpectedMasterVolume"}, {3, D<&IAudioController::ChangeMainAppletMasterVolume>, "ChangeMainAppletMasterVolume"}, {4, D<&IAudioController::SetTransparentVolumeRate>, "SetTransparentVolumeRate"}, + {5, nullptr, "Unknown5"}, }; // clang-format on diff --git a/src/core/hle/service/audio/audio_controller.cpp b/src/core/hle/service/audio/audio_controller.cpp index 4b6f468c92..4456869d35 100644 --- a/src/core/hle/service/audio/audio_controller.cpp +++ b/src/core/hle/service/audio/audio_controller.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -68,6 +71,7 @@ IAudioController::IAudioController(Core::System& system_) {10104, nullptr, "GetAudioOutputChannelCountForPlayReport"}, {10105, nullptr, "BindAudioOutputChannelCountUpdateEventForPlayReport"}, {10106, nullptr, "GetDefaultAudioOutputTargetForPlayReport"}, + {10200, nullptr, "Unknown10200"}, // [20.0.0+] {50000, nullptr, "SetAnalogInputBoostGainForPrototyping"}, {50001, nullptr, "OverrideDefaultTargetForDebug"}, {50003, nullptr, "SetForceOverrideExternalDeviceNameForDebug"}, diff --git a/src/core/hle/service/audio/audio_device.cpp b/src/core/hle/service/audio/audio_device.cpp index 782dddc804..1f7c384846 100644 --- a/src/core/hle/service/audio/audio_device.cpp +++ b/src/core/hle/service/audio/audio_device.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -34,7 +37,8 @@ IAudioDevice::IAudioDevice(Core::System& system_, u64 applet_resource_user_id, u {17, nullptr, "AcquireAudioOutputDeviceNotification"}, // 17.0.0+ {18, nullptr, "ReleaseAudioOutputDeviceNotification"}, // 17.0.0+ {19, nullptr, "SetAudioDeviceOutputVolumeAutoTuneEnabled"}, // 18.0.0+ - {20, nullptr, "IsAudioDeviceOutputVolumeAutoTuneEnabled"} // 18.0.0+ + {20, nullptr, "IsAudioDeviceOutputVolumeAutoTuneEnabled"}, // 18.0.0+ + {21, nullptr, "IsActiveOutputDeviceEstimatedLowLatency"} // 21.0.0+ }; RegisterHandlers(functions); diff --git a/src/core/hle/service/filesystem/fsp/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp/fsp_srv.cpp index 98634c6f60..9d59f96973 100644 --- a/src/core/hle/service/filesystem/fsp/fsp_srv.cpp +++ b/src/core/hle/service/filesystem/fsp/fsp_srv.cpp @@ -298,8 +298,41 @@ Result FSP_SRV::OpenSaveDataFileSystem(OutInterface out_interface, Result FSP_SRV::OpenSaveDataFileSystemBySystemSaveDataId(OutInterface out_interface, FileSys::SaveDataSpaceId space_id, FileSys::SaveDataAttribute attribute) { - LOG_WARNING(Service_FS, "(STUBBED) called, delegating to 51 OpenSaveDataFilesystem"); - R_RETURN(OpenSaveDataFileSystem(out_interface, space_id, attribute)); + LOG_INFO(Service_FS, "called, space_id={}, {}", + space_id, attribute.DebugInfo()); + + R_UNLESS(attribute.system_save_data_id != FileSys::InvalidSystemSaveDataId, + FileSys::ResultInvalidArgument); + + if (attribute.program_id == 0) { + attribute.program_id = program_id; + } + + FileSys::VirtualDir dir{}; + R_TRY(save_data_controller->OpenSaveData(&dir, space_id, attribute)); + + FileSys::StorageId id{}; + switch (space_id) { + case FileSys::SaveDataSpaceId::User: + id = FileSys::StorageId::NandUser; + break; + case FileSys::SaveDataSpaceId::SdSystem: + case FileSys::SaveDataSpaceId::SdUser: + id = FileSys::StorageId::SdCard; + break; + case FileSys::SaveDataSpaceId::System: + id = FileSys::StorageId::NandSystem; + break; + case FileSys::SaveDataSpaceId::Temporary: + case FileSys::SaveDataSpaceId::ProperSystem: + case FileSys::SaveDataSpaceId::SafeMode: + ASSERT(false); + } + + *out_interface = + std::make_shared(system, std::move(dir), SizeGetter::FromStorageId(fsc, id)); + + R_SUCCEED(); } Result FSP_SRV::OpenReadOnlySaveDataFileSystem(OutInterface out_interface, diff --git a/src/core/hle/service/hid/hid_debug_server.cpp b/src/core/hle/service/hid/hid_debug_server.cpp index 0a5d479094..14e48dade3 100644 --- a/src/core/hle/service/hid/hid_debug_server.cpp +++ b/src/core/hle/service/hid/hid_debug_server.cpp @@ -38,6 +38,7 @@ IHidDebugServer::IHidDebugServer(Core::System& system_, std::shared_ptr r {308, nullptr, "SetSevenSixAxisSensorFusionStrength"}, {309, nullptr, "GetSevenSixAxisSensorFusionStrength"}, {310, C<&IHidServer::ResetSevenSixAxisSensorTimestamp>, "ResetSevenSixAxisSensorTimestamp"}, + {320, nullptr, "EnableNpadImu"}, //21.0.0+ + {321, nullptr, "DisableNpadImu"}, //21.0.0+ {400, C<&IHidServer::IsUsbFullKeyControllerEnabled>, "IsUsbFullKeyControllerEnabled"}, {401, nullptr, "EnableUsbFullKeyController"}, {402, nullptr, "IsUsbFullKeyControllerConnected"}, @@ -187,7 +189,56 @@ IHidServer::IHidServer(Core::System& system_, std::shared_ptr r {1002, C<&IHidServer::SetTouchScreenConfiguration>, "SetTouchScreenConfiguration"}, {1003, C<&IHidServer::IsFirmwareUpdateNeededForNotification>, "IsFirmwareUpdateNeededForNotification"}, {1004, C<&IHidServer::SetTouchScreenResolution>, "SetTouchScreenResolution"}, + {1270, nullptr, "DeleteButtonConfigStorageRight"}, + {1271, nullptr, "IsUsingCustomButtonConfig"}, + {1272, nullptr, "IsAnyCustomButtonConfigEnabled"}, + {1273, nullptr, "SetAllCustomButtonConfigEnabled"}, + {1274, nullptr, "SetDefaultButtonConfig"}, + {1275, nullptr, "SetAllDefaultButtonConfig"}, + {1276, nullptr, "SetHidButtonConfigEmbedded"}, + {1277, nullptr, "SetHidButtonConfigFull"}, + {1278, nullptr, "SetHidButtonConfigLeft"}, + {1279, nullptr, "SetHidButtonConfigRight"}, + {1280, nullptr, "GetHidButtonConfigEmbedded"}, + {1281, nullptr, "GetHidButtonConfigFull"}, + {1282, nullptr, "GetHidButtonConfigLeft"}, + {1283, nullptr, "GetHidButtonConfigRight"}, + {1284, nullptr, "GetButtonConfigStorageEmbedded"}, + {1285, nullptr, "GetButtonConfigStorageFull"}, + {1286, nullptr, "GetButtonConfigStorageLeft"}, + {1287, nullptr, "GetButtonConfigStorageRight"}, + {1288, nullptr, "SetButtonConfigStorageEmbedded"}, + {1289, nullptr, "SetButtonConfigStorageFull"}, + {1290, nullptr, "SetButtonConfigStorageLeft"}, + {1291, nullptr, "SetButtonConfigStorageRight"}, + {1308, nullptr, "SetButtonConfigVisible"}, + {1309, nullptr, "IsButtonConfigVisible"}, + {1320, nullptr, "WakeTouchScreenUp"}, + {1321, nullptr, "PutTouchScreenToSleep"}, + {1322, nullptr, "AcquireTouchScreenAsyncWakeCompletedEvent"}, + {1323, nullptr, "StartTouchScreenAutoTuneForSystemSettings"}, + {1324, nullptr, "AcquireTouchScreenAutoTuneCompletedEvent"}, + {1325, nullptr, "IsTouchScreenAutoTuneRequiredForRepairProviderReplacement"}, + {1420, nullptr, "GetAppletResourceProperty"}, {2000, nullptr, "ActivateDigitizer"}, + {3000, nullptr, "GetDebugPadGenericPadMap"}, + {3001, nullptr, "SetDebugPadGenericPadMap"}, + {3002, nullptr, "ResetDebugPadGenericPadMap"}, + {3003, nullptr, "GetDebugPadKeyboardMap"}, + {3004, nullptr, "SetDebugPadKeyboardMap"}, + {3005, nullptr, "ResetDebugPadKeyboardMap"}, + {3006, nullptr, "GetFullKeyGenericPadMap"}, + {3007, nullptr, "SetFullKeyGenericPadMap"}, + {3008, nullptr, "ResetFullKeyGenericPadMap"}, + {3009, nullptr, "GetFullKeyKeyboardMap"}, + {3010, nullptr, "SetFullKeyKeyboardMap"}, + {3011, nullptr, "ResetFullKeyKeyboardMap"}, + {3012, nullptr, "GetDebugPadGenericPadMap"}, //21.0.0+ + {3013, nullptr, "SetDebugPadGenericPadMap"}, //21.0.0+ + {3014, nullptr, "GetDebugPadKeyboardMap"}, //21.0.0+ + {3015, nullptr, "SetDebugPadKeyboardMap"}, //21.0.0+ + {3150, nullptr, "SetMouseLibraryVersion"}, //21.0.0+ + // What? -- {12010, nullptr, "SetButtonConfigLeft"}, }; // clang-format on diff --git a/src/core/hle/service/hid/hid_system_server.cpp b/src/core/hle/service/hid/hid_system_server.cpp index 0f79844c94..7e1949568f 100644 --- a/src/core/hle/service/hid/hid_system_server.cpp +++ b/src/core/hle/service/hid/hid_system_server.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -96,9 +99,12 @@ IHidSystemServer::IHidSystemServer(Core::System& system_, std::shared_ptr o } Result IApplicationManagerInterface::RequestDownloadApplicationControlDataInBackground( - u64 unk, u64 application_id) { - LOG_WARNING(Service_NS, "(STUBBED), app={:016X} unk={}", application_id, unk); + u64 control_source, u64 application_id) { + LOG_WARNING(Service_NS, "(STUBBED), control_source={} app={:016X}", control_source, application_id); R_SUCCEED(); } diff --git a/src/core/hle/service/ns/read_only_application_control_data_interface.cpp b/src/core/hle/service/ns/read_only_application_control_data_interface.cpp index 42d8fc3338..abc0ee8050 100644 --- a/src/core/hle/service/ns/read_only_application_control_data_interface.cpp +++ b/src/core/hle/service/ns/read_only_application_control_data_interface.cpp @@ -27,7 +27,8 @@ IReadOnlyApplicationControlDataInterface::IReadOnlyApplicationControlDataInterfa {2, D<&IReadOnlyApplicationControlDataInterface::ConvertApplicationLanguageToLanguageCode>, "ConvertApplicationLanguageToLanguageCode"}, {3, nullptr, "ConvertLanguageCodeToApplicationLanguage"}, {4, nullptr, "SelectApplicationDesiredLanguage"}, - {5, D<&IReadOnlyApplicationControlDataInterface::GetApplicationControlDataWithIconSize>, "GetApplicationControlDataWithIconSize"}, + {5, D<&IReadOnlyApplicationControlDataInterface::GetApplicationControlDataWithoutIcon>, "GetApplicationControlDataWithIconSize"}, + {19, D<&IReadOnlyApplicationControlDataInterface::GetApplicationControlDataWithoutIcon>, "GetApplicationControlDataWithIconSize"}, }; // clang-format on @@ -163,7 +164,7 @@ Result IReadOnlyApplicationControlDataInterface::GetApplicationControlData( R_SUCCEED(); } -Result IReadOnlyApplicationControlDataInterface::GetApplicationControlDataWithIconSize( +Result IReadOnlyApplicationControlDataInterface::GetApplicationControlDataWithoutIcon( OutBuffer out_buffer, Out out_total_size, ApplicationControlSource application_control_source, @@ -173,19 +174,11 @@ Result IReadOnlyApplicationControlDataInterface::GetApplicationControlDataWithIc constexpr size_t kExpectedBufferSize = 0x14000; constexpr size_t kNACPSize = sizeof(FileSys::RawNACP); - constexpr size_t kMaxIconSize = kExpectedBufferSize - kNACPSize; const FileSys::PatchManager pm{application_id, system.GetFileSystemController(), system.GetContentProvider()}; const auto control = pm.GetControlMetadata(); - const auto size = out_buffer.size(); - - if (size < kExpectedBufferSize) { - LOG_ERROR(Service_NS, "output buffer is too small! (actual={:016X}, required={:016X})", size, kExpectedBufferSize); - R_THROW(ResultUnknown); - } - // Copy NACP if (control.first != nullptr) { const auto bytes = control.first->GetRawBytes(); std::memcpy(out_buffer.data(), bytes.data(), bytes.size()); @@ -193,17 +186,6 @@ Result IReadOnlyApplicationControlDataInterface::GetApplicationControlDataWithIc std::memset(out_buffer.data(), 0, kNACPSize); } - // Copy icon, pad with zeros if needed - size_t icon_size = control.second ? control.second->GetSize() : 0; - if (icon_size > kMaxIconSize) { - icon_size = kMaxIconSize; // Truncate if too large - } - if (control.second != nullptr && icon_size > 0) { - control.second->Read(out_buffer.data() + kNACPSize, icon_size); - } else { - std::memset(out_buffer.data() + kNACPSize, 0, kMaxIconSize); - } - *out_total_size = kExpectedBufferSize; R_SUCCEED(); } diff --git a/src/core/hle/service/ns/read_only_application_control_data_interface.h b/src/core/hle/service/ns/read_only_application_control_data_interface.h index 365e1f7ee3..0a0848bc73 100644 --- a/src/core/hle/service/ns/read_only_application_control_data_interface.h +++ b/src/core/hle/service/ns/read_only_application_control_data_interface.h @@ -31,7 +31,7 @@ public: Out out_actual_size, ApplicationControlSource application_control_source, u64 application_id); - Result GetApplicationControlDataWithIconSize( + Result GetApplicationControlDataWithoutIcon( OutBuffer out_buffer, Out out_total_size, ApplicationControlSource application_control_source, diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp index fcc7f9d392..aab4de39e4 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp @@ -187,7 +187,7 @@ NvResult nvhost_gpu::AllocGPFIFOEx(IoctlAllocGpfifoEx& params, DeviceFD fd) { params.reserved[2]); if (channel_state->initialized) { - LOG_CRITICAL(Service_NVDRV, "Already allocated!"); + LOG_DEBUG(Service_NVDRV, "Channel already initialized; AllocGPFIFOEx returning AlreadyAllocated"); return NvResult::AlreadyAllocated; } @@ -196,6 +196,15 @@ NvResult nvhost_gpu::AllocGPFIFOEx(IoctlAllocGpfifoEx& params, DeviceFD fd) { program_id = session->process->GetProgramId(); } + // Store program id for later lazy initialization + channel_state->program_id = program_id; + + // If address space is not yet bound, defer channel initialization. + if (!channel_state->memory_manager) { + params.fence_out = syncpoint_manager.GetSyncpointFence(channel_syncpoint); + return NvResult::Success; + } + system.GPU().InitChannel(*channel_state, program_id); params.fence_out = syncpoint_manager.GetSyncpointFence(channel_syncpoint); @@ -211,7 +220,7 @@ NvResult nvhost_gpu::AllocGPFIFOEx2(IoctlAllocGpfifoEx& params, DeviceFD fd) { params.reserved[2]); if (channel_state->initialized) { - LOG_CRITICAL(Service_NVDRV, "Already allocated!"); + LOG_DEBUG(Service_NVDRV, "Channel already initialized; AllocGPFIFOEx2 returning AlreadyAllocated"); return NvResult::AlreadyAllocated; } @@ -220,6 +229,15 @@ NvResult nvhost_gpu::AllocGPFIFOEx2(IoctlAllocGpfifoEx& params, DeviceFD fd) { program_id = session->process->GetProgramId(); } + // Store program id for later lazy initialization + channel_state->program_id = program_id; + + // If address space is not yet bound, defer channel initialization. + if (!channel_state->memory_manager) { + params.fence_out = syncpoint_manager.GetSyncpointFence(channel_syncpoint); + return NvResult::Success; + } + system.GPU().InitChannel(*channel_state, program_id); params.fence_out = syncpoint_manager.GetSyncpointFence(channel_syncpoint); @@ -244,9 +262,10 @@ NvResult nvhost_gpu::AllocateObjectContext(IoctlAllocObjCtx& params) { LOG_DEBUG(Service_NVDRV, "called, class_num={:#X}, flags={:#X}, obj_id={:#X}", params.class_num, params.flags, params.obj_id); - if (!channel_state || !channel_state->initialized) { - LOG_CRITICAL(Service_NVDRV, "No address space bound to allocate a object context!"); - return NvResult::NotInitialized; + // Do not require channel initialization here: some clients allocate contexts before binding. + if (!channel_state) { + LOG_ERROR(Service_NVDRV, "No channel state available!"); + return NvResult::InvalidState; } std::scoped_lock lk(channel_mutex); @@ -268,11 +287,12 @@ NvResult nvhost_gpu::AllocateObjectContext(IoctlAllocObjCtx& params) { } if (ctxObjs[ctx_class_number_index].has_value()) { - LOG_ERROR(Service_NVDRV, "Object context for class {:#X} already allocated on this channel", - params.class_num); + LOG_WARNING(Service_NVDRV, "Object context for class {:#X} already allocated on this channel", + params.class_num); return NvResult::AlreadyAllocated; } + // Defer actual hardware context binding until channel is initialized. ctxObjs[ctx_class_number_index] = params; return NvResult::Success; @@ -326,6 +346,11 @@ NvResult nvhost_gpu::SubmitGPFIFOImpl(IoctlSubmitGpfifo& params, Tegra::CommandL std::scoped_lock lock(channel_mutex); + // Lazily initialize channel when address space is available + if (!channel_state->initialized && channel_state->memory_manager) { + system.GPU().InitChannel(*channel_state, channel_state->program_id); + } + const auto bind_id = channel_state->bind_id; auto& flags = params.flags; diff --git a/src/core/hle/service/olsc/remote_storage_controller.cpp b/src/core/hle/service/olsc/remote_storage_controller.cpp index 81d9c96ab5..7e04b7c042 100644 --- a/src/core/hle/service/olsc/remote_storage_controller.cpp +++ b/src/core/hle/service/olsc/remote_storage_controller.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -26,14 +29,20 @@ IRemoteStorageController::IRemoteStorageController(Core::System& system_) {16, nullptr, "CreateCleanupToDeleteSaveDataArchiveInfoTask"}, {17, nullptr, "ListDataInfo"}, {18, nullptr, "GetDataInfo"}, - {19, nullptr, "Unknown19"}, + {19, nullptr, "GetDataInfoCacheUpdateNativeHandleHolder"}, {20, nullptr, "CreateSaveDataArchiveInfoCacheForSaveDataBackupUpdationTask"}, {21, nullptr, "ListSecondarySaves"}, {22, D<&IRemoteStorageController::GetSecondarySave>, "GetSecondarySave"}, {23, nullptr, "TouchSecondarySave"}, {24, nullptr, "GetSecondarySaveDataInfo"}, {25, nullptr, "RegisterDownloadSaveDataTransferTaskForAutonomyRegistration"}, - {900, nullptr, "Unknown900"}, + {26, nullptr, "Unknown26"}, //20.0.0+ + {27, nullptr, "Unknown27"}, //20.0.0+ + {28, nullptr, "Unknown28"}, //20.0.0+ + {29, nullptr, "Unknown29"}, //21.0.0+ + {800, nullptr, "Unknown800"}, //20.0.0+ + {900, nullptr, "SetLoadedDataMissing"}, + {901, nullptr, "Unknown901"}, //20.2.0+ }; // clang-format on diff --git a/src/core/hle/service/prepo/prepo.cpp b/src/core/hle/service/prepo/prepo.cpp index 14e8df63a0..9f03373499 100644 --- a/src/core/hle/service/prepo/prepo.cpp +++ b/src/core/hle/service/prepo/prepo.cpp @@ -1,8 +1,14 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "common/hex_util.h" #include "common/logging/log.h" +#include "common/uuid.h" +#include + #include "core/core.h" #include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/ipc_helpers.h" @@ -27,8 +33,10 @@ public: {10200, &PlayReport::RequestImmediateTransmission, "RequestImmediateTransmission"}, {10300, &PlayReport::GetTransmissionStatus, "GetTransmissionStatus"}, {10400, &PlayReport::GetSystemSessionId, "GetSystemSessionId"}, - {20100, &PlayReport::SaveSystemReport, "SaveSystemReport"}, - {20101, &PlayReport::SaveSystemReportWithUser, "SaveSystemReportWithUser"}, + {20100, &PlayReport::SaveSystemReportOld, "SaveSystemReport"}, +{20101, &PlayReport::SaveSystemReportWithUserOld, "SaveSystemReportWithUser"}, +{20102, &PlayReport::SaveSystemReport, "SaveSystemReport"}, +{20103, &PlayReport::SaveSystemReportWithUser, "SaveSystemReportWithUser"}, {20200, nullptr, "SetOperationMode"}, {30100, nullptr, "ClearStorage"}, {30200, nullptr, "ClearStatistics"}, @@ -121,7 +129,7 @@ private: rb.Push(system_session_id); } - void SaveSystemReport(HLERequestContext& ctx) { + void SaveSystemReportOld(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto title_id = rp.PopRaw(); @@ -131,14 +139,11 @@ private: LOG_DEBUG(Service_PREPO, "called, title_id={:016X}, data1_size={:016X}, data2_size={:016X}", title_id, data1.size(), data2.size()); - const auto& reporter{system.GetReporter()}; - reporter.SavePlayReport(Core::Reporter::PlayReportType::System, title_id, {data1, data2}); - IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } - void SaveSystemReportWithUser(HLERequestContext& ctx) { + void SaveSystemReportWithUserOld(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto user_id = rp.PopRaw(); const auto title_id = rp.PopRaw(); @@ -146,10 +151,12 @@ private: const auto data1 = ctx.ReadBufferA(0); const auto data2 = ctx.ReadBufferX(0); + Common::UUID uuid{}; + std::memcpy(uuid.uuid.data(), user_id.data(), sizeof(Common::UUID)); + LOG_DEBUG(Service_PREPO, - "called, user_id={:016X}{:016X}, title_id={:016X}, data1_size={:016X}, " - "data2_size={:016X}", - user_id[1], user_id[0], title_id, data1.size(), data2.size()); + "called, user_id={}, title_id={:016X}, data1_size={:016X}, data2_size={:016X}", + uuid.FormattedString(), title_id, data1.size(), data2.size()); const auto& reporter{system.GetReporter()}; reporter.SavePlayReport(Core::Reporter::PlayReportType::System, title_id, {data1, data2}, @@ -158,6 +165,53 @@ private: IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } + + // (21.0.0+) buffers: [0x9 (X), 0x5 (A)], inbytes: 0x10 + void SaveSystemReport(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto field0 = rp.PopRaw(); + const auto title_id = rp.PopRaw(); + + const auto data_x = ctx.ReadBufferX(0); + const auto data_a = ctx.ReadBufferA(0); + + LOG_DEBUG(Service_PREPO, + "called, field0={}, title_id={:016X}, data_a_size={}, data_x_size={}", + field0, title_id, data_a.size(), data_x.size()); + + const auto& reporter{system.GetReporter()}; + reporter.SavePlayReport(Core::Reporter::PlayReportType::System, title_id, {data_a, data_x}); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + // (21.0.0+) buffers: [0x9 (X), 0x5 (A)], inbytes: 0x20 + void SaveSystemReportWithUser(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + // 21.0.0+: field0 (u64), user_id (u128), title_id (u64) + const auto field0 = rp.PopRaw(); + const auto user_id = rp.PopRaw(); + const auto title_id = rp.PopRaw(); + + const auto data_x = ctx.ReadBufferX(0); + const auto data_a = ctx.ReadBufferA(0); + + Common::UUID uuid{}; + std::memcpy(uuid.uuid.data(), user_id.data(), sizeof(Common::UUID)); + + LOG_DEBUG(Service_PREPO, + "called, user_id={}, field0={:016X}, title_id={:016X}, data_a_size={}, data_x_size={}", + uuid.FormattedString(), field0, title_id, data_a.size(), data_x.size()); + + const auto& reporter{system.GetReporter()}; + reporter.SavePlayReport(Core::Reporter::PlayReportType::System, title_id, {data_a, data_x}, + std::nullopt, user_id); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } }; void LoopProcess(Core::System& system) { diff --git a/src/core/hle/service/set/firmware_debug_settings_server.cpp b/src/core/hle/service/set/firmware_debug_settings_server.cpp index b3a5e623b6..b7c66047e6 100644 --- a/src/core/hle/service/set/firmware_debug_settings_server.cpp +++ b/src/core/hle/service/set/firmware_debug_settings_server.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -18,6 +21,8 @@ IFirmwareDebugSettingsServer::IFirmwareDebugSettingsServer(Core::System& system_ {21, nullptr, "SetAllowedSslHosts"}, {22, nullptr, "SetHostFsMountPoint"}, {23, nullptr, "SetMemoryUsageRateFlag"}, + {24, nullptr, "CommitSettings"}, //20.0.0+ + {27, nullptr, "SetHttpAuthConfigs"}, //21.0.0+ }; // clang-format on diff --git a/src/core/hle/service/set/system_settings_server.cpp b/src/core/hle/service/set/system_settings_server.cpp index 22e12b1b9c..c9a2a1f497 100644 --- a/src/core/hle/service/set/system_settings_server.cpp +++ b/src/core/hle/service/set/system_settings_server.cpp @@ -307,6 +307,19 @@ ISystemSettingsServer::ISystemSettingsServer(Core::System& system_) {222, nullptr, "SetForceMonauralOutputFlag"}, //17.0.0+ {251, nullptr, "GetAccountIdentificationSettings"}, //18.0.0+ {252, nullptr, "SetAccountIdentificationSettings"}, //18.0.0+ + {263, nullptr, "AcquireVphymDirtyFlagEventHandle"}, //20.0.0+ + {264, nullptr, "GetVphymDirtyFlags"}, //20.0.0+ + {282, nullptr, "ConvertToProductModel"}, //20.0.0+ + {283, nullptr, "ConvertToProductModelName"}, //20.0.0+ + {289, nullptr, "GetDefaultAccountIdentificationFlagSet"}, //20.0.0+ + {300, nullptr, "AcquirePushNotificationDirtyFlagEventHandle"}, //20.0.0+ + {301, nullptr, "GetPushNotificationDirtyFlags"}, //20.0.0+ + {306, nullptr, "GetPinCodeReregistrationGuideAccounts"}, //20.0.0+ + {307, nullptr, "SetPinCodeReregistrationGuideAccounts"}, //20.0.0+ + {315, C<&ISystemSettingsServer::GetHttpAuthConfigs>, "GetHttpAuthConfigs"}, //21.0.0+ + {319, nullptr, "GetAccountUserSettings"}, //21.0.0+ + {320, nullptr, "SetAccountUserSettings"}, //21.0.0+ + {321, nullptr, "GetDefaultAccountUserSettings"}, //21.0.0+ }; // clang-format on @@ -1323,6 +1336,12 @@ Result ISystemSettingsServer::SetPanelCrcMode(s32 panel_crc_mode) { R_SUCCEED(); } +Result ISystemSettingsServer::GetHttpAuthConfigs(Out out_count, OutBuffer out_configs) { + LOG_WARNING(Service_SET, "(STUBBED) called, buffer_size={}", out_configs.size()); + *out_count = 0; + R_SUCCEED(); +} + void ISystemSettingsServer::SetupSettings() { auto system_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::NANDDir) / "system/save/8000000000000050"; diff --git a/src/core/hle/service/set/system_settings_server.h b/src/core/hle/service/set/system_settings_server.h index c67cf47ff6..3d4b1ecb1e 100644 --- a/src/core/hle/service/set/system_settings_server.h +++ b/src/core/hle/service/set/system_settings_server.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -157,6 +160,7 @@ public: Result GetFieldTestingFlag(Out out_field_testing_flag); Result GetPanelCrcMode(Out out_panel_crc_mode); Result SetPanelCrcMode(s32 panel_crc_mode); + Result GetHttpAuthConfigs(Out out_count, OutBuffer out_configs); private: bool LoadSettingsFile(std::filesystem::path& path, auto&& default_func); diff --git a/src/frontend_common/firmware_manager.cpp b/src/frontend_common/firmware_manager.cpp index be3014e3f2..6f3f409aa8 100644 --- a/src/frontend_common/firmware_manager.cpp +++ b/src/frontend_common/firmware_manager.cpp @@ -121,18 +121,12 @@ FirmwareManager::FirmwareCheckResult FirmwareManager::VerifyFirmware(Core::Syste return ErrorFirmwareMissing; } else { const auto pair = GetFirmwareVersion(system); - const auto firmware_data = pair.first; const auto result = pair.second; if (result.IsError()) { LOG_INFO(Frontend, "Unable to read firmware"); return ErrorFirmwareCorrupted; } - - // TODO: update this whenever newer firmware is properly supported - if (firmware_data.major > 20) { - return ErrorFirmwareTooNew; - } } return FirmwareGood; diff --git a/src/frontend_common/firmware_manager.h b/src/frontend_common/firmware_manager.h index 40814a11f8..94469e9295 100644 --- a/src/frontend_common/firmware_manager.h +++ b/src/frontend_common/firmware_manager.h @@ -53,21 +53,10 @@ inline constexpr bool GameRequiresFirmware(u64 program_id) != FIRMWARE_REQUIRED_GAMES.end(); } - enum FirmwareCheckResult { FirmwareGood, ErrorFirmwareMissing, ErrorFirmwareCorrupted, - ErrorFirmwareTooNew, -}; - -static constexpr std::array FIRMWARE_CHECK_STRINGS = { - "", - "Firmware missing. Firmware is required to run certain games and use the Home Menu. " - "Eden only works with firmware 19.0.1 and earlier.", - "Firmware reported as present, but was unable to be read. Check for decryption keys and " - "redump firmware if necessary.", - "Firmware is too new or could not be read. Eden only works with firmware 19.0.1 and earlier.", }; /** @@ -100,16 +89,6 @@ inline bool CheckFirmwarePresence(Core::System &system) */ FirmwareCheckResult VerifyFirmware(Core::System &system); -/** - * \brief Get a string representation of a result from CheckFirmwareVersion. - * \param result The result code. - * \return A string representation of the passed result code. - */ -inline constexpr const char *GetFirmwareCheckString(FirmwareCheckResult result) -{ - return FIRMWARE_CHECK_STRINGS.at(static_cast(result)); -} - /** * @brief Get the currently installed firmware version. * @param system The system to check firmware on. diff --git a/src/qt_common/qt_string_lookup.h b/src/qt_common/qt_string_lookup.h index b33bd044c2..5a19fb00d3 100644 --- a/src/qt_common/qt_string_lookup.h +++ b/src/qt_common/qt_string_lookup.h @@ -11,8 +11,8 @@ /// Small helper to look up enums. /// res = the result code /// base = the base matching value in the StringKey table -#define LOOKUP_ENUM(res, base) StringLookup::Lookup( \ - static_cast((int) res + (int) StringLookup::base)) +#define LOOKUP_ENUM(res, base) QtCommon::StringLookup::Lookup( \ + QtCommon::StringLookup::StringKey((int) res + (int) QtCommon::StringLookup::base)) namespace QtCommon::StringLookup { @@ -20,11 +20,11 @@ Q_NAMESPACE // TODO(crueter): QML interface enum StringKey { - SavesTooltip, - ShadersTooltip, - UserNandTooltip, - SysNandTooltip, - ModsTooltip, + DataManagerSavesTooltip, + DataManagerShadersTooltip, + DataManagerUserNandTooltip, + DataManagerSysNandTooltip, + DataManagerModsTooltip, // Key install results KeyInstallSuccess, @@ -40,6 +40,10 @@ enum StringKey { FwInstallFailedCopy, FwInstallFailedCorrupted, + // Firmware Check results + FwCheckErrorFirmwareMissing, + FwCheckErrorFirmwareCorrupted, + // user data migrator MigrationPromptPrefix, MigrationPrompt, @@ -55,18 +59,19 @@ enum StringKey { KvdbMisaligned, KvdbNoImens, RyujinxNoSaveId, - }; -static const constexpr frozen::map strings = { +// NB: the constexpr check always succeeds (in clangd at least) if size arg < size +// always triple-check the size arg +static const constexpr frozen::map strings = { // 0-4 - {SavesTooltip, + {DataManagerSavesTooltip, QT_TR_NOOP("Contains game save data. DO NOT REMOVE UNLESS YOU KNOW WHAT YOU'RE DOING!")}, - {ShadersTooltip, + {DataManagerShadersTooltip, QT_TR_NOOP("Contains Vulkan and OpenGL pipeline caches. Generally safe to remove.")}, - {UserNandTooltip, QT_TR_NOOP("Contains updates and DLC for games.")}, - {SysNandTooltip, QT_TR_NOOP("Contains firmware and applet data.")}, - {ModsTooltip, QT_TR_NOOP("Contains game mods, patches, and cheats.")}, + {DataManagerUserNandTooltip, QT_TR_NOOP("Contains updates and DLC for games.")}, + {DataManagerSysNandTooltip, QT_TR_NOOP("Contains firmware and applet data.")}, + {DataManagerModsTooltip, QT_TR_NOOP("Contains game mods, patches, and cheats.")}, // Key install // 5-9 @@ -91,8 +96,17 @@ static const constexpr frozen::map strings = { "Firmware installation cancelled, firmware may be in a bad state or corrupted. Restart " "Eden or re-install firmware.")}, + {FwCheckErrorFirmwareMissing, + QT_TR_NOOP( + "Firmware missing. Firmware is required to run certain games and use the Home Menu. " + "Versions 19.0.1 or earlier are recommended, as 20.0.0+ is currently experimental.")}, + {FwCheckErrorFirmwareCorrupted, + QT_TR_NOOP( + "Firmware reported as present, but was unable to be read. Check for decryption keys and " + "redump firmware if necessary.")}, + // migrator - // 15-20 + // 17-22 {MigrationPromptPrefix, QT_TR_NOOP("Eden has detected user data for the following emulators:")}, {MigrationPrompt, QT_TR_NOOP("Would you like to migrate your data for use in Eden?\n" @@ -113,7 +127,7 @@ static const constexpr frozen::map strings = { "This is recommended if you want to share data between emulators.")}, // why am I writing these comments again - // 21-26 + // 23-28 {KvdbNonexistent, QT_TR_NOOP("Ryujinx title database does not exist.")}, {KvdbNoHeader, QT_TR_NOOP("Invalid header on Ryujinx title database.")}, {KvdbInvalidMagic, QT_TR_NOOP("Invalid magic header on Ryujinx title database.")}, diff --git a/src/yuzu/data_dialog.cpp b/src/yuzu/data_dialog.cpp index 4aaa12ce41..8caa362bdf 100644 --- a/src/yuzu/data_dialog.cpp +++ b/src/yuzu/data_dialog.cpp @@ -27,7 +27,7 @@ DataDialog::DataDialog(QWidget *parent) // TODO: Should we make this a single widget that pulls data from a model? #define WIDGET(label, name) \ ui->page->addWidget(new DataWidget(FrontendCommon::DataManager::DataDir::name, \ - QtCommon::StringLookup::name##Tooltip, \ + QtCommon::StringLookup::DataManager##name##Tooltip, \ QStringLiteral(#name), \ this)); \ ui->labels->addItem(label); @@ -80,39 +80,27 @@ DataWidget::DataWidget(FrontendCommon::DataManager::DataDir data_dir, void DataWidget::clear() { - std::string user_id{}; - if (m_dir == FrontendCommon::DataManager::DataDir::Saves) { - user_id = GetProfileIDString(); - } + std::string user_id = selectProfile(); QtCommon::Content::ClearDataDir(m_dir, user_id); scan(); } void DataWidget::open() { - std::string user_id{}; - if (m_dir == FrontendCommon::DataManager::DataDir::Saves) { - user_id = GetProfileIDString(); - } + std::string user_id = selectProfile(); QDesktopServices::openUrl(QUrl::fromLocalFile( QString::fromStdString(FrontendCommon::DataManager::GetDataDirString(m_dir, user_id)))); } void DataWidget::upload() { - std::string user_id{}; - if (m_dir == FrontendCommon::DataManager::DataDir::Saves) { - user_id = GetProfileIDString(); - } + std::string user_id = selectProfile(); QtCommon::Content::ExportDataDir(m_dir, user_id, m_exportName); } void DataWidget::download() { - std::string user_id{}; - if (m_dir == FrontendCommon::DataManager::DataDir::Saves) { - user_id = GetProfileIDString(); - } + std::string user_id = selectProfile(); QtCommon::Content::ImportDataDir(m_dir, user_id, std::bind(&DataWidget::scan, this)); } @@ -131,3 +119,13 @@ void DataWidget::scan() { watcher->setFuture( QtConcurrent::run([this]() { return FrontendCommon::DataManager::DataDirSize(m_dir); })); } + +std::string DataWidget::selectProfile() +{ + std::string user_id{}; + if (m_dir == FrontendCommon::DataManager::DataDir::Saves) { + user_id = GetProfileIDString(); + } + + return user_id; +} diff --git a/src/yuzu/main_window.cpp b/src/yuzu/main_window.cpp index 1547c96dd7..a6e284856f 100644 --- a/src/yuzu/main_window.cpp +++ b/src/yuzu/main_window.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later // Qt on macOS doesn't define VMA shit +#include "qt_common/qt_string_lookup.h" #if defined(QT_STATICPLUGIN) && !defined(__APPLE__) #undef VMA_IMPLEMENTATION #endif @@ -4058,35 +4059,18 @@ void MainWindow::OnOpenControllerMenu() { void MainWindow::OnHomeMenu() { auto result = FirmwareManager::VerifyFirmware(*QtCommon::system.get()); + using namespace QtCommon::StringLookup; + switch (result) { case FirmwareManager::ErrorFirmwareMissing: QMessageBox::warning(this, tr("No firmware available"), - tr("Please install firmware to use the Home Menu.")); + Lookup(FwCheckErrorFirmwareMissing)); return; case FirmwareManager::ErrorFirmwareCorrupted: QMessageBox::warning(this, tr("Firmware Corrupted"), - tr(FirmwareManager::GetFirmwareCheckString(result))); + Lookup(FwCheckErrorFirmwareCorrupted)); return; - case FirmwareManager::ErrorFirmwareTooNew: { - if (!UISettings::values.show_fw_warning.GetValue()) break; - - QMessageBox box(QMessageBox::Warning, - tr("Firmware Too New"), - tr(FirmwareManager::GetFirmwareCheckString(result)) + tr("\nContinue anyways?"), - QMessageBox::Yes | QMessageBox::No, - this); - - QCheckBox *checkbox = new QCheckBox(tr("Don't show again")); - box.setCheckBox(checkbox); - - int button = box.exec(); - if (checkbox->isChecked()) { - UISettings::values.show_fw_warning.SetValue(false); - } - - if (button == static_cast(QMessageBox::No)) return; - break; - } default: + default: break; }