diff options
Diffstat (limited to 'src/core/hle/kernel/k_light_server_session.cpp')
-rw-r--r-- | src/core/hle/kernel/k_light_server_session.cpp | 247 |
1 files changed, 247 insertions, 0 deletions
diff --git a/src/core/hle/kernel/k_light_server_session.cpp b/src/core/hle/kernel/k_light_server_session.cpp new file mode 100644 index 000000000..e5ceb01f2 --- /dev/null +++ b/src/core/hle/kernel/k_light_server_session.cpp @@ -0,0 +1,247 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/kernel/k_light_server_session.h" +#include "core/hle/kernel/k_light_session.h" +#include "core/hle/kernel/k_thread.h" +#include "core/hle/kernel/k_thread_queue.h" +#include "core/hle/kernel/svc_results.h" + +namespace Kernel { + +namespace { + +constexpr u64 InvalidThreadId = std::numeric_limits<u64>::max(); + +class ThreadQueueImplForKLightServerSessionRequest final : public KThreadQueue { +private: + KThread::WaiterList* m_wait_list; + +public: + ThreadQueueImplForKLightServerSessionRequest(KernelCore& kernel, KThread::WaiterList* wl) + : KThreadQueue(kernel), m_wait_list(wl) {} + + virtual void EndWait(KThread* waiting_thread, Result wait_result) override { + // Remove the thread from our wait list. + m_wait_list->erase(m_wait_list->iterator_to(*waiting_thread)); + + // Invoke the base end wait handler. + KThreadQueue::EndWait(waiting_thread, wait_result); + } + + virtual void CancelWait(KThread* waiting_thread, Result wait_result, + bool cancel_timer_task) override { + // Remove the thread from our wait list. + m_wait_list->erase(m_wait_list->iterator_to(*waiting_thread)); + + // Invoke the base cancel wait handler. + KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task); + } +}; + +class ThreadQueueImplForKLightServerSessionReceive final : public KThreadQueue { +private: + KThread** m_server_thread; + +public: + ThreadQueueImplForKLightServerSessionReceive(KernelCore& kernel, KThread** st) + : KThreadQueue(kernel), m_server_thread(st) {} + + virtual void EndWait(KThread* waiting_thread, Result wait_result) override { + // Clear the server thread. + *m_server_thread = nullptr; + + // Set the waiting thread as not cancelable. + waiting_thread->ClearCancellable(); + + // Invoke the base end wait handler. + KThreadQueue::EndWait(waiting_thread, wait_result); + } + + virtual void CancelWait(KThread* waiting_thread, Result wait_result, + bool cancel_timer_task) override { + // Clear the server thread. + *m_server_thread = nullptr; + + // Set the waiting thread as not cancelable. + waiting_thread->ClearCancellable(); + + // Invoke the base cancel wait handler. + KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task); + } +}; + +} // namespace + +KLightServerSession::KLightServerSession(KernelCore& kernel) : KAutoObject(kernel) {} +KLightServerSession::~KLightServerSession() = default; + +void KLightServerSession::Destroy() { + this->CleanupRequests(); + + m_parent->OnServerClosed(); +} + +void KLightServerSession::OnClientClosed() { + this->CleanupRequests(); +} + +Result KLightServerSession::OnRequest(KThread* request_thread) { + ThreadQueueImplForKLightServerSessionRequest wait_queue(m_kernel, + std::addressof(m_request_list)); + + // Send the request. + { + // Lock the scheduler. + KScopedSchedulerLock sl(m_kernel); + + // Check that the server isn't closed. + R_UNLESS(!m_parent->IsServerClosed(), ResultSessionClosed); + + // Check that the request thread isn't terminating. + R_UNLESS(!request_thread->IsTerminationRequested(), ResultTerminationRequested); + + // Add the request thread to our list. + m_request_list.push_back(*request_thread); + + // Begin waiting on the request. + request_thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::IPC); + request_thread->BeginWait(std::addressof(wait_queue)); + + // If we have a server thread, end its wait. + if (m_server_thread != nullptr) { + m_server_thread->EndWait(ResultSuccess); + } + } + + // NOTE: Nintendo returns GetCurrentThread().GetWaitResult() here. + // This is technically incorrect, although it doesn't cause problems in practice + // because this is only ever called with request_thread = GetCurrentThreadPointer(). + R_RETURN(request_thread->GetWaitResult()); +} + +Result KLightServerSession::ReplyAndReceive(u32* data) { + // Set the server context. + GetCurrentThread(m_kernel).SetLightSessionData(data); + + // Reply, if we need to. + if (data[0] & KLightSession::ReplyFlag) { + KScopedSchedulerLock sl(m_kernel); + + // Check that we're open. + R_UNLESS(!m_parent->IsClientClosed(), ResultSessionClosed); + R_UNLESS(!m_parent->IsServerClosed(), ResultSessionClosed); + + // Check that we have a request to reply to. + R_UNLESS(m_current_request != nullptr, ResultInvalidState); + + // Check that the server thread id is correct. + R_UNLESS(m_server_thread_id == GetCurrentThread(m_kernel).GetId(), ResultInvalidState); + + // If we can reply, do so. + if (!m_current_request->IsTerminationRequested()) { + std::memcpy(m_current_request->GetLightSessionData(), + GetCurrentThread(m_kernel).GetLightSessionData(), KLightSession::DataSize); + m_current_request->EndWait(ResultSuccess); + } + + // Close our current request. + m_current_request->Close(); + + // Clear our current request. + m_current_request = nullptr; + m_server_thread_id = InvalidThreadId; + } + + // Create the wait queue for our receive. + ThreadQueueImplForKLightServerSessionReceive wait_queue(m_kernel, + std::addressof(m_server_thread)); + + // Receive. + while (true) { + // Try to receive a request. + { + KScopedSchedulerLock sl(m_kernel); + + // Check that we aren't already receiving. + R_UNLESS(m_server_thread == nullptr, ResultInvalidState); + R_UNLESS(m_server_thread_id == InvalidThreadId, ResultInvalidState); + + // Check that we're open. + R_UNLESS(!m_parent->IsClientClosed(), ResultSessionClosed); + R_UNLESS(!m_parent->IsServerClosed(), ResultSessionClosed); + + // Check that we're not terminating. + R_UNLESS(!GetCurrentThread(m_kernel).IsTerminationRequested(), + ResultTerminationRequested); + + // If we have a request available, use it. + if (auto head = m_request_list.begin(); head != m_request_list.end()) { + // Set our current request. + m_current_request = std::addressof(*head); + m_current_request->Open(); + + // Set our server thread id. + m_server_thread_id = GetCurrentThread(m_kernel).GetId(); + + // Copy the client request data. + std::memcpy(GetCurrentThread(m_kernel).GetLightSessionData(), + m_current_request->GetLightSessionData(), KLightSession::DataSize); + + // We successfully received. + R_SUCCEED(); + } + + // We need to wait for a request to come in. + + // Check if we were cancelled. + if (GetCurrentThread(m_kernel).IsWaitCancelled()) { + GetCurrentThread(m_kernel).ClearWaitCancelled(); + R_THROW(ResultCancelled); + } + + // Mark ourselves as cancellable. + GetCurrentThread(m_kernel).SetCancellable(); + + // Wait for a request to come in. + m_server_thread = GetCurrentThreadPointer(m_kernel); + GetCurrentThread(m_kernel).SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::IPC); + GetCurrentThread(m_kernel).BeginWait(std::addressof(wait_queue)); + } + + // We waited to receive a request; if our wait failed, return the failing result. + R_TRY(GetCurrentThread(m_kernel).GetWaitResult()); + } +} + +void KLightServerSession::CleanupRequests() { + // Cleanup all pending requests. + { + KScopedSchedulerLock sl(m_kernel); + + // Handle the current request. + if (m_current_request != nullptr) { + // Reply to the current request. + if (!m_current_request->IsTerminationRequested()) { + m_current_request->EndWait(ResultSessionClosed); + } + + // Clear our current request. + m_current_request->Close(); + m_current_request = nullptr; + m_server_thread_id = InvalidThreadId; + } + + // Reply to all other requests. + for (auto& thread : m_request_list) { + thread.EndWait(ResultSessionClosed); + } + + // Wait up our server thread, if we have one. + if (m_server_thread != nullptr) { + m_server_thread->EndWait(ResultSessionClosed); + } + } +} + +} // namespace Kernel |