diff --git a/src/citra_qt/debugger/wait_tree.cpp b/src/citra_qt/debugger/wait_tree.cpp index 2b0d4fdbe..af0dfeca1 100644 --- a/src/citra_qt/debugger/wait_tree.cpp +++ b/src/citra_qt/debugger/wait_tree.cpp @@ -161,6 +161,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; @@ -189,6 +192,7 @@ QColor WaitTreeThread::GetColor() const { return QColor(Qt::GlobalColor::darkCyan); 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); @@ -237,7 +241,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 2df74c5b4..736007401 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -225,6 +225,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: case THREADSTATUS_WAIT_IPC: diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index 149a8d1a6..05e536086 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h @@ -38,6 +38,7 @@ enum ThreadStatus { THREADSTATUS_WAIT_IPC, ///< Waiting for the reply from an IPC request 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) diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 8fc801b77..81068570a 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(); @@ -279,4 +321,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