Browse Source
Merge pull request #12299 from liamwhite/light-ipc
Merge pull request #12299 from liamwhite/light-ipc
kernel: implement light IPCnce_cpp
committed by
GitHub
18 changed files with 813 additions and 20 deletions
-
6src/core/CMakeLists.txt
-
69src/core/hle/kernel/k_client_port.cpp
-
2src/core/hle/kernel/k_client_port.h
-
31src/core/hle/kernel/k_light_client_session.cpp
-
39src/core/hle/kernel/k_light_client_session.h
-
247src/core/hle/kernel/k_light_server_session.cpp
-
49src/core/hle/kernel/k_light_server_session.h
-
81src/core/hle/kernel/k_light_session.cpp
-
86src/core/hle/kernel/k_light_session.h
-
9src/core/hle/kernel/k_port.cpp
-
2src/core/hle/kernel/k_port.h
-
56src/core/hle/kernel/k_server_port.cpp
-
5src/core/hle/kernel/k_server_port.h
-
7src/core/hle/kernel/k_thread.h
-
4src/core/hle/kernel/kernel.cpp
-
29src/core/hle/kernel/svc/svc_light_ipc.cpp
-
71src/core/hle/kernel/svc/svc_port.cpp
-
40src/core/hle/kernel/svc/svc_session.cpp
@ -0,0 +1,31 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
|
||||
|
#include "core/hle/kernel/k_light_client_session.h"
|
||||
|
#include "core/hle/kernel/k_light_session.h"
|
||||
|
#include "core/hle/kernel/k_thread.h"
|
||||
|
|
||||
|
namespace Kernel { |
||||
|
|
||||
|
KLightClientSession::KLightClientSession(KernelCore& kernel) : KAutoObject(kernel) {} |
||||
|
|
||||
|
KLightClientSession::~KLightClientSession() = default; |
||||
|
|
||||
|
void KLightClientSession::Destroy() { |
||||
|
m_parent->OnClientClosed(); |
||||
|
} |
||||
|
|
||||
|
void KLightClientSession::OnServerClosed() {} |
||||
|
|
||||
|
Result KLightClientSession::SendSyncRequest(u32* data) { |
||||
|
// Get the request thread.
|
||||
|
KThread* cur_thread = GetCurrentThreadPointer(m_kernel); |
||||
|
|
||||
|
// Set the light data.
|
||||
|
cur_thread->SetLightSessionData(data); |
||||
|
|
||||
|
// Send the request.
|
||||
|
R_RETURN(m_parent->OnRequest(cur_thread)); |
||||
|
} |
||||
|
|
||||
|
} // namespace Kernel
|
||||
@ -0,0 +1,39 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "core/hle/kernel/k_auto_object.h" |
||||
|
#include "core/hle/result.h" |
||||
|
|
||||
|
namespace Kernel { |
||||
|
|
||||
|
class KLightSession; |
||||
|
|
||||
|
class KLightClientSession final : public KAutoObject { |
||||
|
KERNEL_AUTOOBJECT_TRAITS(KLightClientSession, KAutoObject); |
||||
|
|
||||
|
public: |
||||
|
explicit KLightClientSession(KernelCore& kernel); |
||||
|
~KLightClientSession(); |
||||
|
|
||||
|
void Initialize(KLightSession* parent) { |
||||
|
// Set member variables. |
||||
|
m_parent = parent; |
||||
|
} |
||||
|
|
||||
|
virtual void Destroy() override; |
||||
|
|
||||
|
const KLightSession* GetParent() const { |
||||
|
return m_parent; |
||||
|
} |
||||
|
|
||||
|
Result SendSyncRequest(u32* data); |
||||
|
|
||||
|
void OnServerClosed(); |
||||
|
|
||||
|
private: |
||||
|
KLightSession* m_parent; |
||||
|
}; |
||||
|
|
||||
|
} // namespace Kernel |
||||
@ -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
|
||||
@ -0,0 +1,49 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "core/hle/kernel/k_auto_object.h" |
||||
|
#include "core/hle/kernel/k_thread.h" |
||||
|
#include "core/hle/result.h" |
||||
|
|
||||
|
namespace Kernel { |
||||
|
|
||||
|
class KLightSession; |
||||
|
|
||||
|
class KLightServerSession final : public KAutoObject, |
||||
|
public Common::IntrusiveListBaseNode<KLightServerSession> { |
||||
|
KERNEL_AUTOOBJECT_TRAITS(KLightServerSession, KAutoObject); |
||||
|
|
||||
|
private: |
||||
|
KLightSession* m_parent{}; |
||||
|
KThread::WaiterList m_request_list{}; |
||||
|
KThread* m_current_request{}; |
||||
|
u64 m_server_thread_id{std::numeric_limits<u64>::max()}; |
||||
|
KThread* m_server_thread{}; |
||||
|
|
||||
|
public: |
||||
|
explicit KLightServerSession(KernelCore& kernel); |
||||
|
~KLightServerSession(); |
||||
|
|
||||
|
void Initialize(KLightSession* parent) { |
||||
|
// Set member variables. */ |
||||
|
m_parent = parent; |
||||
|
} |
||||
|
|
||||
|
virtual void Destroy() override; |
||||
|
|
||||
|
constexpr const KLightSession* GetParent() const { |
||||
|
return m_parent; |
||||
|
} |
||||
|
|
||||
|
Result OnRequest(KThread* request_thread); |
||||
|
Result ReplyAndReceive(u32* data); |
||||
|
|
||||
|
void OnClientClosed(); |
||||
|
|
||||
|
private: |
||||
|
void CleanupRequests(); |
||||
|
}; |
||||
|
|
||||
|
} // namespace Kernel |
||||
@ -0,0 +1,81 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
|
||||
|
#include "core/hle/kernel/k_client_port.h"
|
||||
|
#include "core/hle/kernel/k_light_client_session.h"
|
||||
|
#include "core/hle/kernel/k_light_server_session.h"
|
||||
|
#include "core/hle/kernel/k_light_session.h"
|
||||
|
#include "core/hle/kernel/k_process.h"
|
||||
|
|
||||
|
namespace Kernel { |
||||
|
|
||||
|
KLightSession::KLightSession(KernelCore& kernel) |
||||
|
: KAutoObjectWithSlabHeapAndContainer(kernel), m_server(kernel), m_client(kernel) {} |
||||
|
KLightSession::~KLightSession() = default; |
||||
|
|
||||
|
void KLightSession::Initialize(KClientPort* client_port, uintptr_t name) { |
||||
|
// Increment reference count.
|
||||
|
// Because reference count is one on creation, this will result
|
||||
|
// in a reference count of two. Thus, when both server and client are closed
|
||||
|
// this object will be destroyed.
|
||||
|
this->Open(); |
||||
|
|
||||
|
// Create our sub sessions.
|
||||
|
KAutoObject::Create(std::addressof(m_server)); |
||||
|
KAutoObject::Create(std::addressof(m_client)); |
||||
|
|
||||
|
// Initialize our sub sessions.
|
||||
|
m_server.Initialize(this); |
||||
|
m_client.Initialize(this); |
||||
|
|
||||
|
// Set state and name.
|
||||
|
m_state = State::Normal; |
||||
|
m_name = name; |
||||
|
|
||||
|
// Set our owner process.
|
||||
|
m_process = GetCurrentProcessPointer(m_kernel); |
||||
|
m_process->Open(); |
||||
|
|
||||
|
// Set our port.
|
||||
|
m_port = client_port; |
||||
|
if (m_port != nullptr) { |
||||
|
m_port->Open(); |
||||
|
} |
||||
|
|
||||
|
// Mark initialized.
|
||||
|
m_initialized = true; |
||||
|
} |
||||
|
|
||||
|
void KLightSession::Finalize() { |
||||
|
if (m_port != nullptr) { |
||||
|
m_port->OnSessionFinalized(); |
||||
|
m_port->Close(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void KLightSession::OnServerClosed() { |
||||
|
if (m_state == State::Normal) { |
||||
|
m_state = State::ServerClosed; |
||||
|
m_client.OnServerClosed(); |
||||
|
} |
||||
|
|
||||
|
this->Close(); |
||||
|
} |
||||
|
|
||||
|
void KLightSession::OnClientClosed() { |
||||
|
if (m_state == State::Normal) { |
||||
|
m_state = State::ClientClosed; |
||||
|
m_server.OnClientClosed(); |
||||
|
} |
||||
|
|
||||
|
this->Close(); |
||||
|
} |
||||
|
|
||||
|
void KLightSession::PostDestroy(uintptr_t arg) { |
||||
|
// Release the session count resource the owner process holds.
|
||||
|
KProcess* owner = reinterpret_cast<KProcess*>(arg); |
||||
|
owner->ReleaseResource(Svc::LimitableResource::SessionCountMax, 1); |
||||
|
owner->Close(); |
||||
|
} |
||||
|
|
||||
|
} // namespace Kernel
|
||||
@ -0,0 +1,86 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "core/hle/kernel/k_light_client_session.h" |
||||
|
#include "core/hle/kernel/k_light_server_session.h" |
||||
|
#include "core/hle/kernel/slab_helpers.h" |
||||
|
#include "core/hle/result.h" |
||||
|
|
||||
|
namespace Kernel { |
||||
|
|
||||
|
class KClientPort; |
||||
|
class KProcess; |
||||
|
|
||||
|
// TODO: SupportDynamicExpansion for SlabHeap |
||||
|
class KLightSession final |
||||
|
: public KAutoObjectWithSlabHeapAndContainer<KLightSession, KAutoObjectWithList> { |
||||
|
KERNEL_AUTOOBJECT_TRAITS(KLightSession, KAutoObject); |
||||
|
|
||||
|
private: |
||||
|
enum class State : u8 { |
||||
|
Invalid = 0, |
||||
|
Normal = 1, |
||||
|
ClientClosed = 2, |
||||
|
ServerClosed = 3, |
||||
|
}; |
||||
|
|
||||
|
public: |
||||
|
static constexpr size_t DataSize = sizeof(u32) * 7; |
||||
|
static constexpr u32 ReplyFlag = (1U << 31); |
||||
|
|
||||
|
private: |
||||
|
KLightServerSession m_server; |
||||
|
KLightClientSession m_client; |
||||
|
State m_state{State::Invalid}; |
||||
|
KClientPort* m_port{}; |
||||
|
uintptr_t m_name{}; |
||||
|
KProcess* m_process{}; |
||||
|
bool m_initialized{}; |
||||
|
|
||||
|
public: |
||||
|
explicit KLightSession(KernelCore& kernel); |
||||
|
~KLightSession(); |
||||
|
|
||||
|
void Initialize(KClientPort* client_port, uintptr_t name); |
||||
|
void Finalize() override; |
||||
|
|
||||
|
bool IsInitialized() const override { |
||||
|
return m_initialized; |
||||
|
} |
||||
|
uintptr_t GetPostDestroyArgument() const override { |
||||
|
return reinterpret_cast<uintptr_t>(m_process); |
||||
|
} |
||||
|
|
||||
|
static void PostDestroy(uintptr_t arg); |
||||
|
|
||||
|
void OnServerClosed(); |
||||
|
void OnClientClosed(); |
||||
|
|
||||
|
bool IsServerClosed() const { |
||||
|
return m_state != State::Normal; |
||||
|
} |
||||
|
bool IsClientClosed() const { |
||||
|
return m_state != State::Normal; |
||||
|
} |
||||
|
|
||||
|
Result OnRequest(KThread* request_thread) { |
||||
|
R_RETURN(m_server.OnRequest(request_thread)); |
||||
|
} |
||||
|
|
||||
|
KLightClientSession& GetClientSession() { |
||||
|
return m_client; |
||||
|
} |
||||
|
KLightServerSession& GetServerSession() { |
||||
|
return m_server; |
||||
|
} |
||||
|
const KLightClientSession& GetClientSession() const { |
||||
|
return m_client; |
||||
|
} |
||||
|
const KLightServerSession& GetServerSession() const { |
||||
|
return m_server; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
} // namespace Kernel |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue