diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index e7fe675cbf..055bea6419 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -243,6 +243,8 @@ add_library(core STATIC
     hle/kernel/k_server_session.h
     hle/kernel/k_session.cpp
     hle/kernel/k_session.h
+    hle/kernel/k_session_request.cpp
+    hle/kernel/k_session_request.h
     hle/kernel/k_shared_memory.cpp
     hle/kernel/k_shared_memory.h
     hle/kernel/k_shared_memory_info.h
diff --git a/src/core/hle/kernel/init/init_slab_setup.cpp b/src/core/hle/kernel/init/init_slab_setup.cpp
index c84d36c8c2..477e4e4075 100644
--- a/src/core/hle/kernel/init/init_slab_setup.cpp
+++ b/src/core/hle/kernel/init/init_slab_setup.cpp
@@ -18,6 +18,7 @@
 #include "core/hle/kernel/k_process.h"
 #include "core/hle/kernel/k_resource_limit.h"
 #include "core/hle/kernel/k_session.h"
+#include "core/hle/kernel/k_session_request.h"
 #include "core/hle/kernel/k_shared_memory.h"
 #include "core/hle/kernel/k_shared_memory_info.h"
 #include "core/hle/kernel/k_system_control.h"
@@ -34,6 +35,7 @@ namespace Kernel::Init {
     HANDLER(KThread, (SLAB_COUNT(KThread)), ##__VA_ARGS__)                                         \
     HANDLER(KEvent, (SLAB_COUNT(KEvent)), ##__VA_ARGS__)                                           \
     HANDLER(KPort, (SLAB_COUNT(KPort)), ##__VA_ARGS__)                                             \
+    HANDLER(KSessionRequest, (SLAB_COUNT(KSession) * 2), ##__VA_ARGS__)                            \
     HANDLER(KSharedMemory, (SLAB_COUNT(KSharedMemory)), ##__VA_ARGS__)                             \
     HANDLER(KSharedMemoryInfo, (SLAB_COUNT(KSharedMemory) * 8), ##__VA_ARGS__)                     \
     HANDLER(KTransferMemory, (SLAB_COUNT(KTransferMemory)), ##__VA_ARGS__)                         \
diff --git a/src/core/hle/kernel/k_client_session.cpp b/src/core/hle/kernel/k_client_session.cpp
index 8892c5b7cb..b4197a8d56 100644
--- a/src/core/hle/kernel/k_client_session.cpp
+++ b/src/core/hle/kernel/k_client_session.cpp
@@ -1,6 +1,7 @@
 // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
 // SPDX-License-Identifier: GPL-2.0-or-later
 
+#include "common/scope_exit.h"
 #include "core/hle/kernel/hle_ipc.h"
 #include "core/hle/kernel/k_client_session.h"
 #include "core/hle/kernel/k_server_session.h"
@@ -10,6 +11,8 @@
 
 namespace Kernel {
 
+static constexpr u32 MessageBufferSize = 0x100;
+
 KClientSession::KClientSession(KernelCore& kernel_)
     : KAutoObjectWithSlabHeapAndContainer{kernel_} {}
 KClientSession::~KClientSession() = default;
@@ -22,8 +25,16 @@ void KClientSession::Destroy() {
 void KClientSession::OnServerClosed() {}
 
 Result KClientSession::SendSyncRequest() {
-    // Signal the server session that new data is available
-    return parent->GetServerSession().OnRequest();
+    // Create a session request.
+    KSessionRequest* request = KSessionRequest::Create(kernel);
+    R_UNLESS(request != nullptr, ResultOutOfResource);
+    SCOPE_EXIT({ request->Close(); });
+
+    // Initialize the request.
+    request->Initialize(nullptr, GetCurrentThread(kernel).GetTLSAddress(), MessageBufferSize);
+
+    // Send the request.
+    return parent->GetServerSession().OnRequest(request);
 }
 
 } // namespace Kernel
diff --git a/src/core/hle/kernel/k_linked_list.h b/src/core/hle/kernel/k_linked_list.h
index 78859ced30..29ebd16b77 100644
--- a/src/core/hle/kernel/k_linked_list.h
+++ b/src/core/hle/kernel/k_linked_list.h
@@ -16,6 +16,7 @@ class KLinkedListNode : public boost::intrusive::list_base_hook<>,
                         public KSlabAllocated<KLinkedListNode> {
 
 public:
+    explicit KLinkedListNode(KernelCore&) {}
     KLinkedListNode() = default;
 
     void Initialize(void* it) {
diff --git a/src/core/hle/kernel/k_page_buffer.h b/src/core/hle/kernel/k_page_buffer.h
index 7e50dc1d18..aef06e2130 100644
--- a/src/core/hle/kernel/k_page_buffer.h
+++ b/src/core/hle/kernel/k_page_buffer.h
@@ -13,6 +13,7 @@ namespace Kernel {
 
 class KPageBuffer final : public KSlabAllocated<KPageBuffer> {
 public:
+    explicit KPageBuffer(KernelCore&) {}
     KPageBuffer() = default;
 
     static KPageBuffer* FromPhysicalAddress(Core::System& system, PAddr phys_addr);
diff --git a/src/core/hle/kernel/k_server_session.cpp b/src/core/hle/kernel/k_server_session.cpp
index 4252c9adbd..685a2a6e67 100644
--- a/src/core/hle/kernel/k_server_session.cpp
+++ b/src/core/hle/kernel/k_server_session.cpp
@@ -29,8 +29,6 @@ namespace Kernel {
 
 using ThreadQueueImplForKServerSessionRequest = KThreadQueue;
 
-static constexpr u32 MessageBufferSize = 0x100;
-
 KServerSession::KServerSession(KernelCore& kernel_)
     : KSynchronizationObject{kernel_}, m_lock{kernel_} {}
 
@@ -73,7 +71,7 @@ bool KServerSession::IsSignaled() const {
     }
 
     // Otherwise, we're signaled if we have a request and aren't handling one.
-    return !m_thread_request_list.empty() && m_current_thread_request == nullptr;
+    return !m_request_list.empty() && m_current_request == nullptr;
 }
 
 void KServerSession::AppendDomainHandler(SessionRequestHandlerPtr handler) {
@@ -178,7 +176,7 @@ Result KServerSession::CompleteSyncRequest(HLERequestContext& context) {
     return result;
 }
 
-Result KServerSession::OnRequest() {
+Result KServerSession::OnRequest(KSessionRequest* request) {
     // Create the wait queue.
     ThreadQueueImplForKServerSessionRequest wait_queue{kernel};
 
@@ -198,14 +196,13 @@ Result KServerSession::OnRequest() {
             this->QueueSyncRequest(GetCurrentThreadPointer(kernel), memory);
         } else {
             // Non-HLE request.
-            auto* thread{GetCurrentThreadPointer(kernel)};
 
             // Get whether we're empty.
-            const bool was_empty = m_thread_request_list.empty();
+            const bool was_empty = m_request_list.empty();
 
-            // Add the thread to the list.
-            thread->Open();
-            m_thread_request_list.push_back(thread);
+            // Add the request to the list.
+            request->Open();
+            m_request_list.push_back(*request);
 
             // If we were empty, signal.
             if (was_empty) {
@@ -213,6 +210,9 @@ Result KServerSession::OnRequest() {
             }
         }
 
+        // If we have a request event, this is asynchronous, and we don't need to wait.
+        R_SUCCEED_IF(request->GetEvent() != nullptr);
+
         // This is a synchronous request, so we should wait for our request to complete.
         GetCurrentThread(kernel).SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::IPC);
         GetCurrentThread(kernel).BeginWait(&wait_queue);
@@ -223,32 +223,32 @@ Result KServerSession::OnRequest() {
 
 Result KServerSession::SendReply() {
     // Lock the session.
-    KScopedLightLock lk(m_lock);
+    KScopedLightLock lk{m_lock};
 
     // Get the request.
-    KThread* client_thread;
+    KSessionRequest* request;
     {
         KScopedSchedulerLock sl{kernel};
 
         // Get the current request.
-        client_thread = m_current_thread_request;
-        R_UNLESS(client_thread != nullptr, ResultInvalidState);
+        request = m_current_request;
+        R_UNLESS(request != nullptr, ResultInvalidState);
 
         // Clear the current request, since we're processing it.
-        m_current_thread_request = nullptr;
-        if (!m_thread_request_list.empty()) {
+        m_current_request = nullptr;
+        if (!m_request_list.empty()) {
             this->NotifyAvailable();
         }
     }
 
     // Close reference to the request once we're done processing it.
-    SCOPE_EXIT({ client_thread->Close(); });
+    SCOPE_EXIT({ request->Close(); });
 
     // Extract relevant information from the request.
-    // const uintptr_t client_message  = request->GetAddress();
-    // const size_t client_buffer_size = request->GetSize();
-    // KThread *client_thread          = request->GetThread();
-    // KEvent *event                   = request->GetEvent();
+    const uintptr_t client_message = request->GetAddress();
+    const size_t client_buffer_size = request->GetSize();
+    KThread* client_thread = request->GetThread();
+    KEvent* event = request->GetEvent();
 
     // Check whether we're closed.
     const bool closed = (client_thread == nullptr || parent->IsClientClosed());
@@ -261,8 +261,8 @@ Result KServerSession::SendReply() {
         UNIMPLEMENTED_IF(server_thread->GetOwnerProcess() != client_thread->GetOwnerProcess());
 
         auto* src_msg_buffer = memory.GetPointer(server_thread->GetTLSAddress());
-        auto* dst_msg_buffer = memory.GetPointer(client_thread->GetTLSAddress());
-        std::memcpy(dst_msg_buffer, src_msg_buffer, MessageBufferSize);
+        auto* dst_msg_buffer = memory.GetPointer(client_message);
+        std::memcpy(dst_msg_buffer, src_msg_buffer, client_buffer_size);
     } else {
         result = ResultSessionClosed;
     }
@@ -278,11 +278,30 @@ Result KServerSession::SendReply() {
 
     // If there's a client thread, update it.
     if (client_thread != nullptr) {
-        // End the client thread's wait.
-        KScopedSchedulerLock sl{kernel};
+        if (event != nullptr) {
+            // // Get the client process/page table.
+            // KProcess *client_process             = client_thread->GetOwnerProcess();
+            // KPageTable *client_page_table        = &client_process->PageTable();
 
-        if (!client_thread->IsTerminationRequested()) {
-            client_thread->EndWait(client_result);
+            // // If we need to, reply with an async error.
+            // if (R_FAILED(client_result)) {
+            //     ReplyAsyncError(client_process, client_message, client_buffer_size,
+            //     client_result);
+            // }
+
+            // // Unlock the client buffer.
+            // // NOTE: Nintendo does not check the result of this.
+            // client_page_table->UnlockForIpcUserBuffer(client_message, client_buffer_size);
+
+            // Signal the event.
+            event->Signal();
+        } else {
+            // End the client thread's wait.
+            KScopedSchedulerLock sl{kernel};
+
+            if (!client_thread->IsTerminationRequested()) {
+                client_thread->EndWait(client_result);
+            }
         }
     }
 
@@ -291,10 +310,10 @@ Result KServerSession::SendReply() {
 
 Result KServerSession::ReceiveRequest() {
     // Lock the session.
-    KScopedLightLock lk(m_lock);
+    KScopedLightLock lk{m_lock};
 
     // Get the request and client thread.
-    // KSessionRequest *request;
+    KSessionRequest* request;
     KThread* client_thread;
 
     {
@@ -304,35 +323,41 @@ Result KServerSession::ReceiveRequest() {
         R_UNLESS(!parent->IsClientClosed(), ResultSessionClosed);
 
         // Ensure we aren't already servicing a request.
-        R_UNLESS(m_current_thread_request == nullptr, ResultNotFound);
+        R_UNLESS(m_current_request == nullptr, ResultNotFound);
 
         // Ensure we have a request to service.
-        R_UNLESS(!m_thread_request_list.empty(), ResultNotFound);
+        R_UNLESS(!m_request_list.empty(), ResultNotFound);
 
         // Pop the first request from the list.
-        client_thread = m_thread_request_list.front();
-        m_thread_request_list.pop_front();
+        request = &m_request_list.front();
+        m_request_list.pop_front();
 
         // Get the thread for the request.
+        client_thread = request->GetThread();
         R_UNLESS(client_thread != nullptr, ResultSessionClosed);
 
         // Open the client thread.
         client_thread->Open();
     }
 
-    // SCOPE_EXIT({ client_thread->Close(); });
+    SCOPE_EXIT({ client_thread->Close(); });
 
     // Set the request as our current.
-    m_current_thread_request = client_thread;
+    m_current_request = request;
+
+    // Get the client address.
+    uintptr_t client_message = request->GetAddress();
+    size_t client_buffer_size = request->GetSize();
+    // bool recv_list_broken = false;
 
     // Receive the message.
     Core::Memory::Memory& memory{kernel.System().Memory()};
     KThread* server_thread{GetCurrentThreadPointer(kernel)};
     UNIMPLEMENTED_IF(server_thread->GetOwnerProcess() != client_thread->GetOwnerProcess());
 
-    auto* src_msg_buffer = memory.GetPointer(client_thread->GetTLSAddress());
+    auto* src_msg_buffer = memory.GetPointer(client_message);
     auto* dst_msg_buffer = memory.GetPointer(server_thread->GetTLSAddress());
-    std::memcpy(dst_msg_buffer, src_msg_buffer, MessageBufferSize);
+    std::memcpy(dst_msg_buffer, src_msg_buffer, client_buffer_size);
 
     // We succeeded.
     return ResultSuccess;
@@ -344,35 +369,34 @@ void KServerSession::CleanupRequests() {
     // Clean up any pending requests.
     while (true) {
         // Get the next request.
-        // KSessionRequest *request = nullptr;
-        KThread* client_thread = nullptr;
+        KSessionRequest* request = nullptr;
         {
             KScopedSchedulerLock sl{kernel};
 
-            if (m_current_thread_request) {
+            if (m_current_request) {
                 // Choose the current request if we have one.
-                client_thread = m_current_thread_request;
-                m_current_thread_request = nullptr;
-            } else if (!m_thread_request_list.empty()) {
+                request = m_current_request;
+                m_current_request = nullptr;
+            } else if (!m_request_list.empty()) {
                 // Pop the request from the front of the list.
-                client_thread = m_thread_request_list.front();
-                m_thread_request_list.pop_front();
+                request = &m_request_list.front();
+                m_request_list.pop_front();
             }
         }
 
         // If there's no request, we're done.
-        if (client_thread == nullptr) {
+        if (request == nullptr) {
             break;
         }
 
         // Close a reference to the request once it's cleaned up.
-        SCOPE_EXIT({ client_thread->Close(); });
+        SCOPE_EXIT({ request->Close(); });
 
         // Extract relevant information from the request.
         // const uintptr_t client_message  = request->GetAddress();
         // const size_t client_buffer_size = request->GetSize();
-        // KThread *client_thread          = request->GetThread();
-        // KEvent *event                   = request->GetEvent();
+        KThread* client_thread = request->GetThread();
+        KEvent* event = request->GetEvent();
 
         // KProcess *server_process             = request->GetServerProcess();
         // KProcess *client_process             = (client_thread != nullptr) ?
@@ -385,11 +409,24 @@ void KServerSession::CleanupRequests() {
 
         // If there's a client thread, update it.
         if (client_thread != nullptr) {
-            // End the client thread's wait.
-            KScopedSchedulerLock sl{kernel};
+            if (event != nullptr) {
+                // // We need to reply async.
+                // ReplyAsyncError(client_process, client_message, client_buffer_size,
+                //                 (R_SUCCEEDED(result) ? ResultSessionClosed : result));
 
-            if (!client_thread->IsTerminationRequested()) {
-                client_thread->EndWait(ResultSessionClosed);
+                // // Unlock the client buffer.
+                // NOTE: Nintendo does not check the result of this.
+                // client_page_table->UnlockForIpcUserBuffer(client_message, client_buffer_size);
+
+                // Signal the event.
+                event->Signal();
+            } else {
+                // End the client thread's wait.
+                KScopedSchedulerLock sl{kernel};
+
+                if (!client_thread->IsTerminationRequested()) {
+                    client_thread->EndWait(ResultSessionClosed);
+                }
             }
         }
     }
diff --git a/src/core/hle/kernel/k_server_session.h b/src/core/hle/kernel/k_server_session.h
index 748d52826c..c40ff4aaf7 100644
--- a/src/core/hle/kernel/k_server_session.h
+++ b/src/core/hle/kernel/k_server_session.h
@@ -12,6 +12,7 @@
 
 #include "core/hle/kernel/hle_ipc.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"
 
@@ -94,7 +95,7 @@ public:
     }
 
     /// TODO: flesh these out to match the real kernel
-    Result OnRequest();
+    Result OnRequest(KSessionRequest* request);
     Result SendReply();
     Result ReceiveRequest();
 
@@ -122,9 +123,8 @@ private:
     KSession* parent{};
 
     /// List of threads which are pending a reply.
-    /// FIXME: KSessionRequest
-    std::list<KThread*> m_thread_request_list;
-    KThread* m_current_thread_request{};
+    boost::intrusive::list<KSessionRequest> m_request_list;
+    KSessionRequest* m_current_request;
 
     KLightLock m_lock;
 };
diff --git a/src/core/hle/kernel/k_session_request.cpp b/src/core/hle/kernel/k_session_request.cpp
new file mode 100644
index 0000000000..520da6aa74
--- /dev/null
+++ b/src/core/hle/kernel/k_session_request.cpp
@@ -0,0 +1,61 @@
+// 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(VAddr client, VAddr 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 = &m_static_mappings[index];
+    } else {
+        // Allocate a page for the extra mappings.
+        if (m_mappings == nullptr) {
+            KPageBuffer* page_buffer = KPageBuffer::Allocate(kernel);
+            R_UNLESS(page_buffer != nullptr, ResultOutOfMemory);
+
+            m_mappings = reinterpret_cast<Mapping*>(page_buffer);
+        }
+
+        mapping = &m_mappings[index - NumStaticMappings];
+    }
+
+    // Set the mapping.
+    mapping->Set(client, server, size, state);
+
+    return ResultSuccess;
+}
+
+Result KSessionRequest::SessionMappings::PushSend(VAddr client, VAddr server, size_t size,
+                                                  KMemoryState state) {
+    ASSERT(m_num_recv == 0);
+    ASSERT(m_num_exch == 0);
+    return this->PushMap(client, server, size, state, m_num_send++);
+}
+
+Result KSessionRequest::SessionMappings::PushReceive(VAddr client, VAddr server, size_t size,
+                                                     KMemoryState state) {
+    ASSERT(m_num_exch == 0);
+    return this->PushMap(client, server, size, state, m_num_send + m_num_recv++);
+}
+
+Result KSessionRequest::SessionMappings::PushExchange(VAddr client, VAddr server, size_t size,
+                                                      KMemoryState state) {
+    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(kernel, reinterpret_cast<KPageBuffer*>(m_mappings));
+        m_mappings = nullptr;
+    }
+}
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_session_request.h b/src/core/hle/kernel/k_session_request.h
new file mode 100644
index 0000000000..fcf5215975
--- /dev/null
+++ b/src/core/hle/kernel/k_session_request.h
@@ -0,0 +1,307 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/kernel/k_auto_object.h"
+#include "core/hle/kernel/k_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 boost::intrusive::list_base_hook<> {
+    KERNEL_AUTOOBJECT_TRAITS(KSessionRequest, KAutoObject);
+
+public:
+    class SessionMappings {
+    private:
+        static constexpr size_t NumStaticMappings = 8;
+
+        class Mapping {
+        public:
+            constexpr void Set(VAddr c, VAddr s, size_t sz, KMemoryState st) {
+                m_client_address = c;
+                m_server_address = s;
+                m_size = sz;
+                m_state = st;
+            }
+
+            constexpr VAddr GetClientAddress() const {
+                return m_client_address;
+            }
+            constexpr VAddr GetServerAddress() const {
+                return m_server_address;
+            }
+            constexpr size_t GetSize() const {
+                return m_size;
+            }
+            constexpr KMemoryState GetMemoryState() const {
+                return m_state;
+            }
+
+        private:
+            VAddr m_client_address;
+            VAddr m_server_address;
+            size_t m_size;
+            KMemoryState m_state;
+        };
+
+    public:
+        explicit SessionMappings(KernelCore& kernel_)
+            : kernel(kernel_), m_mappings(nullptr), m_num_send(), m_num_recv(), m_num_exch() {}
+
+        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(VAddr client, VAddr server, size_t size, KMemoryState state);
+        Result PushReceive(VAddr client, VAddr server, size_t size, KMemoryState state);
+        Result PushExchange(VAddr client, VAddr server, size_t size, KMemoryState state);
+
+        VAddr GetSendClientAddress(size_t i) const {
+            return GetSendMapping(i).GetClientAddress();
+        }
+        VAddr 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();
+        }
+
+        VAddr GetReceiveClientAddress(size_t i) const {
+            return GetReceiveMapping(i).GetClientAddress();
+        }
+        VAddr 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();
+        }
+
+        VAddr GetExchangeClientAddress(size_t i) const {
+            return GetExchangeMapping(i).GetClientAddress();
+        }
+        VAddr 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(VAddr client, VAddr 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& kernel;
+        Mapping m_static_mappings[NumStaticMappings];
+        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_), m_thread(nullptr), m_server(nullptr),
+          m_event(nullptr) {}
+
+    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(kernel, this);
+    }
+
+    void Initialize(KEvent* event, uintptr_t address, size_t size) {
+        m_mappings.Initialize();
+
+        m_thread = GetCurrentThreadPointer(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(VAddr client, VAddr server, size_t size, KMemoryState state) {
+        return m_mappings.PushSend(client, server, size, state);
+    }
+
+    Result PushReceive(VAddr client, VAddr server, size_t size, KMemoryState state) {
+        return m_mappings.PushReceive(client, server, size, state);
+    }
+
+    Result PushExchange(VAddr client, VAddr server, size_t size, KMemoryState state) {
+        return m_mappings.PushExchange(client, server, size, state);
+    }
+
+    VAddr GetSendClientAddress(size_t i) const {
+        return m_mappings.GetSendClientAddress(i);
+    }
+    VAddr 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);
+    }
+
+    VAddr GetReceiveClientAddress(size_t i) const {
+        return m_mappings.GetReceiveClientAddress(i);
+    }
+    VAddr 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);
+    }
+
+    VAddr GetExchangeClientAddress(size_t i) const {
+        return m_mappings.GetExchangeClientAddress(i);
+    }
+    VAddr 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() {
+        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
diff --git a/src/core/hle/kernel/k_shared_memory_info.h b/src/core/hle/kernel/k_shared_memory_info.h
index e43db8515b..2bb6b6d088 100644
--- a/src/core/hle/kernel/k_shared_memory_info.h
+++ b/src/core/hle/kernel/k_shared_memory_info.h
@@ -15,7 +15,8 @@ class KSharedMemoryInfo final : public KSlabAllocated<KSharedMemoryInfo>,
                                 public boost::intrusive::list_base_hook<> {
 
 public:
-    explicit KSharedMemoryInfo() = default;
+    explicit KSharedMemoryInfo(KernelCore&) {}
+    KSharedMemoryInfo() = default;
 
     constexpr void Initialize(KSharedMemory* shmem) {
         shared_memory = shmem;
diff --git a/src/core/hle/kernel/k_thread_local_page.h b/src/core/hle/kernel/k_thread_local_page.h
index 0a7f226807..5d466ace7a 100644
--- a/src/core/hle/kernel/k_thread_local_page.h
+++ b/src/core/hle/kernel/k_thread_local_page.h
@@ -26,7 +26,7 @@ public:
     static_assert(RegionsPerPage > 0);
 
 public:
-    constexpr explicit KThreadLocalPage(VAddr addr = {}) : m_virt_addr(addr) {
+    constexpr explicit KThreadLocalPage(KernelCore&, VAddr addr = {}) : m_virt_addr(addr) {
         m_is_region_free.fill(true);
     }
 
diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h
index 6eded95393..266be2bc4f 100644
--- a/src/core/hle/kernel/kernel.h
+++ b/src/core/hle/kernel/kernel.h
@@ -47,6 +47,7 @@ class KResourceLimit;
 class KScheduler;
 class KServerSession;
 class KSession;
+class KSessionRequest;
 class KSharedMemory;
 class KSharedMemoryInfo;
 class KThread;
@@ -360,6 +361,8 @@ public:
             return slab_heap_container->page_buffer;
         } else if constexpr (std::is_same_v<T, KThreadLocalPage>) {
             return slab_heap_container->thread_local_page;
+        } else if constexpr (std::is_same_v<T, KSessionRequest>) {
+            return slab_heap_container->session_request;
         }
     }
 
@@ -422,6 +425,7 @@ private:
         KSlabHeap<KCodeMemory> code_memory;
         KSlabHeap<KPageBuffer> page_buffer;
         KSlabHeap<KThreadLocalPage> thread_local_page;
+        KSlabHeap<KSessionRequest> session_request;
     };
 
     std::unique_ptr<SlabHeapContainer> slab_heap_container;
diff --git a/src/core/hle/kernel/slab_helpers.h b/src/core/hle/kernel/slab_helpers.h
index 299a981a8a..06b51e9199 100644
--- a/src/core/hle/kernel/slab_helpers.h
+++ b/src/core/hle/kernel/slab_helpers.h
@@ -24,7 +24,7 @@ public:
     }
 
     static Derived* Allocate(KernelCore& kernel) {
-        return kernel.SlabHeap<Derived>().Allocate();
+        return kernel.SlabHeap<Derived>().Allocate(kernel);
     }
 
     static void Free(KernelCore& kernel, Derived* obj) {