// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2021 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "common/scope_exit.h" #include "core/hle/kernel/k_client_port.h" #include "core/hle/kernel/k_light_session.h" #include "core/hle/kernel/k_port.h" #include "core/hle/kernel/k_scheduler.h" #include "core/hle/kernel/k_scoped_resource_reservation.h" #include "core/hle/kernel/k_session.h" #include "core/hle/kernel/svc_results.h" namespace Kernel { KClientPort::KClientPort(KernelCore& kernel) : KSynchronizationObject{kernel} {} KClientPort::~KClientPort() = default; void KClientPort::Initialize(KernelCore& kernel, KPort* parent, s32 max_sessions) { // Set member variables. m_num_sessions = 0; m_peak_sessions = 0; m_parent = parent; m_max_sessions = max_sessions; } void KClientPort::OnSessionFinalized(KernelCore& kernel) { KScopedSchedulerLock sl{kernel}; if (const auto prev = m_num_sessions--; prev == m_max_sessions) { this->NotifyAvailable(kernel); } } void KClientPort::OnServerClosed(KernelCore& kernel) {} bool KClientPort::IsLight(KernelCore& kernel) const { return this->GetParent()->IsLight(); } bool KClientPort::IsServerClosed(KernelCore& kernel) const { return this->GetParent()->IsServerClosed(kernel); } void KClientPort::Destroy(KernelCore& kernel) { // Note with our parent that we're closed. m_parent->OnClientClosed(kernel); // Close our reference to our parent. m_parent->Close(kernel); } bool KClientPort::IsSignaled(KernelCore& kernel) const { return m_num_sessions.load() < m_max_sessions; } Result KClientPort::CreateSession(KernelCore& kernel, KClientSession** out) { // Declare the session we're going to allocate. KSession* session{}; // Reserve a new session from the resource limit. KScopedResourceReservation session_reservation(kernel, GetCurrentProcessPointer(kernel), LimitableResource::SessionCountMax); R_UNLESS(session_reservation.Succeeded(), ResultLimitReached); // Allocate a session normally. // TODO: Dynamic resource limits session = KSession::Create(kernel); // Check that we successfully created a session. R_UNLESS(session != nullptr, ResultOutOfResource); // Update the session counts. { ON_RESULT_FAILURE { session->Close(kernel); }; // Atomically increment the number of sessions. s32 new_sessions{}; { const auto max = m_max_sessions; auto cur_sessions = m_num_sessions.load(std::memory_order_acquire); do { R_UNLESS(cur_sessions < max, ResultOutOfSessions); new_sessions = cur_sessions + 1; } while (!m_num_sessions.compare_exchange_weak(cur_sessions, new_sessions, std::memory_order_relaxed)); } // Atomically update the peak session tracking. { auto peak = m_peak_sessions.load(std::memory_order_acquire); do { if (peak >= new_sessions) { break; } } while (!m_peak_sessions.compare_exchange_weak(peak, new_sessions, std::memory_order_relaxed)); } } // Initialize the session. session->Initialize(kernel, this, m_parent->GetName()); // Commit the session reservation. session_reservation.Commit(); // Register the session. KSession::Register(kernel, session); ON_RESULT_FAILURE { session->GetClientSession().Close(kernel); session->GetServerSession().Close(kernel); }; // Enqueue the session with our parent. R_TRY(m_parent->EnqueueSession(kernel, std::addressof(session->GetServerSession()))); // We succeeded, so set the output. *out = std::addressof(session->GetClientSession()); R_SUCCEED(); } Result KClientPort::CreateLightSession(KernelCore& kernel, KLightClientSession** out) { // Declare the session we're going to allocate. KLightSession* session{}; // Reserve a new session from the resource limit. KScopedResourceReservation session_reservation(kernel, GetCurrentProcessPointer(kernel), Svc::LimitableResource::SessionCountMax); R_UNLESS(session_reservation.Succeeded(), ResultLimitReached); // Allocate a session normally. // TODO: Dynamic resource limits session = KLightSession::Create(kernel); // Check that we successfully created a session. R_UNLESS(session != nullptr, ResultOutOfResource); // Update the session counts. { ON_RESULT_FAILURE { session->Close(kernel); }; // Atomically increment the number of sessions. s32 new_sessions; { const auto max = m_max_sessions; auto cur_sessions = m_num_sessions.load(std::memory_order_acquire); do { R_UNLESS(cur_sessions < max, ResultOutOfSessions); new_sessions = cur_sessions + 1; } while (!m_num_sessions.compare_exchange_weak(cur_sessions, new_sessions, std::memory_order_relaxed)); } // Atomically update the peak session tracking. { auto peak = m_peak_sessions.load(std::memory_order_acquire); do { if (peak >= new_sessions) { break; } } while (!m_peak_sessions.compare_exchange_weak(peak, new_sessions, std::memory_order_relaxed)); } } // Initialize the session. session->Initialize(kernel, this, m_parent->GetName()); // Commit the session reservation. session_reservation.Commit(); // Register the session. KLightSession::Register(kernel, session); ON_RESULT_FAILURE { session->GetClientSession().Close(kernel); session->GetServerSession().Close(kernel); }; // Enqueue the session with our parent. R_TRY(m_parent->EnqueueSession(kernel, std::addressof(session->GetServerSession()))); // We succeeded, so set the output. *out = std::addressof(session->GetClientSession()); R_SUCCEED(); } } // namespace Kernel