From 3d000c834b6ffb8a36b9dfb4b33a20822eac3926 Mon Sep 17 00:00:00 2001 From: Subv Date: Thu, 9 Nov 2017 18:13:11 -0500 Subject: [PATCH] Kernel/Threads: Implement an SleepClientThread function for HLERequestContext-based services 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 Kernel::Event that should be signaled to resume the guest thread when the host async operation completes. --- src/core/hle/kernel/hle_ipc.cpp | 35 ++++++++++++++++++++++++++++++++ src/core/hle/kernel/hle_ipc.h | 21 +++++++++++++++++++ src/core/hle/kernel/thread.cpp | 3 ++- src/core/hle/service/service.cpp | 12 +++++++++-- 4 files changed, 68 insertions(+), 3 deletions(-) diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp index 303b898f1..68e6ff30a 100644 --- a/src/core/hle/kernel/hle_ipc.cpp +++ b/src/core/hle/kernel/hle_ipc.cpp @@ -6,6 +6,7 @@ #include #include "common/assert.h" #include "common/common_types.h" +#include "core/hle/kernel/event.h" #include "core/hle/kernel/handle_table.h" #include "core/hle/kernel/hle_ipc.h" #include "core/hle/kernel/kernel.h" @@ -24,6 +25,40 @@ void SessionRequestHandler::ClientDisconnected(SharedPtr server_s boost::range::remove_erase(connected_sessions, server_session); } +SharedPtr HLERequestContext::SleepClientThread(SharedPtr thread, + const std::string& reason, u64 timeout, + WakeupCallback&& callback) { + // Put the client thread to sleep until the wait event is signaled or the timeout expires. + thread->wakeup_callback = [context = *this, callback](ThreadWakeupReason reason, + SharedPtr thread, + SharedPtr object) mutable { + ASSERT(thread->status == THREADSTATUS_WAIT_HLE_EVENT); + callback(thread, context, reason); + + auto& process = thread->owner_process; + // We must copy the entire command buffer *plus* the entire static buffers area, since + // the translation might need to read from it in order to retrieve the StaticBuffer + // target addresses. + std::array cmd_buff; + Memory::ReadBlock(*process, thread->GetCommandBufferAddress(), cmd_buff.data(), + cmd_buff.size() * sizeof(u32)); + context.WriteToOutgoingCommandBuffer(cmd_buff.data(), *process, Kernel::g_handle_table); + // Copy the translated command buffer back into the thread's command buffer area. + Memory::WriteBlock(*process, thread->GetCommandBufferAddress(), cmd_buff.data(), + cmd_buff.size() * sizeof(u32)); + }; + + auto event = Kernel::Event::Create(Kernel::ResetType::OneShot, "HLE Pause Event: " + reason); + thread->status = THREADSTATUS_WAIT_HLE_EVENT; + thread->wait_objects = {event}; + event->AddWaitingThread(thread); + + if (timeout > 0) + thread->WakeAfterDelay(timeout); + + return event; +} + HLERequestContext::HLERequestContext(SharedPtr session) : session(std::move(session)) { cmd_buf[0] = 0; diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h index 2a246fa27..856bb54be 100644 --- a/src/core/hle/kernel/hle_ipc.h +++ b/src/core/hle/kernel/hle_ipc.h @@ -6,6 +6,7 @@ #include #include +#include #include #include #include "common/common_types.h" @@ -22,6 +23,8 @@ namespace Kernel { class HandleTable; class Process; +class Thread; +class Event; /** * Interface implemented by HLE Session handlers. @@ -140,6 +143,24 @@ public: return session; } + using WakeupCallback = std::function thread, HLERequestContext& context, + ThreadWakeupReason reason)>; + + /* + * Puts the specified guest thread to sleep until the returned event is signaled or until the + * specified timeout expires. + * @param thread Thread to be put to sleep. + * @param reason Reason for pausing the thread, to be used for debugging purposes. + * @param timeout Timeout in nanoseconds after which the thread will be awoken and the callback + * invoked with a Timeout reason. + * @param callback Callback to be invoked when the thread is resumed. This callback must write + * the entire command response once again, regardless of the state of it before this function + * was called. + * @returns Event that when signaled will resume the thread and call the callback function. + */ + SharedPtr SleepClientThread(SharedPtr thread, const std::string& reason, + u64 timeout, WakeupCallback&& callback); + /** * Resolves a object id from the request command buffer into a pointer to an object. See the * "HLE handle protocol" section in the class documentation for more details. diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index 736007401..2d447bf89 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -196,7 +196,8 @@ static void ThreadWakeupCallback(u64 thread_handle, int cycles_late) { } if (thread->status == THREADSTATUS_WAIT_SYNCH_ANY || - thread->status == THREADSTATUS_WAIT_SYNCH_ALL || thread->status == THREADSTATUS_WAIT_ARB) { + thread->status == THREADSTATUS_WAIT_SYNCH_ALL || thread->status == THREADSTATUS_WAIT_ARB || + thread->status == THREADSTATUS_WAIT_HLE_EVENT) { // Invoke the wakeup callback before clearing the wait objects if (thread->wakeup_callback) diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 039c86a07..60bfd4804 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -181,8 +181,16 @@ void ServiceFrameworkBase::HandleSyncRequest(SharedPtr server_ses LOG_TRACE(Service, "%s", MakeFunctionString(info->name, GetServiceName().c_str(), cmd_buf).c_str()); handler_invoker(this, info->handler_callback, context); - context.WriteToOutgoingCommandBuffer(cmd_buf, *Kernel::g_current_process, - Kernel::g_handle_table); + + auto thread = Kernel::GetCurrentThread(); + ASSERT(thread->status == THREADSTATUS_RUNNING || thread->status == THREADSTATUS_WAIT_HLE_EVENT); + // Only write the response immediately if the thread is still running. If the HLE handler put + // the thread to sleep then the writing of the command buffer will be deferred to the wakeup + // callback. + if (thread->status == THREADSTATUS_RUNNING) { + context.WriteToOutgoingCommandBuffer(cmd_buf, *Kernel::g_current_process, + Kernel::g_handle_table); + } } ////////////////////////////////////////////////////////////////////////////////////////////////////