// Copyright 2021 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #include "common/assert.h" #include "common/common_types.h" #include "core/hle/kernel/k_scheduler.h" #include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h" #include "core/hle/kernel/k_synchronization_object.h" #include "core/hle/kernel/k_thread.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/svc_results.h" namespace Kernel { ResultCode KSynchronizationObject::Wait(KernelCore& kernel, s32* out_index, KSynchronizationObject** objects, const s32 num_objects, s64 timeout) { // Allocate space on stack for thread nodes. std::vector thread_nodes(num_objects); // Prepare for wait. KThread* thread = kernel.CurrentScheduler()->GetCurrentThread(); Handle timer = InvalidHandle; { // Setup the scheduling lock and sleep. KScopedSchedulerLockAndSleep slp(kernel, timer, thread, timeout); // Check if any of the objects are already signaled. for (auto i = 0; i < num_objects; ++i) { ASSERT(objects[i] != nullptr); if (objects[i]->IsSignaled()) { *out_index = i; slp.CancelSleep(); return RESULT_SUCCESS; } } // Check if the timeout is zero. if (timeout == 0) { slp.CancelSleep(); return Svc::ResultTimedOut; } // Check if the thread should terminate. if (thread->IsTerminationRequested()) { slp.CancelSleep(); return Svc::ResultTerminationRequested; } // Check if waiting was canceled. if (thread->IsWaitCancelled()) { slp.CancelSleep(); thread->ClearWaitCancelled(); return Svc::ResultCancelled; } // Add the waiters. for (auto i = 0; i < num_objects; ++i) { thread_nodes[i].thread = thread; thread_nodes[i].next = nullptr; if (objects[i]->thread_list_tail == nullptr) { objects[i]->thread_list_head = std::addressof(thread_nodes[i]); } else { objects[i]->thread_list_tail->next = std::addressof(thread_nodes[i]); } objects[i]->thread_list_tail = std::addressof(thread_nodes[i]); } // For debugging only thread->SetWaitObjectsForDebugging({objects, static_cast(num_objects)}); // Mark the thread as waiting. thread->SetCancellable(); thread->SetSyncedObject(nullptr, Svc::ResultTimedOut); thread->SetState(ThreadState::Waiting); thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::Synchronization); } // The lock/sleep is done, so we should be able to get our result. // Thread is no longer cancellable. thread->ClearCancellable(); // For debugging only thread->SetWaitObjectsForDebugging({}); // Cancel the timer as needed. if (timer != InvalidHandle) { auto& time_manager = kernel.TimeManager(); time_manager.UnscheduleTimeEvent(timer); } // Get the wait result. ResultCode wait_result{RESULT_SUCCESS}; s32 sync_index = -1; { KScopedSchedulerLock lock(kernel); KSynchronizationObject* synced_obj; wait_result = thread->GetWaitResult(std::addressof(synced_obj)); for (auto i = 0; i < num_objects; ++i) { // Unlink the object from the list. ThreadListNode* prev_ptr = reinterpret_cast(std::addressof(objects[i]->thread_list_head)); ThreadListNode* prev_val = nullptr; ThreadListNode *prev, *tail_prev; do { prev = prev_ptr; prev_ptr = prev_ptr->next; tail_prev = prev_val; prev_val = prev_ptr; } while (prev_ptr != std::addressof(thread_nodes[i])); if (objects[i]->thread_list_tail == std::addressof(thread_nodes[i])) { objects[i]->thread_list_tail = tail_prev; } prev->next = thread_nodes[i].next; if (objects[i] == synced_obj) { sync_index = i; } } } // Set output. *out_index = sync_index; return wait_result; } KSynchronizationObject::KSynchronizationObject(KernelCore& kernel) : Object{kernel} {} KSynchronizationObject ::~KSynchronizationObject() = default; void KSynchronizationObject::NotifyAvailable(ResultCode result) { KScopedSchedulerLock lock(kernel); // If we're not signaled, we've nothing to notify. if (!this->IsSignaled()) { return; } // Iterate over each thread. for (auto* cur_node = thread_list_head; cur_node != nullptr; cur_node = cur_node->next) { KThread* thread = cur_node->thread; if (thread->GetState() == ThreadState::Waiting) { thread->SetSyncedObject(this, result); thread->SetState(ThreadState::Runnable); } } } std::vector KSynchronizationObject::GetWaitingThreadsForDebugging() const { std::vector threads; // If debugging, dump the list of waiters. { KScopedSchedulerLock lock(kernel); for (auto* cur_node = thread_list_head; cur_node != nullptr; cur_node = cur_node->next) { threads.emplace_back(cur_node->thread); } } return threads; } } // namespace Kernel