/*
* Copyright (c) 2018-2020 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 .
*/
#pragma once
#include
#include
#include
#include
namespace ams::tipc {
template typename _Allocator>
struct PortMeta {
static constexpr inline size_t MaxSessions = NumSessions;
using ServiceObject = tipc::ServiceObject;
using Allocator = _Allocator;
};
struct DummyDeferralManager{
struct Key{};
};
class PortManagerInterface {
public:
virtual Result ProcessRequest(WaitableObject &object) = 0;
};
template
class ServerManagerImpl {
private:
static_assert(util::IsAligned(ThreadStackSize, os::ThreadStackAlignment));
static constexpr inline bool IsDeferralSupported = !std::same_as;
using ResumeKey = typename DeferralManagerType::Key;
static ALWAYS_INLINE uintptr_t ConvertKeyToMessage(ResumeKey key) {
static_assert(sizeof(key) <= sizeof(uintptr_t));
static_assert(std::is_trivial::value);
/* TODO: std::bit_cast */
uintptr_t converted = 0;
std::memcpy(std::addressof(converted), std::addressof(key), sizeof(key));
return converted;
}
static ALWAYS_INLINE ResumeKey ConvertMessageToKey(uintptr_t message) {
static_assert(sizeof(ResumeKey) <= sizeof(uintptr_t));
static_assert(std::is_trivial::value);
/* TODO: std::bit_cast */
ResumeKey converted = {};
std::memcpy(std::addressof(converted), std::addressof(message), sizeof(converted));
return converted;
}
static constexpr inline size_t NumPorts = sizeof...(PortInfos);
static constexpr inline size_t MaxSessions = (PortInfos::MaxSessions + ...);
/* Verify that it's possible to service this many sessions, with our port manager count. */
static_assert(MaxSessions <= NumPorts * svc::ArgumentHandleCountMax);
template requires (Ix < NumPorts)
static constexpr inline size_t SessionsPerPortManager = (Ix == NumPorts - 1) ? ((MaxSessions / NumPorts) + MaxSessions % NumPorts)
: ((MaxSessions / NumPorts));
template requires (Ix < NumPorts)
using PortInfo = typename std::tuple_element>::type;
public:
class PortManagerBase : public PortManagerInterface {
public:
enum MessageType {
MessageType_AddSession = 0,
MessageType_TriggerResume = 1,
};
protected:
s32 m_id;
std::atomic m_num_sessions;
s32 m_port_number;
os::WaitableManagerType m_waitable_manager;
DeferralManagerType m_deferral_manager;
os::MessageQueueType m_message_queue;
os::WaitableHolderType m_message_queue_holder;
uintptr_t m_message_queue_storage[MaxSessions];
ObjectManagerBase *m_object_manager;
public:
PortManagerBase() : m_id(), m_num_sessions(), m_port_number(), m_waitable_manager(), m_deferral_manager(), m_message_queue(), m_message_queue_holder(), m_message_queue_storage(), m_object_manager() {
/* Setup our message queue. */
os::InitializeMessageQueue(std::addressof(m_message_queue), m_message_queue_storage, util::size(m_message_queue_storage));
os::InitializeWaitableHolder(std::addressof(m_message_queue_holder), std::addressof(m_message_queue), os::MessageQueueWaitType::ForNotEmpty);
}
void InitializeBase(s32 id, ObjectManagerBase *manager) {
/* Set our id. */
m_id = id;
/* Reset our session count. */
m_num_sessions = 0;
/* Initialize our waitable manager. */
os::InitializeWaitableManager(std::addressof(m_waitable_manager));
os::LinkWaitableHolder(std::addressof(m_waitable_manager), std::addressof(m_message_queue_holder));
/* Initialize our object manager. */
m_object_manager = manager;
}
void RegisterPort(s32 index, svc::Handle port_handle) {
/* Set our port number. */
this->m_port_number = index;
/* Create a waitable object for the port. */
tipc::WaitableObject object;
/* Setup the object. */
object.InitializeAsPort(port_handle);
/* Register the object. */
m_object_manager->AddObject(object);
}
virtual Result ProcessRequest(WaitableObject &object) override {
/* Process the request, this must succeed because we succeeded when deferring earlier. */
R_ABORT_UNLESS(m_object_manager->ProcessRequest(object));
/* NOTE: We support nested deferral, where Nintendo does not. */
if constexpr (IsDeferralSupported) {
R_UNLESS(!PortManagerBase::IsRequestDeferred(), tipc::ResultRequestDeferred());
}
/* Reply to the request. */
return m_object_manager->Reply(object.GetHandle());
}
Result ReplyAndReceive(os::WaitableHolderType **out_holder, WaitableObject *out_object, svc::Handle reply_target) {
return m_object_manager->ReplyAndReceive(out_holder, out_object, reply_target, std::addressof(m_waitable_manager));
}
void StartRegisterRetry(ResumeKey key) {
/* Begin the retry. */
m_deferral_manager.StartRegisterRetry(key);
}
bool TestResume(ResumeKey key) {
/* Check to see if the key corresponds to some deferred message. */
return m_deferral_manager.TestResume(key);
}
void TriggerResume(ResumeKey key) {
/* Send the key as a message. */
os::SendMessageQueue(std::addressof(m_message_queue), static_cast(MessageType_TriggerResume));
os::SendMessageQueue(std::addressof(m_message_queue), ConvertKeyToMessage(key));
}
private:
static bool IsRequestDeferred() {
if constexpr (IsDeferralSupported) {
/* Get the message buffer. */
const svc::ipc::MessageBuffer message_buffer(svc::ipc::GetMessageBuffer());
/* Parse the hipc headers. */
const svc::ipc::MessageBuffer::MessageHeader message_header(message_buffer);
const svc::ipc::MessageBuffer::SpecialHeader special_header(message_buffer, message_header);
/* Determine raw data index and extents. */
const auto raw_data_offset = message_buffer.GetRawDataIndex(message_header, special_header);
const auto raw_data_count = message_header.GetRawCount();
/* Result is the last raw data word. */
const Result method_result = message_buffer.GetRaw(raw_data_offset + raw_data_count - 1);
/* Check that the result is the special deferral result. */
return tipc::ResultRequestDeferred::Includes(method_result);
} else {
/* If deferral isn't supported, requests are never deferred. */
return false;
}
}
};
template
class PortManagerImpl final : public PortManagerBase {
private:
tipc::ObjectManager m_object_manager_impl;
public:
PortManagerImpl() : PortManagerBase(), m_object_manager_impl() {
/* ... */
}
void Initialize(s32 id) {
/* Initialize our base. */
this->InitializeBase(id, std::addressof(m_object_manager_impl));
/* Initialize our object manager. */
m_object_manager_impl->Initialize(std::addressof(this->m_waitable_manager));
}
};
template
using PortManager = PortManagerImpl, SessionsPerPortManager>;
using PortManagerTuple = decltype([](std::index_sequence) {
return std::tuple...>{};
}(std::make_index_sequence(NumPorts)));
using PortAllocatorTuple = std::tuple;
private:
os::SdkMutex m_mutex;
os::TlsSlot m_tls_slot;
PortManagerTuple m_port_managers;
PortAllocatorTuple m_port_allocators;
os::ThreadType m_port_threads[NumPorts - 1];
alignas(os::ThreadStackAlignment) u8 m_port_stacks[ThreadStackSize * (NumPorts - 1)];
private:
template
ALWAYS_INLINE auto &GetPortManager() {
return std::get(m_port_managers);
}
template
ALWAYS_INLINE const auto &GetPortManager() const {
return std::get(m_port_managers);
}
template
void LoopAutoForPort() {
R_ABORT_UNLESS(this->LoopProcess(this->GetPortManager()));
}
template
static void LoopAutoForPortThreadFunction(void *_this) {
static_cast(_this)->LoopAutoForPort();
}
template
void InitializePortThread(s32 priority) {
/* Create the thread. */
R_ABORT_UNLESS(os::CreateThread(m_port_threads + Ix, LoopAutoForPortThreadFunction, this, m_port_stacks + Ix, ThreadStackSize, priority));
/* Start the thread. */
os::StartThread(m_port_threads + Ix);
}
public:
ServerManagerImpl() : m_mutex(), m_tls_slot(), m_port_managers(), m_port_allocators() { /* ... */ }
os::TlsSlot GetTlsSlot() const { return m_tls_slot; }
void Initialize() {
/* Initialize our tls slot. */
if constexpr (IsDeferralSupported) {
R_ABORT_UNLESS(os::SdkAllocateTlsSlot(std::addressof(m_tls_slot), nullptr));
}
/* Initialize our port managers. */
[this](std::index_sequence) ALWAYS_INLINE_LAMBDA {
(this->GetPortManager().Initialize(static_cast(Ix)), ...);
}(std::make_index_sequence(NumPorts));
}
template
void RegisterPort(svc::Handle port_handle) {
this->GetPortManager().RegisterPort(static_cast(Ix), port_handle);
}
void LoopAuto() {
/* If we have additional threads, create and start them. */
if constexpr (NumPorts > 1) {
const auto thread_priority = os::GetThreadPriority(os::GetCurrentThread());
[thread_priority, this](std::index_sequence) ALWAYS_INLINE_LAMBDA {
/* Create all threads. */
(this->InitializePortThread(thread_priority), ...);
}(std::make_index_sequence(NumPorts - 1));
}
/* Process for the last port. */
this->LoopAutoForPort();
}
private:
Result LoopProcess(PortManagerBase &port_manager) {
/* Set our tls slot's value to be the port manager we're processing for. */
if constexpr (IsDeferralSupported) {
os::SetTlsValue(this->GetTlsSlot(), reinterpret_cast(std::addressof(port_manager)));
}
/* Clear the message buffer. */
/* NOTE: Nintendo only clears the hipc header. */
std::memset(svc::ipc::GetMessageBuffer(), 0, svc::ipc::MessageBufferSize);
/* Process requests forever. */
svc::Handle reply_target = svc::InvalidHandle;
while (true) {
/* TODO */
}
}
};
template
using ServerManagerWithDeferral = ServerManagerImpl;
template
using ServerManagerWithDeferralAndThreadStack = ServerManagerImpl;
template
using ServerManager = ServerManagerImpl;
template
using ServerManagerWithThreadStack = ServerManagerImpl;
}