/* * Copyright (c) 2018-2020 Atmosphère-NX * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include namespace ams::kern { void KSynchronizationObject::Finalize() { MESOSPHERE_ASSERT_THIS(); /* If auditing, ensure that the object has no waiters. */ #if defined(MESOSPHERE_BUILD_FOR_AUDITING) { KScopedSchedulerLock sl; for (auto *cur_node = m_thread_list_head; cur_node != nullptr; cur_node = cur_node->next) { KThread *thread = cur_node->thread; MESOSPHERE_LOG("KSynchronizationObject::Finalize(%p) with %p (id=%ld) waiting.\n", this, thread, thread->GetId()); } } #endif this->OnFinalizeSynchronizationObject(); KAutoObject::Finalize(); } Result KSynchronizationObject::Wait(s32 *out_index, KSynchronizationObject **objects, const s32 num_objects, s64 timeout) { /* Allocate space on stack for thread nodes. */ ThreadListNode *thread_nodes = static_cast(__builtin_alloca(sizeof(ThreadListNode) * num_objects)); /* Prepare for wait. */ KThread *thread = GetCurrentThreadPointer(); KHardwareTimer *timer; { /* Setup the scheduling lock and sleep. */ KScopedSchedulerLockAndSleep slp(std::addressof(timer), thread, timeout); /* Check if any of the objects are already signaled. */ for (auto i = 0; i < num_objects; ++i) { AMS_ASSERT(objects[i] != nullptr); if (objects[i]->IsSignaled()) { *out_index = i; slp.CancelSleep(); return ResultSuccess(); } } /* 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]->m_thread_list_tail == nullptr) { objects[i]->m_thread_list_head = std::addressof(thread_nodes[i]); } else { objects[i]->m_thread_list_tail->next = std::addressof(thread_nodes[i]); } objects[i]->m_thread_list_tail = std::addressof(thread_nodes[i]); } /* Mark the thread as waiting. */ thread->SetCancellable(); thread->SetSyncedObject(nullptr, svc::ResultTimedOut()); thread->SetState(KThread::ThreadState_Waiting); } /* The lock/sleep is done, so we should be able to get our result. */ /* Thread is no longer cancellable. */ thread->ClearCancellable(); /* Cancel the timer as needed. */ if (timer != nullptr) { timer->CancelTask(thread); } /* Get the wait result. */ Result wait_result; s32 sync_index = -1; { KScopedSchedulerLock lk; 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]->m_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]->m_thread_list_tail == std::addressof(thread_nodes[i])) { objects[i]->m_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; } void KSynchronizationObject::NotifyAvailable(Result result) { MESOSPHERE_ASSERT_THIS(); KScopedSchedulerLock sl; /* If we're not signaled, we've nothing to notify. */ if (!this->IsSignaled()) { return; } /* Iterate over each thread. */ for (auto *cur_node = m_thread_list_head; cur_node != nullptr; cur_node = cur_node->next) { KThread *thread = cur_node->thread; if (thread->GetState() == KThread::ThreadState_Waiting) { thread->SetSyncedObject(this, result); thread->SetState(KThread::ThreadState_Runnable); } } } void KSynchronizationObject::DumpWaiters() { MESOSPHERE_ASSERT_THIS(); /* If debugging, dump the list of waiters. */ #if defined(MESOSPHERE_BUILD_FOR_DEBUGGING) { KScopedSchedulerLock sl; MESOSPHERE_RELEASE_LOG("Threads waiting on %p:\n", this); for (auto *cur_node = m_thread_list_head; cur_node != nullptr; cur_node = cur_node->next) { KThread *thread = cur_node->thread; if (KProcess *process = thread->GetOwnerProcess(); process != nullptr) { MESOSPHERE_RELEASE_LOG(" %p tid=%ld pid=%ld (%s)\n", thread, thread->GetId(), process->GetId(), process->GetName()); } else { MESOSPHERE_RELEASE_LOG(" %p tid=%ld (Kernel)\n", thread, thread->GetId()); } } /* If we didn't have any waiters, print so. */ if (m_thread_list_head == nullptr) { MESOSPHERE_RELEASE_LOG(" None\n"); } } #endif } }