From 0a308e224c5242949dc2a82789db8ad75dba41d8 Mon Sep 17 00:00:00 2001 From: Subv Date: Thu, 20 Jul 2017 23:18:56 -0500 Subject: [PATCH 1/2] Kernel/Threads: Add a new thread status that will allow using a Kernel::Event to put a guest thread to sleep inside an HLE handler until said event is signaled. --- src/citra_qt/debugger/wait_tree.cpp | 7 ++++++- src/core/hle/kernel/thread.cpp | 1 + src/core/hle/kernel/thread.h | 1 + src/core/hle/kernel/wait_object.cpp | 3 ++- 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/citra_qt/debugger/wait_tree.cpp b/src/citra_qt/debugger/wait_tree.cpp index 8c244b6b2..9b489b62d 100644 --- a/src/citra_qt/debugger/wait_tree.cpp +++ b/src/citra_qt/debugger/wait_tree.cpp @@ -158,6 +158,9 @@ QString WaitTreeThread::GetText() const { case THREADSTATUS_WAIT_SYNCH_ANY: status = tr("waiting for objects"); break; + case THREADSTATUS_WAIT_HLE_EVENT: + status = tr("waiting for HLE return"); + break; case THREADSTATUS_DORMANT: status = tr("dormant"); break; @@ -184,6 +187,7 @@ QColor WaitTreeThread::GetColor() const { return QColor(Qt::GlobalColor::darkYellow); case THREADSTATUS_WAIT_SYNCH_ALL: case THREADSTATUS_WAIT_SYNCH_ANY: + case THREADSTATUS_WAIT_HLE_EVENT: return QColor(Qt::GlobalColor::red); case THREADSTATUS_DORMANT: return QColor(Qt::GlobalColor::darkCyan); @@ -232,7 +236,8 @@ std::vector> WaitTreeThread::GetChildren() const { list.push_back(std::make_unique(thread.held_mutexes)); } if (thread.status == THREADSTATUS_WAIT_SYNCH_ANY || - thread.status == THREADSTATUS_WAIT_SYNCH_ALL) { + thread.status == THREADSTATUS_WAIT_SYNCH_ALL || + thread.status == THREADSTATUS_WAIT_HLE_EVENT) { list.push_back(std::make_unique(thread.wait_objects, thread.IsSleepingOnWaitAll())); } diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index 2614a260c..a457b99ee 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -276,6 +276,7 @@ void Thread::ResumeFromWait() { switch (status) { case THREADSTATUS_WAIT_SYNCH_ALL: case THREADSTATUS_WAIT_SYNCH_ANY: + case THREADSTATUS_WAIT_HLE_EVENT: case THREADSTATUS_WAIT_ARB: case THREADSTATUS_WAIT_SLEEP: break; diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index 4679c2022..abe5c084c 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h @@ -37,6 +37,7 @@ enum ThreadStatus { THREADSTATUS_WAIT_SLEEP, ///< Waiting due to a SleepThread SVC THREADSTATUS_WAIT_SYNCH_ANY, ///< Waiting due to WaitSynch1 or WaitSynchN with wait_all = false THREADSTATUS_WAIT_SYNCH_ALL, ///< Waiting due to WaitSynchronizationN with wait_all = true + THREADSTATUS_WAIT_HLE_EVENT, ///< Waiting due to an HLE handler pausing the thread THREADSTATUS_DORMANT, ///< Created but not yet made ready THREADSTATUS_DEAD ///< Run to completion, or forcefully terminated }; diff --git a/src/core/hle/kernel/wait_object.cpp b/src/core/hle/kernel/wait_object.cpp index 469554908..320e16d07 100644 --- a/src/core/hle/kernel/wait_object.cpp +++ b/src/core/hle/kernel/wait_object.cpp @@ -39,7 +39,8 @@ SharedPtr WaitObject::GetHighestPriorityReadyThread() { for (const auto& thread : waiting_threads) { // The list of waiting threads must not contain threads that are not waiting to be awakened. ASSERT_MSG(thread->status == THREADSTATUS_WAIT_SYNCH_ANY || - thread->status == THREADSTATUS_WAIT_SYNCH_ALL, + thread->status == THREADSTATUS_WAIT_SYNCH_ALL || + thread->status == THREADSTATUS_WAIT_HLE_EVENT, "Inconsistent thread statuses in waiting_threads"); if (thread->current_priority >= candidate_priority) From f9d55ecf3f80d580c7cf8fe2179118cb1362eb11 Mon Sep 17 00:00:00 2001 From: Subv Date: Fri, 29 Sep 2017 10:00:28 -0500 Subject: [PATCH 2/2] HLE: Implemented SleepClientThread and ContinueClientThread functions to make performing async tasks on the host while in an HLE service function easier. An HLE service function that wants to perform an async operation should put the caller guest thread to sleep using SleepClientThread, passing in a callback to execute when the thread is resumed. SleepClientThread returns a ThreadContinuationToken that should be stored and used with ContinueClientThread to resume the guest thread when the host async operation completes. --- src/core/hle/service/service.cpp | 44 +++++++++++++++++++++++++++++++- src/core/hle/service/service.h | 44 ++++++++++++++++++++++++++++++-- 2 files changed, 85 insertions(+), 3 deletions(-) diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index f267aad74..51e8a015e 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -9,6 +9,7 @@ #include "common/string_util.h" #include "core/hle/ipc.h" #include "core/hle/kernel/client_port.h" +#include "core/hle/kernel/event.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/server_port.h" #include "core/hle/kernel/server_session.h" @@ -210,6 +211,47 @@ void AddService(Interface* interface_) { server_port->SetHleHandler(std::shared_ptr(interface_)); } +bool ThreadContinuationToken::IsValid() { + return thread != nullptr && event != nullptr; +} + +ThreadContinuationToken SleepClientThread(const std::string& reason, + ThreadContinuationToken::Callback callback) { + auto thread = Kernel::GetCurrentThread(); + + ASSERT(thread->status == THREADSTATUS_RUNNING); + + ThreadContinuationToken token; + + token.event = Kernel::Event::Create(Kernel::ResetType::OneShot, "HLE Pause Event: " + reason); + token.thread = thread; + token.callback = std::move(callback); + token.pause_reason = std::move(reason); + + // Make the thread wait on our newly created event, it will be signaled when + // ContinueClientThread is called. + thread->status = THREADSTATUS_WAIT_HLE_EVENT; + thread->wait_objects = {token.event}; + token.event->AddWaitingThread(thread); + + return token; +} + +void ContinueClientThread(ThreadContinuationToken& token) { + ASSERT_MSG(token.IsValid(), "Invalid continuation token"); + ASSERT(token.thread->status == THREADSTATUS_WAIT_HLE_EVENT); + + // Signal the event to wake up the thread + token.event->Signal(); + ASSERT(token.thread->status == THREADSTATUS_READY); + + token.callback(token.thread); + + token.event = nullptr; + token.thread = nullptr; + token.callback = nullptr; +} + /// Initialize ServiceManager void Init() { SM::g_service_manager = std::make_shared(); @@ -280,4 +322,4 @@ void Shutdown() { g_kernel_named_ports.clear(); LOG_DEBUG(Service, "shutdown OK"); } -} +} // namespace Service diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h index 281ff99bb..084994816 100644 --- a/src/core/hle/service/service.h +++ b/src/core/hle/service/service.h @@ -20,7 +20,8 @@ namespace Kernel { class ClientPort; class ServerPort; class ServerSession; -} +class Event; +} // namespace Kernel namespace Service { @@ -249,6 +250,45 @@ private: } }; +/* + * Token representing a pause request for a guest thread from an HLE service function. + * Using this token a function can put a guest thread to sleep to defer returning a result from + * SendSyncRequest until an async operation completes on the host. To use it, call SleepClientThread + * to create a specific continuation token for the current thread, perform your async operation, and + * then call ContinueClientThread passing in the returned token as a parameter. + */ +class ThreadContinuationToken { +public: + using Callback = std::function thread)>; + friend ThreadContinuationToken SleepClientThread(const std::string& reason, Callback callback); + friend void ContinueClientThread(ThreadContinuationToken& token); + + bool IsValid(); + +private: + Kernel::SharedPtr event; + Kernel::SharedPtr thread; + Callback callback; + std::string pause_reason; +}; + +/* + * Puts the current guest thread to sleep and returns a ThreadContinuationToken to be used with + * ContinueClientThread. + * @param reason Reason for pausing the thread, to be used for debugging purposes. + * @param callback Callback to be invoked when the thread is resumed by ContinueClientThread. + * @returns ThreadContinuationToken representing the pause request. + */ +ThreadContinuationToken SleepClientThread(const std::string& reason, + ThreadContinuationToken::Callback callback); + +/* + * Completes a continuation request and resumes the associated guest thread. + * This function invalidates the token. + * @param token The continuation token associated with the continuation request. + */ +void ContinueClientThread(ThreadContinuationToken& token); + /// Initialize ServiceManager void Init(); @@ -263,4 +303,4 @@ void AddNamedPort(std::string name, Kernel::SharedPtr port); /// Adds a service to the services table void AddService(Interface* interface_); -} // namespace +} // namespace Service