diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp index 61b4b8e46a..660f783421 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" @@ -30,6 +31,40 @@ void SessionRequestHandler::ClientDisconnected(SharedPtr server_s connected_sessions.end()); } +SharedPtr HLERequestContext::SleepClientThread(SharedPtr thread, + const std::string& reason, + std::chrono::nanoseconds 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.count() > 0) + thread->WakeAfterDelay(timeout.count()); + + 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 95d9624db0..90732d35a7 100644 --- a/src/core/hle/kernel/hle_ipc.h +++ b/src/core/hle/kernel/hle_ipc.h @@ -6,7 +6,9 @@ #include #include +#include #include +#include #include #include #include "common/common_types.h" @@ -23,6 +25,8 @@ namespace Kernel { class HandleTable; class Process; +class Thread; +class Event; /** * Interface implemented by HLE Session handlers. @@ -166,6 +170,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, + std::chrono::nanoseconds 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 8430f5e0d7..b86ca6cf8c 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 133e14d5e5..39eab20db7 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -182,8 +182,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); + } } ////////////////////////////////////////////////////////////////////////////////////////////////////