/* * Copyright (c) Atmosphère-NX * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include namespace ams::kern { namespace { class ThreadQueueImplForKSynchronizationObjectWait final : public KThreadQueueWithoutEndWait { private: using ThreadListNode = KSynchronizationObject::ThreadListNode; private: KSynchronizationObject **m_objects; ThreadListNode *m_nodes; s32 m_count; public: constexpr ThreadQueueImplForKSynchronizationObjectWait(KSynchronizationObject **o, ThreadListNode *n, s32 c) : m_objects(o), m_nodes(n), m_count(c) { /* ... */ } virtual void NotifyAvailable(KThread *waiting_thread, KSynchronizationObject *signaled_object, Result wait_result) override { /* Determine the sync index, and unlink all nodes. */ s32 sync_index = -1; for (auto i = 0; i < m_count; ++i) { /* Check if this is the signaled object. */ if (m_objects[i] == signaled_object && sync_index == -1) { sync_index = i; } /* Unlink the current node from the current object. */ m_objects[i]->UnlinkNode(std::addressof(m_nodes[i])); } /* Set the waiting thread's sync index. */ waiting_thread->SetSyncedIndex(sync_index); /* Set the waiting thread as not cancellable. */ waiting_thread->ClearCancellable(); /* Invoke the base end wait handler. */ KThreadQueue::EndWait(waiting_thread, wait_result); } virtual void CancelWait(KThread *waiting_thread, Result wait_result, bool cancel_timer_task) override { /* Remove all nodes from our list. */ for (auto i = 0; i < m_count; ++i) { m_objects[i]->UnlinkNode(std::addressof(m_nodes[i])); } /* Set the waiting thread as not cancellable. */ waiting_thread->ClearCancellable(); /* Invoke the base cancel wait handler. */ KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task); } }; } 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 /* NOTE: In Nintendo's kernel, the following is virtual and called here. */ /* this->OnFinalizeSynchronizationObject(); */ } 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; ThreadQueueImplForKSynchronizationObjectWait wait_queue(objects, thread_nodes, num_objects); { /* Setup the scheduling lock and sleep. */ KScopedSchedulerLockAndSleep slp(std::addressof(timer), thread, timeout); /* Check if the thread should terminate. */ if (thread->IsTerminationRequested()) { slp.CancelSleep(); R_THROW(svc::ResultTerminationRequested()); } /* Check if any of the objects are already signaled. */ for (auto i = 0; i < num_objects; ++i) { MESOSPHERE_ASSERT(objects[i] != nullptr); if (objects[i]->IsSignaled()) { *out_index = i; slp.CancelSleep(); R_SUCCEED(); } } /* Check if the timeout is zero. */ if (timeout == 0) { slp.CancelSleep(); R_THROW(svc::ResultTimedOut()); } /* Check if waiting was canceled. */ if (thread->IsWaitCancelled()) { slp.CancelSleep(); thread->ClearWaitCancelled(); R_THROW(svc::ResultCancelled()); } /* Add the waiters. */ for (auto i = 0; i < num_objects; ++i) { thread_nodes[i].thread = thread; thread_nodes[i].next = nullptr; objects[i]->LinkNode(std::addressof(thread_nodes[i])); } /* Mark the thread as cancellable. */ thread->SetCancellable(); /* Clear the thread's synced index. */ thread->SetSyncedIndex(-1); /* Wait for an object to be signaled. */ wait_queue.SetHardwareTimer(timer); thread->BeginWait(std::addressof(wait_queue)); } /* Set the output index. */ *out_index = thread->GetSyncedIndex(); /* Get the wait result. */ R_RETURN(thread->GetWaitResult()); } 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) { cur_node->thread->NotifyAvailable(this, result); } } 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 } }