/* * Copyright (c) Atmosphère-NX * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include <mesosphere.hpp> namespace ams::kern { namespace { constexpr u64 InvalidThreadId = -1ull; class ThreadQueueImplForKLightServerSessionRequest final : public KThreadQueue { private: KThread::WaiterList *m_wait_list; public: constexpr ThreadQueueImplForKLightServerSessionRequest(KThread::WaiterList *wl) : KThreadQueue(), 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: constexpr ThreadQueueImplForKLightServerSessionReceive(KThread **st) : KThreadQueue(), 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); } }; } void KLightServerSession::Destroy() { MESOSPHERE_ASSERT_THIS(); this->CleanupRequests(); m_parent->OnServerClosed(); } void KLightServerSession::OnClientClosed() { MESOSPHERE_ASSERT_THIS(); this->CleanupRequests(); } Result KLightServerSession::OnRequest(KThread *request_thread) { MESOSPHERE_ASSERT_THIS(); ThreadQueueImplForKLightServerSessionRequest wait_queue(std::addressof(m_request_list)); /* Send the request. */ { /* Lock the scheduler. */ KScopedSchedulerLock sl; /* Check that the server isn't closed. */ R_UNLESS(!m_parent->IsServerClosed(), svc::ResultSessionClosed()); /* Check that the request thread isn't terminating. */ R_UNLESS(!request_thread->IsTerminationRequested(), svc::ResultTerminationRequested()); /* Add the request thread to our list. */ m_request_list.push_back(*request_thread); /* Begin waiting on the request. */ 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) { MESOSPHERE_ASSERT_THIS(); /* Set the server context. */ GetCurrentThread().SetLightSessionData(data); /* Reply, if we need to. */ if (data[0] & KLightSession::ReplyFlag) { KScopedSchedulerLock sl; /* Check that we're open. */ R_UNLESS(!m_parent->IsClientClosed(), svc::ResultSessionClosed()); R_UNLESS(!m_parent->IsServerClosed(), svc::ResultSessionClosed()); /* Check that we have a request to reply to. */ R_UNLESS(m_current_request != nullptr, svc::ResultInvalidState()); /* Check that the server thread id is correct. */ R_UNLESS(m_server_thread_id == GetCurrentThread().GetId(), svc::ResultInvalidState()); /* If we can reply, do so. */ if (!m_current_request->IsTerminationRequested()) { std::memcpy(m_current_request->GetLightSessionData(), GetCurrentThread().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; } /* Close any pending objects before we wait. */ GetCurrentThread().DestroyClosedObjects(); /* Create the wait queue for our receive. */ ThreadQueueImplForKLightServerSessionReceive wait_queue(std::addressof(m_server_thread)); /* Receive. */ while (true) { /* Try to receive a request. */ { KScopedSchedulerLock sl; /* Check that we aren't already receiving. */ R_UNLESS(m_server_thread == nullptr, svc::ResultInvalidState()); R_UNLESS(m_server_thread_id == InvalidThreadId, svc::ResultInvalidState()); /* Check that we're open. */ R_UNLESS(!m_parent->IsClientClosed(), svc::ResultSessionClosed()); R_UNLESS(!m_parent->IsServerClosed(), svc::ResultSessionClosed()); /* Check that we're not terminating. */ R_UNLESS(!GetCurrentThread().IsTerminationRequested(), svc::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().GetId(); /* Copy the client request data. */ std::memcpy(GetCurrentThread().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().IsWaitCancelled()) { GetCurrentThread().ClearWaitCancelled(); R_THROW(svc::ResultCancelled()); } /* Mark ourselves as cancellable. */ GetCurrentThread().SetCancellable(); /* Wait for a request to come in. */ m_server_thread = GetCurrentThreadPointer(); GetCurrentThread().BeginWait(std::addressof(wait_queue)); } /* We waited to receive a request; if our wait failed, return the failing result. */ R_TRY(GetCurrentThread().GetWaitResult()); } } void KLightServerSession::CleanupRequests() { /* Cleanup all pending requests. */ { KScopedSchedulerLock sl; /* Handle the current request. */ if (m_current_request != nullptr) { /* Reply to the current request. */ if (!m_current_request->IsTerminationRequested()) { m_current_request->EndWait(svc::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(svc::ResultSessionClosed()); } /* Wait up our server thread, if we have one. */ if (m_server_thread != nullptr) { m_server_thread->EndWait(svc::ResultSessionClosed()); } } } }