code:
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <tuple>
#include <utility>

#include "common/assert.h"
#include "common/common_types.h"
#include "common/logging/log.h"
#include "common/scope_exit.h"
#include "common/scratch_buffer.h"
#include "core/core.h"
#include "core/core_timing.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/hle/kernel/message_buffer.h"
#include "core/hle/service/hle_ipc.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/memory.h"

namespace Kernel {

namespace {

constexpr inline size_t PointerTransferBufferAlignment = 0x10;
constexpr inline size_t ReceiveListDataSize =
    MessageBuffer::MessageHeader::ReceiveListCountType_CountMax *
    MessageBuffer::ReceiveListEntry::GetDataSize() / sizeof(u32);

using ThreadQueueImplForKServerSessionRequest = KThreadQueue;

class ReceiveList {
public:
    static constexpr int GetEntryCount(const MessageBuffer::MessageHeader& header) {
        const auto count = header.GetReceiveListCount();
        switch (count) {
        case MessageBuffer::MessageHeader::ReceiveListCountType_None:
            return 0;
        case MessageBuffer::MessageHeader::ReceiveListCountType_ToMessageBuffer:
            return 0;
        case MessageBuffer::MessageHeader::ReceiveListCountType_ToSingleBuffer:
            return 1;
        default:
            return count - MessageBuffer::MessageHeader::ReceiveListCountType_CountOffset;
        }
    }

    explicit ReceiveList(const u32* dst_msg, uint64_t dst_address,
                         KProcessPageTable& dst_page_table,
                         const MessageBuffer::MessageHeader& dst_header,
                         const MessageBuffer::SpecialHeader& dst_special_header, size_t msg_size,
                         size_t out_offset, s32 dst_recv_list_idx, bool is_tls) {
        m_recv_list_count = dst_header.GetReceiveListCount();
        m_msg_buffer_end = dst_address + sizeof(u32) * out_offset;
        m_msg_buffer_space_end = dst_address + msg_size;

        // NOTE: Nintendo calculates the receive list index here using the special header.
        // We pre-calculate it in the caller, and pass it as a parameter.
        (void)dst_special_header;

        const u32* recv_list = dst_msg + dst_recv_list_idx;
        const auto entry_count = GetEntryCount(dst_header);

        if (is_tls) {
            // Messages from TLS to TLS are contained within one page.
            std::memcpy(m_data.data(), recv_list,
                        entry_count * MessageBuffer::ReceiveListEntry::GetDataSize());
        } else {
            // If any buffer is not from TLS, perform a normal read instead.
            uint64_t cur_addr = dst_address + dst_recv_list_idx * sizeof(u32);
            dst_page_table.GetMemory().ReadBlock(
                cur_addr, m_data.data(),
                entry_count * MessageBuffer::ReceiveListEntry::GetDataSize());
        }
    }

    bool IsIndex() const {
        return m_recv_list_count >
               static_cast<s32>(MessageBuffer::MessageHeader::ReceiveListCountType_CountOffset);
    }

    bool IsToMessageBuffer() const {
        return m_recv_list_count ==
               MessageBuffer::MessageHeader::ReceiveListCountType_ToMessageBuffer;
    }

    void GetBuffer(uint64_t& out, size_t size, int& key) const {
        switch (m_recv_list_count) {
        case MessageBuffer::MessageHeader::ReceiveListCountType_None: {
            out = 0;
            break;
        }
        case MessageBuffer::MessageHeader::ReceiveListCountType_ToMessageBuffer: {
            const uint64_t buf =
                Common::AlignUp(m_msg_buffer_end + key, PointerTransferBufferAlignment);

            if ((buf < buf + size) && (buf + size <= m_msg_buffer_space_end)) {
                out = buf;
                key = static_cast<int>(buf + size - m_msg_buffer_end);
            } else {
                out = 0;
            }
            break;
        }
        case MessageBuffer::MessageHeader::ReceiveListCountType_ToSingleBuffer: {
            const MessageBuffer::ReceiveListEntry entry(m_data[0], m_data[1]);
            const uint64_t buf =
                Common::AlignUp(entry.GetAddress() + key, PointerTransferBufferAlignment);

            const uint64_t entry_addr = entry.GetAddress();
            const size_t entry_size = entry.GetSize();

            if ((buf < buf + size) && (entry_addr < entry_addr + entry_size) &&
                (buf + size <= entry_addr + entry_size)) {
                out = buf;
                key = static_cast<int>(buf + size - entry_addr);
            } else {
                out = 0;
            }
            break;
        }
        default: {
            if (key < m_recv_list_count -
                          static_cast<s32>(
                              MessageBuffer::MessageHeader::ReceiveListCountType_CountOffset)) {
                const MessageBuffer::ReceiveListEntry entry(m_data[2 * key + 0],
                                                            m_data[2 * key + 1]);

                const uintptr_t entry_addr = entry.GetAddress();
                const size_t entry_size = entry.GetSize();

                if ((entry_addr < entry_addr + entry_size) && (entry_size >= size)) {
                    out = entry_addr;
                }
            } else {
                out = 0;
            }
            break;
        }
        }
    }

private:
    std::array<u32, ReceiveListDataSize> m_data;
    s32 m_recv_list_count;
    uint64_t m_msg_buffer_end;
    uint64_t m_msg_buffer_space_end;
};

template <bool MoveHandleAllowed>
Result ProcessMessageSpecialData(s32& offset, KProcess& dst_process, KProcess& src_process,
                                 KThread& src_thread, const MessageBuffer& dst_msg,
                                 const MessageBuffer& src_msg,
                                 const MessageBuffer::SpecialHeader& src_special_header) {
    // Copy the special header to the destination.
    offset = dst_msg.Set(src_special_header);

    // Copy the process ID.
    if (src_special_header.GetHasProcessId()) {
        offset = dst_msg.SetProcessId(offset, src_process.GetProcessId());
    }

    // Prepare to process handles.
    auto& dst_handle_table = dst_process.GetHandleTable();
    auto& src_handle_table = src_process.GetHandleTable();
    Result result = ResultSuccess;

    // Process copy handles.
    for (auto i = 0; i < src_special_header.GetCopyHandleCount(); ++i) {
        // Get the handles.
        const Handle src_handle = src_msg.GetHandle(offset);
        Handle dst_handle = Svc::InvalidHandle;

        // If we're in a success state, try to move the handle to the new table.
        if (R_SUCCEEDED(result) && src_handle != Svc::InvalidHandle) {
            KScopedAutoObject obj =
                src_handle_table.GetObjectForIpc(src_handle, std::addressof(src_thread));
            if (obj.IsNotNull()) {
                Result add_result =
                    dst_handle_table.Add(std::addressof(dst_handle), obj.GetPointerUnsafe());
                if (R_FAILED(add_result)) {
                    result = add_result;
                    dst_handle = Svc::InvalidHandle;
                }
            } else {
                result = ResultInvalidHandle;
            }
        }

        // Set the handle.
        offset = dst_msg.SetHandle(offset, dst_handle);
    }

    // Process move handles.
    if constexpr (MoveHandleAllowed) {
        for (auto i = 0; i < src_special_header.GetMoveHandleCount(); ++i) {
            // Get the handles.
            const Handle src_handle = src_msg.GetHandle(offset);
            Handle dst_handle = Svc::InvalidHandle;

            // Whether or not we've succeeded, we need to remove the handles from the source table.
            if (src_handle != Svc::InvalidHandle) {
                if (R_SUCCEEDED(result)) {
                    KScopedAutoObject obj =
                        src_handle_table.GetObjectForIpcWithoutPseudoHandle(src_handle);
                    if (obj.IsNotNull()) {
                        Result add_result = dst_handle_table.Add(std::addressof(dst_handle),
                                                                 obj.GetPointerUnsafe());

                        src_handle_table.Remove(src_handle);

                        if (R_FAILED(add_result)) {
                            result = add_result;
                            dst_handle = Svc::InvalidHandle;
                        }
                    } else {
                        result = ResultInvalidHandle;
                    }
                } else {
                    src_handle_table.Remove(src_handle);
                }
            }

            // Set the handle.
            offset = dst_msg.SetHandle(offset, dst_handle);
        }
    }

    R_RETURN(result);
}

Result ProcessReceiveMessagePointerDescriptors(int& offset, int& pointer_key,
                                               KProcessPageTable& dst_page_table,
                                               KProcessPageTable& src_page_table,
                                               const MessageBuffer& dst_msg,
                                               const MessageBuffer& src_msg,
                                               const ReceiveList& dst_recv_list, bool dst_user) {
    // Get the offset at the start of processing.
    const int cur_offset = offset;

    // Get the pointer desc.
    MessageBuffer::PointerDescriptor src_desc(src_msg, cur_offset);
    offset += static_cast<int>(MessageBuffer::PointerDescriptor::GetDataSize() / sizeof(u32));

    // Extract address/size.
    const uint64_t src_pointer = src_desc.GetAddress();
    const size_t recv_size = src_desc.GetSize();
    uint64_t recv_pointer = 0;

    // Process the buffer, if it has a size.
    if (recv_size > 0) {
        // If using indexing, set index.
        if (dst_recv_list.IsIndex()) {
            pointer_key = src_desc.GetIndex();
        }

        // Get the buffer.
        dst_recv_list.GetBuffer(recv_pointer, recv_size, pointer_key);
        R_UNLESS(recv_pointer != 0, ResultOutOfResource);

        // Perform the pointer data copy.
        if (dst_user) {
            R_TRY(src_page_table.CopyMemoryFromHeapToHeapWithoutCheckDestination(
                dst_page_table, recv_pointer, recv_size, KMemoryState::FlagReferenceCounted,
                KMemoryState::FlagReferenceCounted,
                KMemoryPermission::NotMapped | KMemoryPermission::KernelReadWrite,
                KMemoryAttribute::Uncached | KMemoryAttribute::Locked, KMemoryAttribute::Locked,
                src_pointer, KMemoryState::FlagLinearMapped, KMemoryState::FlagLinearMapped,
                KMemoryPermission::UserRead, KMemoryAttribute::Uncached, KMemoryAttribute::None));
        } else {
            R_TRY(src_page_table.CopyMemoryFromLinearToUser(
                recv_pointer, recv_size, src_pointer, KMemoryState::FlagLinearMapped,
                KMemoryState::FlagLinearMapped, KMemoryPermission::UserRead,
                KMemoryAttribute::Uncached, KMemoryAttribute::None));
        }
    }

    // Set the output descriptor.
    dst_msg.Set(cur_offset, MessageBuffer::PointerDescriptor(reinterpret_cast<void*>(recv_pointer),
                                                             recv_size, src_desc.GetIndex()));

    R_SUCCEED();
}

constexpr Result GetMapAliasMemoryState(KMemoryState& out,
                                        MessageBuffer::MapAliasDescriptor::Attribute attr) {
    switch (attr) {
    case MessageBuffer::MapAliasDescriptor::Attribute::Ipc:
        out = KMemoryState::Ipc;
        break;
    case MessageBuffer::MapAliasDescriptor::Attribute::NonSecureIpc:
        out = KMemoryState::NonSecureIpc;
        break;
    case MessageBuffer::MapAliasDescriptor::Attribute::NonDeviceIpc:
        out = KMemoryState::NonDeviceIpc;
        break;
    default:
        R_THROW(ResultInvalidCombination);
    }

    R_SUCCEED();
}

constexpr Result GetMapAliasTestStateAndAttributeMask(KMemoryState& out_state,
                                                      KMemoryAttribute& out_attr_mask,
                                                      KMemoryState state) {
    switch (state) {
    case KMemoryState::Ipc:
        out_state = KMemoryState::FlagCanUseIpc;
        out_attr_mask =
            KMemoryAttribute::Uncached | KMemoryAttribute::DeviceShared | KMemoryAttribute::Locked;
        break;
    case KMemoryState::NonSecureIpc:
        out_state = KMemoryState::FlagCanUseNonSecureIpc;
        out_attr_mask = KMemoryAttribute::Uncached | KMemoryAttribute::Locked;
        break;
    case KMemoryState::NonDeviceIpc:
        out_state = KMemoryState::FlagCanUseNonDeviceIpc;
        out_attr_mask = KMemoryAttribute::Uncached | KMemoryAttribute::Locked;
        break;
    default:
        R_THROW(ResultInvalidCombination);
    }

    R_SUCCEED();
}

void CleanupSpecialData(KProcess& dst_process, u32* dst_msg_ptr, size_t dst_buffer_size) {
    // Parse the message.
    const MessageBuffer dst_msg(dst_msg_ptr, dst_buffer_size);
    const MessageBuffer::MessageHeader dst_header(dst_msg);
    const MessageBuffer::SpecialHeader dst_special_header(dst_msg, dst_header);

    // Check that the size is big enough.
    if (MessageBuffer::GetMessageBufferSize(dst_header, dst_special_header) > dst_buffer_size) {
        return;
    }

    // Set the special header.
    int offset = dst_msg.Set(dst_special_header);

    // Clear the process id, if needed.
    if (dst_special_header.GetHasProcessId()) {
        offset = dst_msg.SetProcessId(offset, 0);
    }

    // Clear handles, as relevant.
    auto& dst_handle_table = dst_process.GetHandleTable();
    for (auto i = 0;
         i < (dst_special_header.GetCopyHandleCount() + dst_special_header.GetMoveHandleCount());
         ++i) {
        const Handle handle = dst_msg.GetHandle(offset);

        if (handle != Svc::InvalidHandle) {
            dst_handle_table.Remove(handle);
        }

        offset = dst_msg.SetHandle(offset, Svc::InvalidHandle);
    }
}

Result CleanupServerHandles(KernelCore& kernel, uint64_t message, size_t buffer_size,
                            KPhysicalAddress message_paddr) {
    // Server is assumed to be current thread.
    KThread& thread = GetCurrentThread(kernel);

    // Get the linear message pointer.
    u32* msg_ptr;
    if (message) {
        msg_ptr = kernel.System().DeviceMemory().GetPointer<u32>(message_paddr);
    } else {
        msg_ptr = GetCurrentMemory(kernel).GetPointer<u32>(thread.GetTlsAddress());
        buffer_size = MessageBufferSize;
        message = GetInteger(thread.GetTlsAddress());
    }

    // Parse the message.
    const MessageBuffer msg(msg_ptr, buffer_size);
    const MessageBuffer::MessageHeader header(msg);
    const MessageBuffer::SpecialHeader special_header(msg, header);

    // Check that the size is big enough.
    R_UNLESS(MessageBuffer::GetMessageBufferSize(header, special_header) <= buffer_size,
             ResultInvalidCombination);

    // If there's a special header, there may be move handles we need to close.
    if (header.GetHasSpecialHeader()) {
        // Determine the offset to the start of handles.
        auto offset = msg.GetSpecialDataIndex(header, special_header);
        if (special_header.GetHasProcessId()) {
            offset += static_cast<int>(sizeof(u64) / sizeof(u32));
        }
        if (auto copy_count = special_header.GetCopyHandleCount(); copy_count > 0) {
            offset += static_cast<int>((sizeof(Svc::Handle) * copy_count) / sizeof(u32));
        }

        // Get the handle table.
        auto& handle_table = thread.GetOwnerProcess()->GetHandleTable();

        // Close the handles.
        for (auto i = 0; i < special_header.GetMoveHandleCount(); ++i) {
            handle_table.Remove(msg.GetHandle(offset));
            offset += static_cast<int>(sizeof(Svc::Handle) / sizeof(u32));
        }
    }

    R_SUCCEED();
}

Result CleanupServerMap(KSessionRequest* request, KProcess* server_process) {
    // If there's no server process, there's nothing to clean up.
    R_SUCCEED_IF(server_process == nullptr);

    // Get the page table.
    auto& server_page_table = server_process->GetPageTable();

    // Cleanup Send mappings.
    for (size_t i = 0; i < request->GetSendCount(); ++i) {
        R_TRY(server_page_table.CleanupForIpcServer(request->GetSendServerAddress(i),
                                                    request->GetSendSize(i),
                                                    request->GetSendMemoryState(i)));
    }

    // Cleanup Receive mappings.
    for (size_t i = 0; i < request->GetReceiveCount(); ++i) {
        R_TRY(server_page_table.CleanupForIpcServer(request->GetReceiveServerAddress(i),
                                                    request->GetReceiveSize(i),
                                                    request->GetReceiveMemoryState(i)));
    }

    // Cleanup Exchange mappings.
    for (size_t i = 0; i < request->GetExchangeCount(); ++i) {
        R_TRY(server_page_table.CleanupForIpcServer(request->GetExchangeServerAddress(i),
                                                    request->GetExchangeSize(i),
                                                    request->GetExchangeMemoryState(i)));
    }

    R_SUCCEED();
}

Result CleanupClientMap(KSessionRequest* request, KProcessPageTable* client_page_table) {
    // If there's no client page table, there's nothing to clean up.
    R_SUCCEED_IF(client_page_table == nullptr);

    // Cleanup Send mappings.
    for (size_t i = 0; i < request->GetSendCount(); ++i) {
        R_TRY(client_page_table->CleanupForIpcClient(request->GetSendClientAddress(i),
                                                     request->GetSendSize(i),
                                                     request->GetSendMemoryState(i)));
    }

    // Cleanup Receive mappings.
    for (size_t i = 0; i < request->GetReceiveCount(); ++i) {
        R_TRY(client_page_table->CleanupForIpcClient(request->GetReceiveClientAddress(i),
                                                     request->GetReceiveSize(i),
                                                     request->GetReceiveMemoryState(i)));
    }

    // Cleanup Exchange mappings.
    for (size_t i = 0; i < request->GetExchangeCount(); ++i) {
        R_TRY(client_page_table->CleanupForIpcClient(request->GetExchangeClientAddress(i),
                                                     request->GetExchangeSize(i),
                                                     request->GetExchangeMemoryState(i)));
    }

    R_SUCCEED();
}

Result CleanupMap(KSessionRequest* request, KProcess* server_process,
                  KProcessPageTable* client_page_table) {
    // Cleanup the server map.
    R_TRY(CleanupServerMap(request, server_process));

    // Cleanup the client map.
    R_TRY(CleanupClientMap(request, client_page_table));

    R_SUCCEED();
}

Result ProcessReceiveMessageMapAliasDescriptors(int& offset, KProcessPageTable& dst_page_table,
                                                KProcessPageTable& src_page_table,
                                                const MessageBuffer& dst_msg,
                                                const MessageBuffer& src_msg,
                                                KSessionRequest* request, KMemoryPermission perm,
                                                bool send) {
    // Get the offset at the start of processing.
    const int cur_offset = offset;

    // Get the map alias descriptor.
    MessageBuffer::MapAliasDescriptor src_desc(src_msg, cur_offset);
    offset += static_cast<int>(MessageBuffer::MapAliasDescriptor::GetDataSize() / sizeof(u32));

    // Extract address/size.
    const KProcessAddress src_address = src_desc.GetAddress();
    const size_t size = src_desc.GetSize();
    KProcessAddress dst_address = 0;

    // Determine the result memory state.
    KMemoryState dst_state;
    R_TRY(GetMapAliasMemoryState(dst_state, src_desc.GetAttribute()));

    // Process the buffer, if it has a size.
    if (size > 0) {
        // Set up the source pages for ipc.
        R_TRY(dst_page_table.SetupForIpc(std::addressof(dst_address), size, src_address,
                                         src_page_table, perm, dst_state, send));

        // Ensure that we clean up on failure.
        ON_RESULT_FAILURE {
            dst_page_table.CleanupForIpcServer(dst_address, size, dst_state);
            src_page_table.CleanupForIpcClient(src_address, size, dst_state);
        };

        // Push the appropriate mapping.
        if (perm == KMemoryPermission::UserRead) {
            R_TRY(request->PushSend(src_address, dst_address, size, dst_state));
        } else if (send) {
            R_TRY(request->PushExchange(src_address, dst_address, size, dst_state));
        } else {
            R_TRY(request->PushReceive(src_address, dst_address, size, dst_state));
        }
    }

    // Set the output descriptor.
    dst_msg.Set(cur_offset,
                MessageBuffer::MapAliasDescriptor(reinterpret_cast<void*>(GetInteger(dst_address)),
                                                  size, src_desc.GetAttribute()));

    R_SUCCEED();
}

Result ReceiveMessage(KernelCore& kernel, bool& recv_list_broken, uint64_t dst_message_buffer,
                      size_t dst_buffer_size, KPhysicalAddress dst_message_paddr,
                      KThread& src_thread, uint64_t src_message_buffer, size_t src_buffer_size,
                      KServerSession* session, KSessionRequest* request) {
    // Prepare variables for receive.
    KThread& dst_thread = GetCurrentThread(kernel);
    KProcess& dst_process = *(dst_thread.GetOwnerProcess());
    KProcess& src_process = *(src_thread.GetOwnerProcess());
    auto& dst_page_table = dst_process.GetPageTable();
    auto& src_page_table = src_process.GetPageTable();

    // NOTE: Session is used only for debugging, and so may go unused.
    (void)session;

    // The receive list is initially not broken.
    recv_list_broken = false;

    // Set the server process for the request.
    request->SetServerProcess(std::addressof(dst_process));

    // Determine the message buffers.
    u32 *dst_msg_ptr, *src_msg_ptr;
    bool dst_user, src_user;

    if (dst_message_buffer) {
        dst_msg_ptr = kernel.System().DeviceMemory().GetPointer<u32>(dst_message_paddr);
        dst_user = true;
    } else {
        dst_msg_ptr = dst_page_table.GetMemory().GetPointer<u32>(dst_thread.GetTlsAddress());
        dst_buffer_size = MessageBufferSize;
        dst_message_buffer = GetInteger(dst_thread.GetTlsAddress());
        dst_user = false;
    }

    if (src_message_buffer) {
        // NOTE: Nintendo does not check the result of this GetPhysicalAddress call.
        src_msg_ptr = src_page_table.GetMemory().GetPointer<u32>(src_message_buffer);
        src_user = true;
    } else {
        src_msg_ptr = src_page_table.GetMemory().GetPointer<u32>(src_thread.GetTlsAddress());
        src_buffer_size = MessageBufferSize;
        src_message_buffer = GetInteger(src_thread.GetTlsAddress());
        src_user = false;
    }

    // Parse the headers.
    const MessageBuffer dst_msg(dst_msg_ptr, dst_buffer_size);
    const MessageBuffer src_msg(src_msg_ptr, src_buffer_size);
    const MessageBuffer::MessageHeader dst_header(dst_msg);
    const MessageBuffer::MessageHeader src_header(src_msg);
    const MessageBuffer::SpecialHeader dst_special_header(dst_msg, dst_header);
    const MessageBuffer::SpecialHeader src_special_header(src_msg, src_header);

    // Get the end of the source message.
    const size_t src_end_offset =
        MessageBuffer::GetRawDataIndex(src_header, src_special_header) + src_header.GetRawCount();

    // Ensure that the headers fit.
    R_UNLESS(MessageBuffer::GetMessageBufferSize(dst_header, dst_special_header) <= dst_buffer_size,
             ResultInvalidCombination);
    R_UNLESS(MessageBuffer::GetMessageBufferSize(src_header, src_special_header) <= src_buffer_size,
             ResultInvalidCombination);

    // Ensure the receive list offset is after the end of raw data.
    if (dst_header.GetReceiveListOffset()) {
        R_UNLESS(dst_header.GetReceiveListOffset() >=
                     MessageBuffer::GetRawDataIndex(dst_header, dst_special_header) +
                         dst_header.GetRawCount(),
                 ResultInvalidCombination);
    }

    // Ensure that the destination buffer is big enough to receive the source.
    R_UNLESS(dst_buffer_size >= src_end_offset * sizeof(u32), ResultMessageTooLarge);

    // Get the receive list.
    const s32 dst_recv_list_idx =
        MessageBuffer::GetReceiveListIndex(dst_header, dst_special_header);
    ReceiveList dst_recv_list(dst_msg_ptr, dst_message_buffer, dst_page_table, dst_header,
                              dst_special_header, dst_buffer_size, src_end_offset,
                              dst_recv_list_idx, !dst_user);

    // Ensure that the source special header isn't invalid.
    const bool src_has_special_header = src_header.GetHasSpecialHeader();
    if (src_has_special_header) {
        // Sending move handles from client -> server is not allowed.
        R_UNLESS(src_special_header.GetMoveHandleCount() == 0, ResultInvalidCombination);
    }

    // Prepare for further processing.
    int pointer_key = 0;
    int offset = dst_msg.Set(src_header);

    // Set up a guard to make sure that we end up in a clean state on error.
    ON_RESULT_FAILURE {
        // Cleanup mappings.
        CleanupMap(request, std::addressof(dst_process), std::addressof(src_page_table));

        // Cleanup special data.
        if (src_header.GetHasSpecialHeader()) {
            CleanupSpecialData(dst_process, dst_msg_ptr, dst_buffer_size);
        }

        // Cleanup the header if the receive list isn't broken.
        if (!recv_list_broken) {
            dst_msg.Set(dst_header);
            if (dst_header.GetHasSpecialHeader()) {
                dst_msg.Set(dst_special_header);
            }
        }
    };

    // Process any special data.
    if (src_header.GetHasSpecialHeader()) {
        // After we process, make sure we track whether the receive list is broken.
        SCOPE_EXIT {
            if (offset > dst_recv_list_idx) {
                recv_list_broken = true;
            }
        };

        // Process special data.
        R_TRY(ProcessMessageSpecialData<false>(offset, dst_process, src_process, src_thread,
                                               dst_msg, src_msg, src_special_header));
    }

    // Process any pointer buffers.
    for (auto i = 0; i < src_header.GetPointerCount(); ++i) {
        // After we process, make sure we track whether the receive list is broken.
        SCOPE_EXIT {
            if (offset > dst_recv_list_idx) {
                recv_list_broken = true;
            }
        };

        R_TRY(ProcessReceiveMessagePointerDescriptors(
            offset, pointer_key, dst_page_table, src_page_table, dst_msg, src_msg, dst_recv_list,
            dst_user && dst_header.GetReceiveListCount() ==
                            MessageBuffer::MessageHeader::ReceiveListCountType_ToMessageBuffer));
    }

    // Process any map alias buffers.
    for (auto i = 0; i < src_header.GetMapAliasCount(); ++i) {
        // After we process, make sure we track whether the receive list is broken.
        SCOPE_EXIT {
            if (offset > dst_recv_list_idx) {
                recv_list_broken = true;
            }
        };

        // We process in order send, recv, exch. Buffers after send (recv/exch) are ReadWrite.
        const KMemoryPermission perm = (i >= src_header.GetSendCount())
                                           ? KMemoryPermission::UserReadWrite
                                           : KMemoryPermission::UserRead;

        // Buffer is send if it is send or exch.
        const bool send = (i < src_header.GetSendCount()) ||
                          (i >= src_header.GetSendCount() + src_header.GetReceiveCount());

        R_TRY(ProcessReceiveMessageMapAliasDescriptors(offset, dst_page_table, src_page_table,
                                                       dst_msg, src_msg, request, perm, send));
    }

    // Process any raw data.
    if (const auto raw_count = src_header.GetRawCount(); raw_count != 0) {
        // After we process, make sure we track whether the receive list is broken.
        SCOPE_EXIT {
            if (offset + raw_count > dst_recv_list_idx) {
                recv_list_broken = true;
            }
        };

        // Get the offset and size.
        const size_t offset_words = offset * sizeof(u32);
        const size_t raw_size = raw_count * sizeof(u32);

        if (!dst_user && !src_user) {
            // Fast case is TLS -> TLS, do raw memcpy if we can.
            std::memcpy(dst_msg_ptr + offset, src_msg_ptr + offset, raw_size);
        } else if (dst_user) {
            // Determine how much fast size we can copy.
            const size_t max_fast_size = std::min<size_t>(offset_words + raw_size, PageSize);
            const size_t fast_size = max_fast_size - offset_words;

            // Determine source state; if user buffer, we require heap, and otherwise only linear
            // mapped (to enable tls use).
            const auto src_state =
                src_user ? KMemoryState::FlagReferenceCounted : KMemoryState::FlagLinearMapped;

            // Determine the source permission. User buffer should be unmapped + read, TLS should be
            // user readable.
            const KMemoryPermission src_perm = static_cast<KMemoryPermission>(
                src_user ? KMemoryPermission::NotMapped | KMemoryPermission::KernelRead
                         : KMemoryPermission::UserRead);

            // Perform the fast part of the copy.
            R_TRY(src_page_table.CopyMemoryFromLinearToKernel(
                dst_msg_ptr + offset, fast_size, src_message_buffer + offset_words, src_state,
                src_state, src_perm, KMemoryAttribute::Uncached, KMemoryAttribute::None));

            // If the fast part of the copy didn't get everything, perform the slow part of the
            // copy.
            if (fast_size < raw_size) {
                R_TRY(src_page_table.CopyMemoryFromHeapToHeap(
                    dst_page_table, dst_message_buffer + max_fast_size, raw_size - fast_size,
                    KMemoryState::FlagReferenceCounted, KMemoryState::FlagReferenceCounted,
                    KMemoryPermission::NotMapped | KMemoryPermission::KernelReadWrite,
                    KMemoryAttribute::Uncached | KMemoryAttribute::Locked, KMemoryAttribute::Locked,
                    src_message_buffer + max_fast_size, src_state, src_state, src_perm,
                    KMemoryAttribute::Uncached, KMemoryAttribute::None));
            }
        } else /* if (src_user) */ {
            // The source is a user buffer, so it should be unmapped + readable.
            constexpr KMemoryPermission SourcePermission = static_cast<KMemoryPermission>(
                KMemoryPermission::NotMapped | KMemoryPermission::KernelRead);

            // Copy the memory.
            R_TRY(src_page_table.CopyMemoryFromLinearToUser(
                dst_message_buffer + offset_words, raw_size, src_message_buffer + offset_words,
                KMemoryState::FlagReferenceCounted, KMemoryState::FlagReferenceCounted,
                SourcePermission, KMemoryAttribute::Uncached, KMemoryAttribute::None));
        }
    }

    // We succeeded!
    R_SUCCEED();
}

Result ProcessSendMessageReceiveMapping(KProcessPageTable& src_page_table,
                                        KProcessPageTable& dst_page_table,
                                        KProcessAddress client_address,
                                        KProcessAddress server_address, size_t size,
                                        KMemoryState src_state) {
    // If the size is zero, there's nothing to process.
    R_SUCCEED_IF(size == 0);

    // Get the memory state and attribute mask to test.
    KMemoryState test_state;
    KMemoryAttribute test_attr_mask;
    R_TRY(GetMapAliasTestStateAndAttributeMask(test_state, test_attr_mask, src_state));

    // Determine buffer extents.
    KProcessAddress aligned_dst_start = Common::AlignDown(GetInteger(client_address), PageSize);
    KProcessAddress aligned_dst_end = Common::AlignUp(GetInteger(client_address) + size, PageSize);
    KProcessAddress mapping_dst_start = Common::AlignUp(GetInteger(client_address), PageSize);
    KProcessAddress mapping_dst_end =
        Common::AlignDown(GetInteger(client_address) + size, PageSize);

    KProcessAddress mapping_src_end =
        Common::AlignDown(GetInteger(server_address) + size, PageSize);

    // If the start of the buffer is unaligned, handle that.
    if (aligned_dst_start != mapping_dst_start) {
        ASSERT(client_address < mapping_dst_start);
        const size_t copy_size = std::min<size_t>(size, mapping_dst_start - client_address);
        R_TRY(dst_page_table.CopyMemoryFromUserToLinear(
            client_address, copy_size, test_state, test_state, KMemoryPermission::UserReadWrite,
            test_attr_mask, KMemoryAttribute::None, server_address));
    }

    // If the end of the buffer is unaligned, handle that.
    if (mapping_dst_end < aligned_dst_end &&
        (aligned_dst_start == mapping_dst_start || aligned_dst_start < mapping_dst_end)) {
        const size_t copy_size = client_address + size - mapping_dst_end;
        R_TRY(dst_page_table.CopyMemoryFromUserToLinear(
            mapping_dst_end, copy_size, test_state, test_state, KMemoryPermission::UserReadWrite,
            test_attr_mask, KMemoryAttribute::None, mapping_src_end));
    }

    R_SUCCEED();
}

Result ProcessSendMessagePointerDescriptors(int& offset, int& pointer_key,
                                            KProcessPageTable& src_page_table,
                                            KProcessPageTable& dst_page_table,
                                            const MessageBuffer& dst_msg,
                                            const MessageBuffer& src_msg,
                                            const ReceiveList& dst_recv_list, bool dst_user) {
    // Get the offset at the start of processing.
    const int cur_offset = offset;

    // Get the pointer desc.
    MessageBuffer::PointerDescriptor src_desc(src_msg, cur_offset);
    offset += static_cast<int>(MessageBuffer::PointerDescriptor::GetDataSize() / sizeof(u32));

    // Extract address/size.
    const uint64_t src_pointer = src_desc.GetAddress();
    const size_t recv_size = src_desc.GetSize();
    uint64_t recv_pointer = 0;

    // Process the buffer, if it has a size.
    if (recv_size > 0) {
        // If using indexing, set index.
        if (dst_recv_list.IsIndex()) {
            pointer_key = src_desc.GetIndex();
        }

        // Get the buffer.
        dst_recv_list.GetBuffer(recv_pointer, recv_size, pointer_key);
        R_UNLESS(recv_pointer != 0, ResultOutOfResource);

        // Perform the pointer data copy.
        const bool dst_heap = dst_user && dst_recv_list.IsToMessageBuffer();
        const auto dst_state =
            dst_heap ? KMemoryState::FlagReferenceCounted : KMemoryState::FlagLinearMapped;
        const KMemoryPermission dst_perm =
            dst_heap ? KMemoryPermission::NotMapped | KMemoryPermission::KernelReadWrite
                     : KMemoryPermission::UserReadWrite;
        R_TRY(dst_page_table.CopyMemoryFromUserToLinear(
            recv_pointer, recv_size, dst_state, dst_state, dst_perm, KMemoryAttribute::Uncached,
            KMemoryAttribute::None, src_pointer));
    }

    // Set the output descriptor.
    dst_msg.Set(cur_offset, MessageBuffer::PointerDescriptor(reinterpret_cast<void*>(recv_pointer),
                                                             recv_size, src_desc.GetIndex()));

    R_SUCCEED();
}

Result SendMessage(KernelCore& kernel, uint64_t src_message_buffer, size_t src_buffer_size,
                   KPhysicalAddress src_message_paddr, KThread& dst_thread,
                   uint64_t dst_message_buffer, size_t dst_buffer_size, KServerSession* session,
                   KSessionRequest* request) {
    // Prepare variables for send.
    KThread& src_thread = GetCurrentThread(kernel);
    KProcess& dst_process = *(dst_thread.GetOwnerProcess());
    KProcess& src_process = *(src_thread.GetOwnerProcess());
    auto& dst_page_table = dst_process.GetPageTable();
    auto& src_page_table = src_process.GetPageTable();

    // NOTE: Session is used only for debugging, and so may go unused.
    (void)session;

    // Determine the message buffers.
    u32 *dst_msg_ptr, *src_msg_ptr;
    bool dst_user, src_user;

    if (dst_message_buffer) {
        // NOTE: Nintendo does not check the result of this GetPhysicalAddress call.
        dst_msg_ptr = dst_page_table.GetMemory().GetPointer<u32>(dst_message_buffer);
        dst_user = true;
    } else {
        dst_msg_ptr = dst_page_table.GetMemory().GetPointer<u32>(dst_thread.GetTlsAddress());
        dst_buffer_size = MessageBufferSize;
        dst_message_buffer = GetInteger(dst_thread.GetTlsAddress());
        dst_user = false;
    }

    if (src_message_buffer) {
        src_msg_ptr = src_page_table.GetMemory().GetPointer<u32>(src_message_buffer);
        src_user = true;
    } else {
        src_msg_ptr = src_page_table.GetMemory().GetPointer<u32>(src_thread.GetTlsAddress());
        src_buffer_size = MessageBufferSize;
        src_message_buffer = GetInteger(src_thread.GetTlsAddress());
        src_user = false;
    }

    // Parse the headers.
    const MessageBuffer dst_msg(dst_msg_ptr, dst_buffer_size);
    const MessageBuffer src_msg(src_msg_ptr, src_buffer_size);
    const MessageBuffer::MessageHeader dst_header(dst_msg);
    const MessageBuffer::MessageHeader src_header(src_msg);
    const MessageBuffer::SpecialHeader dst_special_header(dst_msg, dst_header);
    const MessageBuffer::SpecialHeader src_special_header(src_msg, src_header);

    // Get the end of the source message.
    const size_t src_end_offset =
        MessageBuffer::GetRawDataIndex(src_header, src_special_header) + src_header.GetRawCount();

    // Declare variables for processing.
    int offset = 0;
    int pointer_key = 0;
    bool processed_special_data = false;

    // Send the message.
    {
        // Make sure that we end up in a clean state on error.
        ON_RESULT_FAILURE {
            // Cleanup special data.
            if (processed_special_data) {
                if (src_header.GetHasSpecialHeader()) {
                    CleanupSpecialData(dst_process, dst_msg_ptr, dst_buffer_size);
                }
            } else {
                CleanupServerHandles(kernel, src_user ? src_message_buffer : 0, src_buffer_size,
                                     src_message_paddr);
            }

            // Cleanup mappings.
            CleanupMap(request, std::addressof(src_process), std::addressof(dst_page_table));
        };

        // Ensure that the headers fit.
        R_UNLESS(MessageBuffer::GetMessageBufferSize(src_header, src_special_header) <=
                     src_buffer_size,
                 ResultInvalidCombination);
        R_UNLESS(MessageBuffer::GetMessageBufferSize(dst_header, dst_special_header) <=
                     dst_buffer_size,
                 ResultInvalidCombination);

        // Ensure the receive list offset is after the end of raw data.
        if (dst_header.GetReceiveListOffset()) {
            R_UNLESS(dst_header.GetReceiveListOffset() >=
                         MessageBuffer::GetRawDataIndex(dst_header, dst_special_header) +
                             dst_header.GetRawCount(),
                     ResultInvalidCombination);
        }

        // Ensure that the destination buffer is big enough to receive the source.
        R_UNLESS(dst_buffer_size >= src_end_offset * sizeof(u32), ResultMessageTooLarge);

        // Replies must have no buffers.
        R_UNLESS(src_header.GetSendCount() == 0, ResultInvalidCombination);
        R_UNLESS(src_header.GetReceiveCount() == 0, ResultInvalidCombination);
        R_UNLESS(src_header.GetExchangeCount() == 0, ResultInvalidCombination);

        // Get the receive list.
        const s32 dst_recv_list_idx =
            MessageBuffer::GetReceiveListIndex(dst_header, dst_special_header);
        ReceiveList dst_recv_list(dst_msg_ptr, dst_message_buffer, dst_page_table, dst_header,
                                  dst_special_header, dst_buffer_size, src_end_offset,
                                  dst_recv_list_idx, !dst_user);

        // Handle any receive buffers.
        for (size_t i = 0; i < request->GetReceiveCount(); ++i) {
            R_TRY(ProcessSendMessageReceiveMapping(
                src_page_table, dst_page_table, request->GetReceiveClientAddress(i),
                request->GetReceiveServerAddress(i), request->GetReceiveSize(i),
                request->GetReceiveMemoryState(i)));
        }

        // Handle any exchange buffers.
        for (size_t i = 0; i < request->GetExchangeCount(); ++i) {
            R_TRY(ProcessSendMessageReceiveMapping(
                src_page_table, dst_page_table, request->GetExchangeClientAddress(i),
                request->GetExchangeServerAddress(i), request->GetExchangeSize(i),
                request->GetExchangeMemoryState(i)));
        }

        // Set the header.
        offset = dst_msg.Set(src_header);

        // Process any special data.
        ASSERT(GetCurrentThreadPointer(kernel) == std::addressof(src_thread));
        processed_special_data = true;
        if (src_header.GetHasSpecialHeader()) {
            R_TRY(ProcessMessageSpecialData<true>(offset, dst_process, src_process, src_thread,
                                                  dst_msg, src_msg, src_special_header));
        }

        // Process any pointer buffers.
        for (auto i = 0; i < src_header.GetPointerCount(); ++i) {
            R_TRY(ProcessSendMessagePointerDescriptors(
                offset, pointer_key, src_page_table, dst_page_table, dst_msg, src_msg,
                dst_recv_list,
                dst_user &&
                    dst_header.GetReceiveListCount() ==
                        MessageBuffer::MessageHeader::ReceiveListCountType_ToMessageBuffer));
        }

        // Clear any map alias buffers.
        for (auto i = 0; i < src_header.GetMapAliasCount(); ++i) {
            offset = dst_msg.Set(offset, MessageBuffer::MapAliasDescriptor());
        }

        // Process any raw data.
        if (const auto raw_count = src_header.GetRawCount(); raw_count != 0) {
            // Get the offset and size.
            const size_t offset_words = offset * sizeof(u32);
            const size_t raw_size = raw_count * sizeof(u32);

            if (!dst_user && !src_user) {
                // Fast case is TLS -> TLS, do raw memcpy if we can.
                std::memcpy(dst_msg_ptr + offset, src_msg_ptr + offset, raw_size);
            } else if (src_user) {
                // Determine how much fast size we can copy.
                const size_t max_fast_size = std::min<size_t>(offset_words + raw_size, PageSize);
                const size_t fast_size = max_fast_size - offset_words;

                // Determine dst state; if user buffer, we require heap, and otherwise only linear
                // mapped (to enable tls use).
                const auto dst_state =
                    dst_user ? KMemoryState::FlagReferenceCounted : KMemoryState::FlagLinearMapped;

                // Determine the dst permission. User buffer should be unmapped + read, TLS should
                // be user readable.
                const KMemoryPermission dst_perm =
                    dst_user ? KMemoryPermission::NotMapped | KMemoryPermission::KernelReadWrite
                             : KMemoryPermission::UserReadWrite;

                // Perform the fast part of the copy.
                R_TRY(dst_page_table.CopyMemoryFromKernelToLinear(
                    dst_message_buffer + offset_words, fast_size, dst_state, dst_state, dst_perm,
                    KMemoryAttribute::Uncached, KMemoryAttribute::None, src_msg_ptr + offset));

                // If the fast part of the copy didn't get everything, perform the slow part of the
                // copy.
                if (fast_size < raw_size) {
                    R_TRY(dst_page_table.CopyMemoryFromHeapToHeap(
                        dst_page_table, dst_message_buffer + max_fast_size, raw_size - fast_size,
                        dst_state, dst_state, dst_perm, KMemoryAttribute::Uncached,
                        KMemoryAttribute::None, src_message_buffer + max_fast_size,
                        KMemoryState::FlagReferenceCounted, KMemoryState::FlagReferenceCounted,
                        KMemoryPermission::NotMapped | KMemoryPermission::KernelRead,
                        KMemoryAttribute::Uncached | KMemoryAttribute::Locked,
                        KMemoryAttribute::Locked));
                }
            } else /* if (dst_user) */ {
                // The destination is a user buffer, so it should be unmapped + readable.
                constexpr KMemoryPermission DestinationPermission =
                    KMemoryPermission::NotMapped | KMemoryPermission::KernelReadWrite;

                // Copy the memory.
                R_TRY(dst_page_table.CopyMemoryFromUserToLinear(
                    dst_message_buffer + offset_words, raw_size, KMemoryState::FlagReferenceCounted,
                    KMemoryState::FlagReferenceCounted, DestinationPermission,
                    KMemoryAttribute::Uncached, KMemoryAttribute::None,
                    src_message_buffer + offset_words));
            }
        }
    }

    // Perform (and validate) any remaining cleanup.
    R_RETURN(CleanupMap(request, std::addressof(src_process), std::addressof(dst_page_table)));
}

void ReplyAsyncError(KProcess* to_process, uint64_t to_msg_buf, size_t to_msg_buf_size,
                     Result result) {
    // Convert the address to a linear pointer.
    u32* to_msg = to_process->GetMemory().GetPointer<u32>(to_msg_buf);

    // Set the error.
    MessageBuffer msg(to_msg, to_msg_buf_size);
    msg.SetAsyncResult(result);
}

} // namespace

KServerSession::KServerSession(KernelCore& kernel)
    : KSynchronizationObject{kernel}, m_lock{m_kernel} {}

KServerSession::~KServerSession() = default;

void KServerSession::Destroy() {
    m_parent->OnServerClosed();

    this->CleanupRequests();

    m_parent->Close();
}

Result KServerSession::ReceiveRequest(uintptr_t server_message, uintptr_t server_buffer_size,
                                      KPhysicalAddress server_message_paddr,
                                      std::shared_ptr<Service::HLERequestContext>* out_context,
                                      std::weak_ptr<Service::SessionRequestManager> manager) {
    // Lock the session.
    KScopedLightLock lk{m_lock};

    // Get the request and client thread.
    KSessionRequest* request;
    KThread* client_thread;

    {
        KScopedSchedulerLock sl{m_kernel};

        // Ensure that we can service the request.
        R_UNLESS(!m_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 = std::addressof(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.
    uint64_t client_message = request->GetAddress();
    size_t client_buffer_size = request->GetSize();
    bool recv_list_broken = false;

    // Receive the message.
    Result result = ResultSuccess;

    if (out_context != nullptr) {
        // HLE request.
        if (!client_message) {
            client_message = GetInteger(client_thread->GetTlsAddress());
        }
        Core::Memory::Memory& memory{client_thread->GetOwnerProcess()->GetMemory()};
        u32* cmd_buf{reinterpret_cast<u32*>(memory.GetPointer(client_message))};
        *out_context =
            std::make_shared<Service::HLERequestContext>(m_kernel, memory, this, client_thread);
        (*out_context)->SetSessionRequestManager(manager);
        (*out_context)->PopulateFromIncomingCommandBuffer(cmd_buf);
        // We succeeded.
        R_SUCCEED();
    } else {
        result = ReceiveMessage(m_kernel, recv_list_broken, server_message, server_buffer_size,
                                server_message_paddr, *client_thread, client_message,
                                client_buffer_size, this, request);
    }

    // Handle cleanup on receive failure.
    if (R_FAILED(result)) {
        // Cache the result to return it to the client.
        const Result result_for_client = result;

        // Clear the current request.
        {
            KScopedSchedulerLock sl(m_kernel);
            ASSERT(m_current_request == request);
            m_current_request = nullptr;
            if (!m_request_list.empty()) {
                this->NotifyAvailable();
            }
        }

        // Reply to the client.
        {
            // After we reply, close our reference to the request.
            SCOPE_EXIT {
                request->Close();
            };

            // Get the event to check whether the request is async.
            if (KEvent* event = request->GetEvent(); event != nullptr) {
                // The client sent an async request.
                KProcess* client = client_thread->GetOwnerProcess();
                auto& client_pt = client->GetPageTable();

                // Send the async result.
                if (R_FAILED(result_for_client)) {
                    ReplyAsyncError(client, client_message, client_buffer_size, result_for_client);
                }

                // Unlock the client buffer.
                // NOTE: Nintendo does not check the result of this.
                client_pt.UnlockForIpcUserBuffer(client_message, client_buffer_size);

                // Signal the event.
                event->Signal();
            } else {
                // End the client thread's wait.
                KScopedSchedulerLock sl(m_kernel);

                if (!client_thread->IsTerminationRequested()) {
                    client_thread->EndWait(result_for_client);
                }
            }
        }

        // Set the server result.
        if (recv_list_broken) {
            result = ResultReceiveListBroken;
        } else {
            result = ResultNotFound;
        }
    }

    R_RETURN(result);
}

Result KServerSession::SendReply(uintptr_t server_message, uintptr_t server_buffer_size,
                                 KPhysicalAddress server_message_paddr, bool is_hle) {
    // Lock the session.
    KScopedLightLock lk{m_lock};

    // Get the request.
    KSessionRequest* request;
    {
        KScopedSchedulerLock sl{m_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 uint64_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 || m_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 {
            result = SendMessage(m_kernel, server_message, server_buffer_size, server_message_paddr,
                                 *client_thread, client_message, client_buffer_size, this, request);
        }
    } else if (!is_hle) {
        // Otherwise, we'll need to do some cleanup.
        KProcess* server_process = request->GetServerProcess();
        KProcess* client_process =
            (client_thread != nullptr) ? client_thread->GetOwnerProcess() : nullptr;
        KProcessPageTable* client_page_table =
            (client_process != nullptr) ? std::addressof(client_process->GetPageTable()) : nullptr;

        // Cleanup server handles.
        result = CleanupServerHandles(m_kernel, server_message, server_buffer_size,
                                      server_message_paddr);

        // Cleanup mappings.
        Result cleanup_map_result = CleanupMap(request, server_process, client_page_table);

        // If we successfully cleaned up handles, use the map cleanup result as our result.
        if (R_SUCCEEDED(result)) {
            result = cleanup_map_result;
        }
    }

    // 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();
            KProcessPageTable* client_page_table = std::addressof(client_process->GetPageTable());

            // 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{m_kernel};

            if (!client_thread->IsTerminationRequested()) {
                client_thread->EndWait(client_result);
            }
        }
    }

    R_RETURN(result);
}

Result KServerSession::OnRequest(KSessionRequest* request) {
    // Create the wait queue.
    ThreadQueueImplForKServerSessionRequest wait_queue{m_kernel};

    {
        // Lock the scheduler.
        KScopedSchedulerLock sl{m_kernel};

        // Ensure that we can handle new requests.
        R_UNLESS(!m_parent->IsServerClosed(), ResultSessionClosed);

        // Check that we're not terminating.
        R_UNLESS(!GetCurrentThread(m_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(m_kernel).SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::IPC);
        GetCurrentThread(m_kernel).BeginWait(std::addressof(wait_queue));
    }

    return GetCurrentThread(m_kernel).GetWaitResult();
}

bool KServerSession::IsSignaled() const {
    ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel));

    // If the client is closed, we're always signaled.
    if (m_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;
}

void KServerSession::CleanupRequests() {
    KScopedLightLock lk(m_lock);

    // Clean up any pending requests.
    while (true) {
        // Get the next request.
        KSessionRequest* request = nullptr;
        {
            KScopedSchedulerLock sl{m_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 = std::addressof(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 uint64_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) ? std::addressof(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{m_kernel};

                if (!client_thread->IsTerminationRequested()) {
                    client_thread->EndWait(ResultSessionClosed);
                }
            }
        }
    }
}

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{m_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);
}

} // namespace Kernel


code:
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <list>
#include <memory>
#include <string>
#include <utility>

#include "common/intrusive_list.h"

#include "core/hle/kernel/k_light_lock.h"
#include "core/hle/kernel/k_session_request.h"
#include "core/hle/kernel/k_synchronization_object.h"
#include "core/hle/result.h"

namespace Service {
class HLERequestContext;
class SessionRequestManager;
} // namespace Service

namespace Kernel {

class KernelCore;
class KSession;
class KThread;

class KServerSession final : public KSynchronizationObject,
                             public Common::IntrusiveListBaseNode<KServerSession> {
    KERNEL_AUTOOBJECT_TRAITS(KServerSession, KSynchronizationObject);

    friend class ServiceThread;

public:
    explicit KServerSession(KernelCore& kernel);
    ~KServerSession() override;

    void Destroy() override;

    void Initialize(KSession* p) {
        m_parent = p;
    }

    const KSession* GetParent() const {
        return m_parent;
    }

    bool IsSignaled() const override;
    void OnClientClosed();

    Result OnRequest(KSessionRequest* request);
    Result SendReply(uintptr_t server_message, uintptr_t server_buffer_size,
                     KPhysicalAddress server_message_paddr, bool is_hle = false);
    Result ReceiveRequest(uintptr_t server_message, uintptr_t server_buffer_size,
                          KPhysicalAddress server_message_paddr,
                          std::shared_ptr<Service::HLERequestContext>* out_context = nullptr,
                          std::weak_ptr<Service::SessionRequestManager> manager = {});

    Result SendReplyHLE() {
        R_RETURN(this->SendReply(0, 0, 0, true));
    }

    Result ReceiveRequestHLE(std::shared_ptr<Service::HLERequestContext>* out_context,
                             std::weak_ptr<Service::SessionRequestManager> manager) {
        R_RETURN(this->ReceiveRequest(0, 0, 0, out_context, manager));
    }

private:
    /// Frees up waiting client sessions when this server session is about to die
    void CleanupRequests();

    /// KSession that owns this KServerSession
    KSession* m_parent{};

    /// List of threads which are pending a reply.
    using RequestList = Common::IntrusiveListBaseTraits<KSessionRequest>::ListType;
    RequestList m_request_list{};
    KSessionRequest* m_current_request{};

    KLightLock m_lock;
};

} // namespace Kernel

repo_name: litucks/torzu

license: mit


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "core/hle/kernel/k_client_port.h"
#include "core/hle/kernel/k_client_session.h"
#include "core/hle/kernel/k_scoped_resource_reservation.h"
#include "core/hle/kernel/k_server_session.h"
#include "core/hle/kernel/k_session.h"

namespace Kernel {

KSession::KSession(KernelCore& kernel)
    : KAutoObjectWithSlabHeapAndContainer{kernel}, m_server{kernel}, m_client{kernel} {}
KSession::~KSession() = default;

void KSession::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.
    this->SetState(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 KSession::Finalize() {
    if (m_port != nullptr) {
        m_port->OnSessionFinalized();
        m_port->Close();
    }
}

void KSession::OnServerClosed() {
    if (this->GetState() == State::Normal) {
        this->SetState(State::ServerClosed);
        m_client.OnServerClosed();
    }
}

void KSession::OnClientClosed() {
    if (this->GetState() == State::Normal) {
        SetState(State::ClientClosed);
        m_server.OnClientClosed();
    }
}

void KSession::PostDestroy(uintptr_t arg) {
    // Release the session count resource the owner process holds.
    KProcess* owner = reinterpret_cast<KProcess*>(arg);
    owner->GetResourceLimit()->Release(LimitableResource::SessionCountMax, 1);
    owner->Close();
}

} // namespace Kernel


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <atomic>
#include <string>

#include "core/hle/kernel/k_client_session.h"
#include "core/hle/kernel/k_server_session.h"
#include "core/hle/kernel/slab_helpers.h"

namespace Kernel {

class SessionRequestManager;

class KSession final : public KAutoObjectWithSlabHeapAndContainer<KSession, KAutoObjectWithList> {
    KERNEL_AUTOOBJECT_TRAITS(KSession, KAutoObject);

public:
    explicit KSession(KernelCore& kernel);
    ~KSession() override;

    void Initialize(KClientPort* 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 this->GetState() != State::Normal;
    }

    bool IsClientClosed() const {
        return this->GetState() != State::Normal;
    }

    Result OnRequest(KSessionRequest* request) {
        R_RETURN(m_server.OnRequest(request));
    }

    KClientSession& GetClientSession() {
        return m_client;
    }

    KServerSession& GetServerSession() {
        return m_server;
    }

    const KClientSession& GetClientSession() const {
        return m_client;
    }

    const KServerSession& GetServerSession() const {
        return m_server;
    }

    const KClientPort* GetParent() const {
        return m_port;
    }

private:
    enum class State : u8 {
        Invalid = 0,
        Normal = 1,
        ClientClosed = 2,
        ServerClosed = 3,
    };

    void SetState(State state) {
        m_atomic_state = static_cast<u8>(state);
    }

    State GetState() const {
        return static_cast<State>(m_atomic_state.load());
    }

    KServerSession m_server;
    KClientSession m_client;
    KClientPort* m_port{};
    uintptr_t m_name{};
    KProcess* m_process{};
    std::atomic<u8> m_atomic_state{static_cast<u8>(State::Invalid)};
    bool m_initialized{};
};

} // namespace Kernel


code:
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "core/hle/kernel/k_page_buffer.h"
#include "core/hle/kernel/k_session_request.h"

namespace Kernel {

Result KSessionRequest::SessionMappings::PushMap(KProcessAddress client, KProcessAddress server,
                                                 size_t size, KMemoryState state, size_t index) {
    // At most 15 buffers of each type (4-bit descriptor counts).
    ASSERT(index < ((1ul << 4) - 1) * 3);

    // Get the mapping.
    Mapping* mapping;
    if (index < NumStaticMappings) {
        mapping = std::addressof(m_static_mappings[index]);
    } else {
        // Allocate a page for the extra mappings.
        if (m_mappings == nullptr) {
            KPageBuffer* page_buffer = KPageBuffer::Allocate(m_kernel);
            R_UNLESS(page_buffer != nullptr, ResultOutOfMemory);

            m_mappings = reinterpret_cast<Mapping*>(page_buffer);
        }

        mapping = std::addressof(m_mappings[index - NumStaticMappings]);
    }

    // Set the mapping.
    mapping->Set(client, server, size, state);

    R_SUCCEED();
}

Result KSessionRequest::SessionMappings::PushSend(KProcessAddress client, KProcessAddress server,
                                                  size_t size, KMemoryState state) {
    ASSERT(m_num_recv == 0);
    ASSERT(m_num_exch == 0);
    R_RETURN(this->PushMap(client, server, size, state, m_num_send++));
}

Result KSessionRequest::SessionMappings::PushReceive(KProcessAddress client, KProcessAddress server,
                                                     size_t size, KMemoryState state) {
    ASSERT(m_num_exch == 0);
    R_RETURN(this->PushMap(client, server, size, state, m_num_send + m_num_recv++));
}

Result KSessionRequest::SessionMappings::PushExchange(KProcessAddress client,
                                                      KProcessAddress server, size_t size,
                                                      KMemoryState state) {
    R_RETURN(this->PushMap(client, server, size, state, m_num_send + m_num_recv + m_num_exch++));
}

void KSessionRequest::SessionMappings::Finalize() {
    if (m_mappings) {
        KPageBuffer::Free(m_kernel, reinterpret_cast<KPageBuffer*>(m_mappings));
        m_mappings = nullptr;
    }
}

} // namespace Kernel


code:
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <array>

#include "common/intrusive_list.h"

#include "core/hle/kernel/k_auto_object.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/kernel/k_memory_block.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/slab_helpers.h"

namespace Kernel {

class KSessionRequest final : public KSlabAllocated<KSessionRequest>,
                              public KAutoObject,
                              public Common::IntrusiveListBaseNode<KSessionRequest> {
    KERNEL_AUTOOBJECT_TRAITS(KSessionRequest, KAutoObject);

public:
    class SessionMappings {
    private:
        static constexpr size_t NumStaticMappings = 8;

        class Mapping {
        public:
            constexpr void Set(KProcessAddress c, KProcessAddress s, size_t sz, KMemoryState st) {
                m_client_address = c;
                m_server_address = s;
                m_size = sz;
                m_state = st;
            }

            constexpr KProcessAddress GetClientAddress() const {
                return m_client_address;
            }
            constexpr KProcessAddress GetServerAddress() const {
                return m_server_address;
            }
            constexpr size_t GetSize() const {
                return m_size;
            }
            constexpr KMemoryState GetMemoryState() const {
                return m_state;
            }

        private:
            KProcessAddress m_client_address{};
            KProcessAddress m_server_address{};
            size_t m_size{};
            KMemoryState m_state{};
        };

    public:
        explicit SessionMappings(KernelCore& kernel) : m_kernel(kernel) {}

        void Initialize() {}
        void Finalize();

        size_t GetSendCount() const {
            return m_num_send;
        }
        size_t GetReceiveCount() const {
            return m_num_recv;
        }
        size_t GetExchangeCount() const {
            return m_num_exch;
        }

        Result PushSend(KProcessAddress client, KProcessAddress server, size_t size,
                        KMemoryState state);
        Result PushReceive(KProcessAddress client, KProcessAddress server, size_t size,
                           KMemoryState state);
        Result PushExchange(KProcessAddress client, KProcessAddress server, size_t size,
                            KMemoryState state);

        KProcessAddress GetSendClientAddress(size_t i) const {
            return GetSendMapping(i).GetClientAddress();
        }
        KProcessAddress GetSendServerAddress(size_t i) const {
            return GetSendMapping(i).GetServerAddress();
        }
        size_t GetSendSize(size_t i) const {
            return GetSendMapping(i).GetSize();
        }
        KMemoryState GetSendMemoryState(size_t i) const {
            return GetSendMapping(i).GetMemoryState();
        }

        KProcessAddress GetReceiveClientAddress(size_t i) const {
            return GetReceiveMapping(i).GetClientAddress();
        }
        KProcessAddress GetReceiveServerAddress(size_t i) const {
            return GetReceiveMapping(i).GetServerAddress();
        }
        size_t GetReceiveSize(size_t i) const {
            return GetReceiveMapping(i).GetSize();
        }
        KMemoryState GetReceiveMemoryState(size_t i) const {
            return GetReceiveMapping(i).GetMemoryState();
        }

        KProcessAddress GetExchangeClientAddress(size_t i) const {
            return GetExchangeMapping(i).GetClientAddress();
        }
        KProcessAddress GetExchangeServerAddress(size_t i) const {
            return GetExchangeMapping(i).GetServerAddress();
        }
        size_t GetExchangeSize(size_t i) const {
            return GetExchangeMapping(i).GetSize();
        }
        KMemoryState GetExchangeMemoryState(size_t i) const {
            return GetExchangeMapping(i).GetMemoryState();
        }

    private:
        Result PushMap(KProcessAddress client, KProcessAddress server, size_t size,
                       KMemoryState state, size_t index);

        const Mapping& GetSendMapping(size_t i) const {
            ASSERT(i < m_num_send);

            const size_t index = i;
            if (index < NumStaticMappings) {
                return m_static_mappings[index];
            } else {
                return m_mappings[index - NumStaticMappings];
            }
        }

        const Mapping& GetReceiveMapping(size_t i) const {
            ASSERT(i < m_num_recv);

            const size_t index = m_num_send + i;
            if (index < NumStaticMappings) {
                return m_static_mappings[index];
            } else {
                return m_mappings[index - NumStaticMappings];
            }
        }

        const Mapping& GetExchangeMapping(size_t i) const {
            ASSERT(i < m_num_exch);

            const size_t index = m_num_send + m_num_recv + i;
            if (index < NumStaticMappings) {
                return m_static_mappings[index];
            } else {
                return m_mappings[index - NumStaticMappings];
            }
        }

    private:
        KernelCore& m_kernel;
        std::array<Mapping, NumStaticMappings> m_static_mappings{};
        Mapping* m_mappings{};
        u8 m_num_send{};
        u8 m_num_recv{};
        u8 m_num_exch{};
    };

public:
    explicit KSessionRequest(KernelCore& kernel) : KAutoObject(kernel), m_mappings(kernel) {}

    static KSessionRequest* Create(KernelCore& kernel) {
        KSessionRequest* req = KSessionRequest::Allocate(kernel);
        if (req != nullptr) [[likely]] {
            KAutoObject::Create(req);
        }
        return req;
    }

    void Destroy() override {
        this->Finalize();
        KSessionRequest::Free(m_kernel, this);
    }

    void Initialize(KEvent* event, uintptr_t address, size_t size) {
        m_mappings.Initialize();

        m_thread = GetCurrentThreadPointer(m_kernel);
        m_event = event;
        m_address = address;
        m_size = size;

        m_thread->Open();
        if (m_event != nullptr) {
            m_event->Open();
        }
    }

    static void PostDestroy(uintptr_t arg) {}

    KThread* GetThread() const {
        return m_thread;
    }
    KEvent* GetEvent() const {
        return m_event;
    }
    uintptr_t GetAddress() const {
        return m_address;
    }
    size_t GetSize() const {
        return m_size;
    }
    KProcess* GetServerProcess() const {
        return m_server;
    }

    void SetServerProcess(KProcess* process) {
        m_server = process;
        m_server->Open();
    }

    void ClearThread() {
        m_thread = nullptr;
    }
    void ClearEvent() {
        m_event = nullptr;
    }

    size_t GetSendCount() const {
        return m_mappings.GetSendCount();
    }
    size_t GetReceiveCount() const {
        return m_mappings.GetReceiveCount();
    }
    size_t GetExchangeCount() const {
        return m_mappings.GetExchangeCount();
    }

    Result PushSend(KProcessAddress client, KProcessAddress server, size_t size,
                    KMemoryState state) {
        return m_mappings.PushSend(client, server, size, state);
    }

    Result PushReceive(KProcessAddress client, KProcessAddress server, size_t size,
                       KMemoryState state) {
        return m_mappings.PushReceive(client, server, size, state);
    }

    Result PushExchange(KProcessAddress client, KProcessAddress server, size_t size,
                        KMemoryState state) {
        return m_mappings.PushExchange(client, server, size, state);
    }

    KProcessAddress GetSendClientAddress(size_t i) const {
        return m_mappings.GetSendClientAddress(i);
    }
    KProcessAddress GetSendServerAddress(size_t i) const {
        return m_mappings.GetSendServerAddress(i);
    }
    size_t GetSendSize(size_t i) const {
        return m_mappings.GetSendSize(i);
    }
    KMemoryState GetSendMemoryState(size_t i) const {
        return m_mappings.GetSendMemoryState(i);
    }

    KProcessAddress GetReceiveClientAddress(size_t i) const {
        return m_mappings.GetReceiveClientAddress(i);
    }
    KProcessAddress GetReceiveServerAddress(size_t i) const {
        return m_mappings.GetReceiveServerAddress(i);
    }
    size_t GetReceiveSize(size_t i) const {
        return m_mappings.GetReceiveSize(i);
    }
    KMemoryState GetReceiveMemoryState(size_t i) const {
        return m_mappings.GetReceiveMemoryState(i);
    }

    KProcessAddress GetExchangeClientAddress(size_t i) const {
        return m_mappings.GetExchangeClientAddress(i);
    }
    KProcessAddress GetExchangeServerAddress(size_t i) const {
        return m_mappings.GetExchangeServerAddress(i);
    }
    size_t GetExchangeSize(size_t i) const {
        return m_mappings.GetExchangeSize(i);
    }
    KMemoryState GetExchangeMemoryState(size_t i) const {
        return m_mappings.GetExchangeMemoryState(i);
    }

private:
    // NOTE: This is public and virtual in Nintendo's kernel.
    void Finalize() override {
        m_mappings.Finalize();

        if (m_thread) {
            m_thread->Close();
        }
        if (m_event) {
            m_event->Close();
        }
        if (m_server) {
            m_server->Close();
        }
    }

private:
    SessionMappings m_mappings;
    KThread* m_thread{};
    KProcess* m_server{};
    KEvent* m_event{};
    uintptr_t m_address{};
    size_t m_size{};
};

} // namespace Kernel


code:
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "common/assert.h"
#include "core/core.h"
#include "core/hle/kernel/k_page_table.h"
#include "core/hle/kernel/k_scoped_resource_reservation.h"
#include "core/hle/kernel/k_shared_memory.h"
#include "core/hle/kernel/k_system_resource.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/svc_results.h"

namespace Kernel {

KSharedMemory::KSharedMemory(KernelCore& kernel) : KAutoObjectWithSlabHeapAndContainer{kernel} {}
KSharedMemory::~KSharedMemory() = default;

Result KSharedMemory::Initialize(Core::DeviceMemory& device_memory, KProcess* owner_process,
                                 Svc::MemoryPermission owner_permission,
                                 Svc::MemoryPermission user_permission, std::size_t size) {
    // Set members.
    m_owner_process = owner_process;
    m_device_memory = std::addressof(device_memory);
    m_owner_permission = owner_permission;
    m_user_permission = user_permission;
    m_size = Common::AlignUp(size, PageSize);

    const size_t num_pages = Common::DivideUp(size, PageSize);

    // Get the resource limit.
    KResourceLimit* reslimit = m_kernel.GetSystemResourceLimit();

    // Reserve memory for ourselves.
    KScopedResourceReservation memory_reservation(reslimit, LimitableResource::PhysicalMemoryMax,
                                                  size);
    R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached);

    // Allocate the memory.

    //! HACK: Open continuous mapping from sysmodule pool.
    auto option = KMemoryManager::EncodeOption(KMemoryManager::Pool::Secure,
                                               KMemoryManager::Direction::FromBack);
    m_physical_address = m_kernel.MemoryManager().AllocateAndOpenContinuous(num_pages, 1, option);
    R_UNLESS(m_physical_address != 0, ResultOutOfMemory);

    //! Insert the result into our page group.
    m_page_group.emplace(m_kernel,
                         std::addressof(m_kernel.GetSystemSystemResource().GetBlockInfoManager()));
    m_page_group->AddBlock(m_physical_address, num_pages);

    // Commit our reservation.
    memory_reservation.Commit();

    // Set our resource limit.
    m_resource_limit = reslimit;
    m_resource_limit->Open();

    // Mark initialized.
    m_is_initialized = true;

    // Clear all pages in the memory.
    for (const auto& block : *m_page_group) {
        std::memset(m_device_memory->GetPointer<void>(block.GetAddress()), 0, block.GetSize());
    }

    R_SUCCEED();
}

void KSharedMemory::Finalize() {
    // Close and finalize the page group.
    m_page_group->Close();
    m_page_group->Finalize();

    // Release the memory reservation.
    m_resource_limit->Release(LimitableResource::PhysicalMemoryMax, m_size);
    m_resource_limit->Close();
}

Result KSharedMemory::Map(KProcess& target_process, KProcessAddress address, std::size_t map_size,
                          Svc::MemoryPermission map_perm) {
    // Validate the size.
    R_UNLESS(m_size == map_size, ResultInvalidSize);

    // Validate the permission.
    const Svc::MemoryPermission test_perm =
        std::addressof(target_process) == m_owner_process ? m_owner_permission : m_user_permission;
    if (test_perm == Svc::MemoryPermission::DontCare) {
        ASSERT(map_perm == Svc::MemoryPermission::Read || map_perm == Svc::MemoryPermission::Write);
    } else {
        R_UNLESS(map_perm == test_perm, ResultInvalidNewMemoryPermission);
    }

    R_RETURN(target_process.GetPageTable().MapPageGroup(
        address, *m_page_group, KMemoryState::Shared, ConvertToKMemoryPermission(map_perm)));
}

Result KSharedMemory::Unmap(KProcess& target_process, KProcessAddress address,
                            std::size_t unmap_size) {
    // Validate the size.
    R_UNLESS(m_size == unmap_size, ResultInvalidSize);

    R_RETURN(
        target_process.GetPageTable().UnmapPageGroup(address, *m_page_group, KMemoryState::Shared));
}

} // namespace Kernel


code:
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <optional>
#include <string>

#include "core/device_memory.h"
#include "core/hle/kernel/k_memory_block.h"
#include "core/hle/kernel/k_page_group.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_typed_address.h"
#include "core/hle/kernel/slab_helpers.h"
#include "core/hle/result.h"

namespace Kernel {

class KernelCore;

class KSharedMemory final
    : public KAutoObjectWithSlabHeapAndContainer<KSharedMemory, KAutoObjectWithList> {
    KERNEL_AUTOOBJECT_TRAITS(KSharedMemory, KAutoObject);

public:
    explicit KSharedMemory(KernelCore& kernel);
    ~KSharedMemory() override;

    Result Initialize(Core::DeviceMemory& device_memory_, KProcess* owner_process_,
                      Svc::MemoryPermission owner_permission_,
                      Svc::MemoryPermission user_permission_, std::size_t size_);

    /**
     * Maps a shared memory block to an address in the target process' address space
     * @param target_process Process on which to map the memory block
     * @param address Address in system memory to map shared memory block to
     * @param map_size Size of the shared memory block to map
     * @param permissions Memory block map permissions (specified by SVC field)
     */
    Result Map(KProcess& target_process, KProcessAddress address, std::size_t map_size,
               Svc::MemoryPermission permissions);

    /**
     * Unmaps a shared memory block from an address in the target process' address space
     * @param target_process Process on which to unmap the memory block
     * @param address Address in system memory to unmap shared memory block
     * @param unmap_size Size of the shared memory block to unmap
     */
    Result Unmap(KProcess& target_process, KProcessAddress address, std::size_t unmap_size);

    /**
     * Gets a pointer to the shared memory block
     * @param offset Offset from the start of the shared memory block to get pointer
     * @return A pointer to the shared memory block from the specified offset
     */
    u8* GetPointer(std::size_t offset = 0) {
        return m_device_memory->GetPointer<u8>(m_physical_address + offset);
    }

    /**
     * Gets a pointer to the shared memory block
     * @param offset Offset from the start of the shared memory block to get pointer
     * @return A pointer to the shared memory block from the specified offset
     */
    const u8* GetPointer(std::size_t offset = 0) const {
        return m_device_memory->GetPointer<u8>(m_physical_address + offset);
    }

    void Finalize() override;

    bool IsInitialized() const override {
        return m_is_initialized;
    }
    static void PostDestroy(uintptr_t arg) {}

private:
    Core::DeviceMemory* m_device_memory{};
    KProcess* m_owner_process{};
    std::optional<KPageGroup> m_page_group{};
    Svc::MemoryPermission m_owner_permission{};
    Svc::MemoryPermission m_user_permission{};
    KPhysicalAddress m_physical_address{};
    std::size_t m_size{};
    KResourceLimit* m_resource_limit{};
    bool m_is_initialized{};
};

} // namespace Kernel


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "common/intrusive_list.h"

#include "core/hle/kernel/slab_helpers.h"

namespace Kernel {

class KSharedMemory;

class KSharedMemoryInfo final : public KSlabAllocated<KSharedMemoryInfo>,
                                public Common::IntrusiveListBaseNode<KSharedMemoryInfo> {

public:
    explicit KSharedMemoryInfo(KernelCore&) {}
    KSharedMemoryInfo() = default;

    constexpr void Initialize(KSharedMemory* m) {
        m_shared_memory = m;
        m_reference_count = 0;
    }

    constexpr KSharedMemory* GetSharedMemory() const {
        return m_shared_memory;
    }

    constexpr void Open() {
        ++m_reference_count;
        ASSERT(m_reference_count > 0);
    }

    constexpr bool Close() {
        ASSERT(m_reference_count > 0);
        return (--m_reference_count) == 0;
    }

private:
    KSharedMemory* m_shared_memory{};
    size_t m_reference_count{};
};

} // namespace Kernel


code:
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <atomic>

#include "common/assert.h"
#include "common/atomic_ops.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/spin_lock.h"

namespace Kernel {

class KernelCore;

namespace impl {

class KSlabHeapImpl {
    YUZU_NON_COPYABLE(KSlabHeapImpl);
    YUZU_NON_MOVEABLE(KSlabHeapImpl);

public:
    struct Node {
        Node* next{};
    };

public:
    constexpr KSlabHeapImpl() = default;

    void Initialize() {
        ASSERT(m_head == nullptr);
    }

    Node* GetHead() const {
        return m_head;
    }

    void* Allocate() {
        // KScopedInterruptDisable di;

        m_lock.lock();

        Node* ret = m_head;
        if (ret != nullptr) [[likely]] {
            m_head = ret->next;
        }

        m_lock.unlock();
        return ret;
    }

    void Free(void* obj) {
        // KScopedInterruptDisable di;

        m_lock.lock();

        Node* node = static_cast<Node*>(obj);
        node->next = m_head;
        m_head = node;

        m_lock.unlock();
    }

private:
    std::atomic<Node*> m_head{};
    Common::SpinLock m_lock;
};

} // namespace impl

template <bool SupportDynamicExpansion>
class KSlabHeapBase : protected impl::KSlabHeapImpl {
    YUZU_NON_COPYABLE(KSlabHeapBase);
    YUZU_NON_MOVEABLE(KSlabHeapBase);

private:
    size_t m_obj_size{};
    uintptr_t m_peak{};
    uintptr_t m_start{};
    uintptr_t m_end{};

private:
    void UpdatePeakImpl(uintptr_t obj) {
        const uintptr_t alloc_peak = obj + this->GetObjectSize();
        uintptr_t cur_peak = m_peak;
        do {
            if (alloc_peak <= cur_peak) {
                break;
            }
        } while (
            !Common::AtomicCompareAndSwap(std::addressof(m_peak), alloc_peak, cur_peak, cur_peak));
    }

public:
    constexpr KSlabHeapBase() = default;

    bool Contains(uintptr_t address) const {
        return m_start <= address && address < m_end;
    }

    void Initialize(size_t obj_size, void* memory, size_t memory_size) {
        // Ensure we don't initialize a slab using null memory.
        ASSERT(memory != nullptr);

        // Set our object size.
        m_obj_size = obj_size;

        // Initialize the base allocator.
        KSlabHeapImpl::Initialize();

        // Set our tracking variables.
        const size_t num_obj = (memory_size / obj_size);
        m_start = reinterpret_cast<uintptr_t>(memory);
        m_end = m_start + num_obj * obj_size;
        m_peak = m_start;

        // Free the objects.
        u8* cur = reinterpret_cast<u8*>(m_end);

        for (size_t i = 0; i < num_obj; i++) {
            cur -= obj_size;
            KSlabHeapImpl::Free(cur);
        }
    }

    size_t GetSlabHeapSize() const {
        return (m_end - m_start) / this->GetObjectSize();
    }

    size_t GetObjectSize() const {
        return m_obj_size;
    }

    void* Allocate() {
        void* obj = KSlabHeapImpl::Allocate();

        return obj;
    }

    void Free(void* obj) {
        // Don't allow freeing an object that wasn't allocated from this heap.
        const bool contained = this->Contains(reinterpret_cast<uintptr_t>(obj));
        ASSERT(contained);
        KSlabHeapImpl::Free(obj);
    }

    size_t GetObjectIndex(const void* obj) const {
        if constexpr (SupportDynamicExpansion) {
            if (!this->Contains(reinterpret_cast<uintptr_t>(obj))) {
                return std::numeric_limits<size_t>::max();
            }
        }

        return (reinterpret_cast<uintptr_t>(obj) - m_start) / this->GetObjectSize();
    }

    size_t GetPeakIndex() const {
        return this->GetObjectIndex(reinterpret_cast<const void*>(m_peak));
    }

    uintptr_t GetSlabHeapAddress() const {
        return m_start;
    }

    size_t GetNumRemaining() const {
        // Only calculate the number of remaining objects under debug configuration.
        return 0;
    }
};

template <typename T>
class KSlabHeap final : public KSlabHeapBase<false> {
private:
    using BaseHeap = KSlabHeapBase<false>;

public:
    constexpr KSlabHeap() = default;

    void Initialize(void* memory, size_t memory_size) {
        BaseHeap::Initialize(sizeof(T), memory, memory_size);
    }

    T* Allocate() {
        T* obj = static_cast<T*>(BaseHeap::Allocate());

        if (obj != nullptr) [[likely]] {
            std::construct_at(obj);
        }
        return obj;
    }

    T* Allocate(KernelCore& kernel) {
        T* obj = static_cast<T*>(BaseHeap::Allocate());

        if (obj != nullptr) [[likely]] {
            std::construct_at(obj, kernel);
        }
        return obj;
    }

    void Free(T* obj) {
        BaseHeap::Free(obj);
    }

    size_t GetObjectIndex(const T* obj) const {
        return BaseHeap::GetObjectIndex(obj);
    }
};

} // namespace Kernel


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "core/hle/kernel/k_spin_lock.h"

namespace Kernel {

void KSpinLock::Lock() {
    m_lock.lock();
}

void KSpinLock::Unlock() {
    m_lock.unlock();
}

bool KSpinLock::TryLock() {
    return m_lock.try_lock();
}

} // namespace Kernel


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <mutex>

#include "common/common_funcs.h"
#include "core/hle/kernel/k_scoped_lock.h"

namespace Kernel {

class KSpinLock {
public:
    explicit KSpinLock() = default;

    YUZU_NON_COPYABLE(KSpinLock);
    YUZU_NON_MOVEABLE(KSpinLock);

    void Lock();
    void Unlock();
    bool TryLock();

private:
    std::mutex m_lock;
};

// TODO(bunnei): Alias for now, in case we want to implement these accurately in the future.
using KAlignedSpinLock = KSpinLock;
using KNotAlignedSpinLock = KSpinLock;

using KScopedSpinLock = KScopedLock<KSpinLock>;
using KScopedAlignedSpinLock = KScopedLock<KAlignedSpinLock>;
using KScopedNotAlignedSpinLock = KScopedLock<KNotAlignedSpinLock>;

} // namespace Kernel