diff --git a/src/core/hle/function_wrappers.h b/src/core/hle/function_wrappers.h index 410bb87ea..5e6002f4e 100644 --- a/src/core/hle/function_wrappers.h +++ b/src/core/hle/function_wrappers.h @@ -16,9 +16,6 @@ namespace HLE { #define PARAM(n) Core::CPU().GetReg(n) -/// An invalid result code that is meant to be overwritten when a thread resumes from waiting -static const ResultCode RESULT_INVALID(0xDEADC0DE); - /** * HLE a function return from the current ARM11 userland process * @param res Result to return @@ -68,10 +65,18 @@ void Wrap() { (PARAM(3) != 0), (((s64)PARAM(4) << 32) | PARAM(0))) .raw; - if (retval != RESULT_INVALID.raw) { - Core::CPU().SetReg(1, (u32)param_1); - FuncReturn(retval); - } + Core::CPU().SetReg(1, (u32)param_1); + FuncReturn(retval); +} + +template +void Wrap() { + s32 param_1 = 0; + u32 retval = + func(¶m_1, (Kernel::Handle*)Memory::GetPointer(PARAM(1)), (s32)PARAM(2), PARAM(3)).raw; + + Core::CPU().SetReg(1, (u32)param_1); + FuncReturn(retval); } template @@ -92,9 +97,7 @@ template void Wrap() { s32 retval = func(PARAM(0), (((s64)PARAM(3) << 32) | PARAM(2))).raw; - if (retval != RESULT_INVALID.raw) { - FuncReturn(retval); - } + FuncReturn(retval); } template diff --git a/src/core/hle/kernel/client_session.cpp b/src/core/hle/kernel/client_session.cpp index fef97af1f..646a5cc64 100644 --- a/src/core/hle/kernel/client_session.cpp +++ b/src/core/hle/kernel/client_session.cpp @@ -9,6 +9,7 @@ #include "core/hle/kernel/hle_ipc.h" #include "core/hle/kernel/server_session.h" #include "core/hle/kernel/session.h" +#include "core/hle/kernel/thread.h" namespace Kernel { @@ -27,19 +28,24 @@ ClientSession::~ClientSession() { // TODO(Subv): Force a wake up of all the ServerSession's waiting threads and set // their WaitSynchronization result to 0xC920181A. + + // Clean up the list of client threads with pending requests, they are unneeded now that the + // client endpoint is closed. + server->pending_requesting_threads.clear(); + server->currently_handling = nullptr; } parent->client = nullptr; } -ResultCode ClientSession::SendSyncRequest() { +ResultCode ClientSession::SendSyncRequest(SharedPtr thread) { // Keep ServerSession alive until we're done working with it. SharedPtr server = parent->server; if (server == nullptr) return ERR_SESSION_CLOSED_BY_REMOTE; // Signal the server session that new data is available - return server->HandleSyncRequest(); + return server->HandleSyncRequest(std::move(thread)); } } // namespace diff --git a/src/core/hle/kernel/client_session.h b/src/core/hle/kernel/client_session.h index 2de379c09..daf521529 100644 --- a/src/core/hle/kernel/client_session.h +++ b/src/core/hle/kernel/client_session.h @@ -14,6 +14,7 @@ namespace Kernel { class ServerSession; class Session; +class Thread; class ClientSession final : public Object { public: @@ -34,9 +35,10 @@ public: /** * Sends an SyncRequest from the current emulated thread. + * @param thread Thread that initiated the request. * @return ResultCode of the operation. */ - ResultCode SendSyncRequest(); + ResultCode SendSyncRequest(SharedPtr thread); std::string name; ///< Name of client port (optional) diff --git a/src/core/hle/kernel/server_session.cpp b/src/core/hle/kernel/server_session.cpp index d197137c3..337896abf 100644 --- a/src/core/hle/kernel/server_session.cpp +++ b/src/core/hle/kernel/server_session.cpp @@ -32,22 +32,29 @@ ResultVal> ServerSession::Create(std::string name) { SharedPtr server_session(new ServerSession); server_session->name = std::move(name); - server_session->signaled = false; server_session->parent = nullptr; return MakeResult(std::move(server_session)); } bool ServerSession::ShouldWait(Thread* thread) const { - return !signaled; + // Closed sessions should never wait, an error will be returned from svcReplyAndReceive. + if (parent->client == nullptr) + return false; + // Wait if we have no pending requests, or if we're currently handling a request. + return pending_requesting_threads.empty() || currently_handling != nullptr; } void ServerSession::Acquire(Thread* thread) { ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); - signaled = false; + // We are now handling a request, pop it from the stack. + // TODO(Subv): What happens if the client endpoint is closed before any requests are made? + ASSERT(!pending_requesting_threads.empty()); + currently_handling = pending_requesting_threads.back(); + pending_requesting_threads.pop_back(); } -ResultCode ServerSession::HandleSyncRequest() { +ResultCode ServerSession::HandleSyncRequest(SharedPtr thread) { // The ServerSession received a sync request, this means that there's new data available // from its ClientSession, so wake up any threads that may be waiting on a svcReplyAndReceive or // similar. @@ -60,11 +67,14 @@ ResultCode ServerSession::HandleSyncRequest() { return result; hle_handler->HandleSyncRequest(SharedPtr(this)); // TODO(Subv): Translate the response command buffer. + } else { + // Add the thread to the list of threads that have issued a sync request with this + // server. + pending_requesting_threads.push_back(std::move(thread)); } // If this ServerSession does not have an HLE implementation, just wake up the threads waiting // on it. - signaled = true; WakeupAllWaitingThreads(); return RESULT_SUCCESS; } @@ -90,4 +100,4 @@ ResultCode TranslateHLERequest(ServerSession* server_session) { // TODO(Subv): Implement this function once multiple concurrent processes are supported. return RESULT_SUCCESS; } -} +} // namespace Kernel diff --git a/src/core/hle/kernel/server_session.h b/src/core/hle/kernel/server_session.h index 5365605da..f4360ddf3 100644 --- a/src/core/hle/kernel/server_session.h +++ b/src/core/hle/kernel/server_session.h @@ -67,20 +67,30 @@ public: /** * Handle a sync request from the emulated application. + * @param thread Thread that initiated the request. * @returns ResultCode from the operation. */ - ResultCode HandleSyncRequest(); + ResultCode HandleSyncRequest(SharedPtr thread); bool ShouldWait(Thread* thread) const override; void Acquire(Thread* thread) override; std::string name; ///< The name of this session (optional) - bool signaled; ///< Whether there's new data available to this ServerSession std::shared_ptr parent; ///< The parent session, which links to the client endpoint. std::shared_ptr hle_handler; ///< This session's HLE request handler (optional) + /// List of threads that are pending a response after a sync request. This list is processed in + /// a LIFO manner, thus, the last request will be dispatched first. + /// TODO(Subv): Verify if this is indeed processed in LIFO using a hardware test. + std::vector> pending_requesting_threads; + + /// Thread whose request is currently being handled. A request is considered "handled" when a + /// response is sent via svcReplyAndReceive. + /// TODO(Subv): Find a better name for this. + SharedPtr currently_handling; + private: ServerSession(); ~ServerSession() override; diff --git a/src/core/hle/svc.cpp b/src/core/hle/svc.cpp index c05401143..e4b803046 100644 --- a/src/core/hle/svc.cpp +++ b/src/core/hle/svc.cpp @@ -25,6 +25,7 @@ #include "core/hle/kernel/semaphore.h" #include "core/hle/kernel/server_port.h" #include "core/hle/kernel/server_session.h" +#include "core/hle/kernel/session.h" #include "core/hle/kernel/shared_memory.h" #include "core/hle/kernel/thread.h" #include "core/hle/kernel/timer.h" @@ -237,7 +238,7 @@ static ResultCode SendSyncRequest(Kernel::Handle handle) { // TODO(Subv): svcSendSyncRequest should put the caller thread to sleep while the server // responds and cause a reschedule. - return session->SendSyncRequest(); + return session->SendSyncRequest(Kernel::GetCurrentThread()); } /// Close a handle @@ -398,6 +399,112 @@ static ResultCode WaitSynchronizationN(s32* out, Kernel::Handle* handles, s32 ha } } +/// In a single operation, sends a IPC reply and waits for a new request. +static ResultCode ReplyAndReceive(s32* index, Kernel::Handle* handles, s32 handle_count, + Kernel::Handle reply_target) { + // 'handles' has to be a valid pointer even if 'handle_count' is 0. + if (handles == nullptr) + return Kernel::ERR_INVALID_POINTER; + + // Check if 'handle_count' is invalid + if (handle_count < 0) + return Kernel::ERR_OUT_OF_RANGE; + + using ObjectPtr = SharedPtr; + std::vector objects(handle_count); + + for (int i = 0; i < handle_count; ++i) { + auto object = Kernel::g_handle_table.Get(handles[i]); + if (object == nullptr) + return ERR_INVALID_HANDLE; + objects[i] = object; + } + + // We are also sending a command reply. + // Do not send a reply if the command id in the command buffer is 0xFFFF. + u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::Header header{cmd_buff[0]}; + if (reply_target != 0 && header.command_id != 0xFFFF) { + auto session = Kernel::g_handle_table.Get(reply_target); + if (session == nullptr) + return ERR_INVALID_HANDLE; + + auto request_thread = std::move(session->currently_handling); + + // Mark the request as "handled". + session->currently_handling = nullptr; + + // Error out if there's no request thread or the session was closed. + // TODO(Subv): Is the same error code (ClosedByRemote) returned for both of these cases? + if (request_thread == nullptr || session->parent->client == nullptr) { + *index = -1; + return Kernel::ERR_SESSION_CLOSED_BY_REMOTE; + } + + // TODO(Subv): Perform IPC translation from the current thread to request_thread. + + // Note: The scheduler is not invoked here. + request_thread->ResumeFromWait(); + } + + if (handle_count == 0) { + *index = 0; + // The kernel uses this value as a placeholder for the real error, and returns it when we + // pass no handles and do not perform any reply. + if (reply_target == 0 || header.command_id == 0xFFFF) + return ResultCode(0xE7E3FFFF); + + return RESULT_SUCCESS; + } + + auto thread = Kernel::GetCurrentThread(); + + // Find the first object that is acquirable in the provided list of objects + auto itr = std::find_if(objects.begin(), objects.end(), [thread](const ObjectPtr& object) { + return !object->ShouldWait(thread); + }); + + if (itr != objects.end()) { + // We found a ready object, acquire it and set the result value + Kernel::WaitObject* object = itr->get(); + object->Acquire(thread); + *index = std::distance(objects.begin(), itr); + + if (object->GetHandleType() == Kernel::HandleType::ServerSession) { + auto server_session = static_cast(object); + if (server_session->parent->client == nullptr) + return Kernel::ERR_SESSION_CLOSED_BY_REMOTE; + + // TODO(Subv): Perform IPC translation from the ServerSession to the current thread. + } + return RESULT_SUCCESS; + } + + // No objects were ready to be acquired, prepare to suspend the thread. + + // TODO(Subv): Perform IPC translation upon wakeup. + + // Put the thread to sleep + thread->status = THREADSTATUS_WAIT_SYNCH_ANY; + + // Add the thread to each of the objects' waiting threads. + for (size_t i = 0; i < objects.size(); ++i) { + Kernel::WaitObject* object = objects[i].get(); + object->AddWaitingThread(thread); + } + + thread->wait_objects = std::move(objects); + + Core::System::GetInstance().PrepareReschedule(); + + // Note: The output of this SVC will be set to RESULT_SUCCESS if the thread resumes due to a + // signal in one of its wait objects, or to 0xC8A01836 if there was a translation error. + // By default the index is set to -1. + thread->wait_set_output = true; + *index = -1; + return RESULT_SUCCESS; +} + /// Create an address arbiter (to allocate access to shared resources) static ResultCode CreateAddressArbiter(Kernel::Handle* out_handle) { using Kernel::AddressArbiter; @@ -1163,7 +1270,7 @@ static const FunctionDef SVC_Table[] = { {0x4C, nullptr, "ReplyAndReceive2"}, {0x4D, nullptr, "ReplyAndReceive3"}, {0x4E, nullptr, "ReplyAndReceive4"}, - {0x4F, nullptr, "ReplyAndReceive"}, + {0x4F, HLE::Wrap, "ReplyAndReceive"}, {0x50, nullptr, "BindInterrupt"}, {0x51, nullptr, "UnbindInterrupt"}, {0x52, nullptr, "InvalidateProcessDataCache"},