/*
* 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 .
*/
#include
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());
}
}
}
}