// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include #include #include "common/assert.h" #include "common/common_types.h" #include "common/logging/log.h" #include "common/scope_exit.h" #include "core/core.h" #include "core/core_timing.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/hle_ipc.h" #include "core/hle/kernel/k_client_port.h" #include "core/hle/kernel/k_handle_table.h" #include "core/hle/kernel/k_process.h" #include "core/hle/kernel/k_scheduler.h" #include "core/hle/kernel/k_server_port.h" #include "core/hle/kernel/k_server_session.h" #include "core/hle/kernel/k_session.h" #include "core/hle/kernel/k_thread.h" #include "core/hle/kernel/k_thread_queue.h" #include "core/hle/kernel/kernel.h" #include "core/memory.h" namespace Kernel { using ThreadQueueImplForKServerSessionRequest = KThreadQueue; KServerSession::KServerSession(KernelCore& kernel_) : KSynchronizationObject{kernel_}, m_lock{kernel_} {} KServerSession::~KServerSession() = default; void KServerSession::Initialize(KSession* parent_session_, std::string&& name_) { // Set member variables. parent = parent_session_; name = std::move(name_); } void KServerSession::Destroy() { parent->OnServerClosed(); this->CleanupRequests(); parent->Close(); } void KServerSession::OnClientClosed() { KScopedLightLock lk{m_lock}; // Handle any pending requests. KSessionRequest* prev_request = nullptr; while (true) { // Declare variables for processing the request. KSessionRequest* request = nullptr; KEvent* event = nullptr; KThread* thread = nullptr; bool cur_request = false; bool terminate = false; // Get the next request. { KScopedSchedulerLock sl{kernel}; if (m_current_request != nullptr && m_current_request != prev_request) { // Set the request, open a reference as we process it. request = m_current_request; request->Open(); cur_request = true; // Get thread and event for the request. thread = request->GetThread(); event = request->GetEvent(); // If the thread is terminating, handle that. if (thread->IsTerminationRequested()) { request->ClearThread(); request->ClearEvent(); terminate = true; } prev_request = request; } else if (!m_request_list.empty()) { // Pop the request from the front of the list. request = std::addressof(m_request_list.front()); m_request_list.pop_front(); // Get thread and event for the request. thread = request->GetThread(); event = request->GetEvent(); } } // If there are no requests, we're done. if (request == nullptr) { break; } // All requests must have threads. ASSERT(thread != nullptr); // Ensure that we close the request when done. SCOPE_EXIT({ request->Close(); }); // If we're terminating, close a reference to the thread and event. if (terminate) { thread->Close(); if (event != nullptr) { event->Close(); } } // If we need to, reply. if (event != nullptr && !cur_request) { // There must be no mappings. ASSERT(request->GetSendCount() == 0); ASSERT(request->GetReceiveCount() == 0); ASSERT(request->GetExchangeCount() == 0); // // Get the process and page table. // KProcess *client_process = thread->GetOwnerProcess(); // auto &client_pt = client_process->GetPageTable(); // // Reply to the request. // ReplyAsyncError(client_process, request->GetAddress(), request->GetSize(), // ResultSessionClosed); // // Unlock the buffer. // // NOTE: Nintendo does not check the result of this. // client_pt.UnlockForIpcUserBuffer(request->GetAddress(), request->GetSize()); // Signal the event. event->Signal(); } } // Notify. this->NotifyAvailable(ResultSessionClosed); } bool KServerSession::IsSignaled() const { ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(kernel)); // If the client is closed, we're always signaled. if (parent->IsClientClosed()) { return true; } // Otherwise, we're signaled if we have a request and aren't handling one. return !m_request_list.empty() && m_current_request == nullptr; } Result KServerSession::OnRequest(KSessionRequest* request) { // Create the wait queue. ThreadQueueImplForKServerSessionRequest wait_queue{kernel}; { // Lock the scheduler. KScopedSchedulerLock sl{kernel}; // Ensure that we can handle new requests. R_UNLESS(!parent->IsServerClosed(), ResultSessionClosed); // Check that we're not terminating. R_UNLESS(!GetCurrentThread(kernel).IsTerminationRequested(), ResultTerminationRequested); // Get whether we're empty. const bool was_empty = m_request_list.empty(); // Add the request to the list. request->Open(); m_request_list.push_back(*request); // If we were empty, signal. if (was_empty) { this->NotifyAvailable(); } // If we have a request event, this is asynchronous, and we don't need to wait. R_SUCCEED_IF(request->GetEvent() != nullptr); // This is a synchronous request, so we should wait for our request to complete. GetCurrentThread(kernel).SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::IPC); GetCurrentThread(kernel).BeginWait(&wait_queue); } return GetCurrentThread(kernel).GetWaitResult(); } Result KServerSession::SendReply(bool is_hle) { // Lock the session. KScopedLightLock lk{m_lock}; // Get the request. KSessionRequest* request; { KScopedSchedulerLock sl{kernel}; // Get the current request. request = m_current_request; R_UNLESS(request != nullptr, ResultInvalidState); // Clear the current request, since we're processing it. m_current_request = nullptr; if (!m_request_list.empty()) { this->NotifyAvailable(); } } // Close reference to the request once we're done processing it. SCOPE_EXIT({ request->Close(); }); // Extract relevant information from the request. const uintptr_t client_message = request->GetAddress(); const size_t client_buffer_size = request->GetSize(); KThread* client_thread = request->GetThread(); KEvent* event = request->GetEvent(); // Check whether we're closed. const bool closed = (client_thread == nullptr || parent->IsClientClosed()); Result result = ResultSuccess; if (!closed) { // If we're not closed, send the reply. if (is_hle) { // HLE servers write directly to a pointer to the thread command buffer. Therefore // the reply has already been written in this case. } else { Core::Memory::Memory& memory{kernel.System().Memory()}; KThread* server_thread{GetCurrentThreadPointer(kernel)}; UNIMPLEMENTED_IF(server_thread->GetOwnerProcess() != client_thread->GetOwnerProcess()); auto* src_msg_buffer = memory.GetPointer(server_thread->GetTLSAddress()); auto* dst_msg_buffer = memory.GetPointer(client_message); std::memcpy(dst_msg_buffer, src_msg_buffer, client_buffer_size); } } else { result = ResultSessionClosed; } // Select a result for the client. Result client_result = result; if (closed && R_SUCCEEDED(result)) { result = ResultSessionClosed; client_result = ResultSessionClosed; } else { result = ResultSuccess; } // If there's a client thread, update it. if (client_thread != nullptr) { if (event != nullptr) { // // Get the client process/page table. // KProcess *client_process = client_thread->GetOwnerProcess(); // KPageTable *client_page_table = &client_process->PageTable(); // // If we need to, reply with an async error. // if (R_FAILED(client_result)) { // ReplyAsyncError(client_process, client_message, client_buffer_size, // client_result); // } // // Unlock the client buffer. // // NOTE: Nintendo does not check the result of this. // client_page_table->UnlockForIpcUserBuffer(client_message, client_buffer_size); // Signal the event. event->Signal(); } else { // End the client thread's wait. KScopedSchedulerLock sl{kernel}; if (!client_thread->IsTerminationRequested()) { client_thread->EndWait(client_result); } } } return result; } Result KServerSession::ReceiveRequest(std::shared_ptr* out_context, std::weak_ptr manager) { // Lock the session. KScopedLightLock lk{m_lock}; // Get the request and client thread. KSessionRequest* request; KThread* client_thread; { KScopedSchedulerLock sl{kernel}; // Ensure that we can service the request. R_UNLESS(!parent->IsClientClosed(), ResultSessionClosed); // Ensure we aren't already servicing a request. R_UNLESS(m_current_request == nullptr, ResultNotFound); // Ensure we have a request to service. R_UNLESS(!m_request_list.empty(), ResultNotFound); // Pop the first request from the list. request = &m_request_list.front(); m_request_list.pop_front(); // Get the thread for the request. client_thread = request->GetThread(); R_UNLESS(client_thread != nullptr, ResultSessionClosed); // Open the client thread. client_thread->Open(); } SCOPE_EXIT({ client_thread->Close(); }); // Set the request as our current. m_current_request = request; // Get the client address. uintptr_t client_message = request->GetAddress(); size_t client_buffer_size = request->GetSize(); // bool recv_list_broken = false; // Receive the message. Core::Memory::Memory& memory{kernel.System().Memory()}; if (out_context != nullptr) { // HLE request. u32* cmd_buf{reinterpret_cast(memory.GetPointer(client_message))}; *out_context = std::make_shared(kernel, memory, this, client_thread); (*out_context)->SetSessionRequestManager(manager); (*out_context) ->PopulateFromIncomingCommandBuffer(client_thread->GetOwnerProcess()->GetHandleTable(), cmd_buf); } else { KThread* server_thread{GetCurrentThreadPointer(kernel)}; UNIMPLEMENTED_IF(server_thread->GetOwnerProcess() != client_thread->GetOwnerProcess()); auto* src_msg_buffer = memory.GetPointer(client_message); auto* dst_msg_buffer = memory.GetPointer(server_thread->GetTLSAddress()); std::memcpy(dst_msg_buffer, src_msg_buffer, client_buffer_size); } // We succeeded. return ResultSuccess; } void KServerSession::CleanupRequests() { KScopedLightLock lk(m_lock); // Clean up any pending requests. while (true) { // Get the next request. KSessionRequest* request = nullptr; { KScopedSchedulerLock sl{kernel}; if (m_current_request) { // Choose the current request if we have one. request = m_current_request; m_current_request = nullptr; } else if (!m_request_list.empty()) { // Pop the request from the front of the list. request = &m_request_list.front(); m_request_list.pop_front(); } } // If there's no request, we're done. if (request == nullptr) { break; } // Close a reference to the request once it's cleaned up. SCOPE_EXIT({ request->Close(); }); // Extract relevant information from the request. // const uintptr_t client_message = request->GetAddress(); // const size_t client_buffer_size = request->GetSize(); KThread* client_thread = request->GetThread(); KEvent* event = request->GetEvent(); // KProcess *server_process = request->GetServerProcess(); // KProcess *client_process = (client_thread != nullptr) ? // client_thread->GetOwnerProcess() : nullptr; // KProcessPageTable *client_page_table = (client_process != nullptr) ? // &client_process->GetPageTable() : nullptr; // Cleanup the mappings. // Result result = CleanupMap(request, server_process, client_page_table); // If there's a client thread, update it. if (client_thread != nullptr) { if (event != nullptr) { // // We need to reply async. // ReplyAsyncError(client_process, client_message, client_buffer_size, // (R_SUCCEEDED(result) ? ResultSessionClosed : result)); // // Unlock the client buffer. // NOTE: Nintendo does not check the result of this. // client_page_table->UnlockForIpcUserBuffer(client_message, client_buffer_size); // Signal the event. event->Signal(); } else { // End the client thread's wait. KScopedSchedulerLock sl{kernel}; if (!client_thread->IsTerminationRequested()) { client_thread->EndWait(ResultSessionClosed); } } } } } } // namespace Kernel