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

#include "common/scope_exit.h"
#include "core/hle/kernel/k_client_port.h"
#include "core/hle/kernel/k_light_session.h"
#include "core/hle/kernel/k_port.h"
#include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/k_scoped_resource_reservation.h"
#include "core/hle/kernel/k_session.h"
#include "core/hle/kernel/svc_results.h"

namespace Kernel {

KClientPort::KClientPort(KernelCore& kernel) : KSynchronizationObject{kernel} {}
KClientPort::~KClientPort() = default;

void KClientPort::Initialize(KPort* parent, s32 max_sessions) {
    // Set member variables.
    m_num_sessions = 0;
    m_peak_sessions = 0;
    m_parent = parent;
    m_max_sessions = max_sessions;
}

void KClientPort::OnSessionFinalized() {
    KScopedSchedulerLock sl{m_kernel};

    if (const auto prev = m_num_sessions--; prev == m_max_sessions) {
        this->NotifyAvailable();
    }
}

void KClientPort::OnServerClosed() {}

bool KClientPort::IsLight() const {
    return this->GetParent()->IsLight();
}

bool KClientPort::IsServerClosed() const {
    return this->GetParent()->IsServerClosed();
}

void KClientPort::Destroy() {
    // Note with our parent that we're closed.
    m_parent->OnClientClosed();

    // Close our reference to our parent.
    m_parent->Close();
}

bool KClientPort::IsSignaled() const {
    return m_num_sessions.load() < m_max_sessions;
}

Result KClientPort::CreateSession(KClientSession** out) {
    // Declare the session we're going to allocate.
    KSession* session{};

    // Reserve a new session from the resource limit.
    KScopedResourceReservation session_reservation(GetCurrentProcessPointer(m_kernel),
                                                   LimitableResource::SessionCountMax);
    R_UNLESS(session_reservation.Succeeded(), ResultLimitReached);

    // Allocate a session normally.
    // TODO: Dynamic resource limits
    session = KSession::Create(m_kernel);

    // Check that we successfully created a session.
    R_UNLESS(session != nullptr, ResultOutOfResource);

    // Update the session counts.
    {
        ON_RESULT_FAILURE {
            session->Close();
        };

        // Atomically increment the number of sessions.
        s32 new_sessions{};
        {
            const auto max = m_max_sessions;
            auto cur_sessions = m_num_sessions.load(std::memory_order_acquire);
            do {
                R_UNLESS(cur_sessions < max, ResultOutOfSessions);
                new_sessions = cur_sessions + 1;
            } while (!m_num_sessions.compare_exchange_weak(cur_sessions, new_sessions,
                                                           std::memory_order_relaxed));
        }

        // Atomically update the peak session tracking.
        {
            auto peak = m_peak_sessions.load(std::memory_order_acquire);
            do {
                if (peak >= new_sessions) {
                    break;
                }
            } while (!m_peak_sessions.compare_exchange_weak(peak, new_sessions,
                                                            std::memory_order_relaxed));
        }
    }

    // Initialize the session.
    session->Initialize(this, m_parent->GetName());

    // Commit the session reservation.
    session_reservation.Commit();

    // Register the session.
    KSession::Register(m_kernel, session);
    ON_RESULT_FAILURE {
        session->GetClientSession().Close();
        session->GetServerSession().Close();
    };

    // Enqueue the session with our parent.
    R_TRY(m_parent->EnqueueSession(std::addressof(session->GetServerSession())));

    // We succeeded, so set the output.
    *out = std::addressof(session->GetClientSession());
    R_SUCCEED();
}

Result KClientPort::CreateLightSession(KLightClientSession** out) {
    // Declare the session we're going to allocate.
    KLightSession* session{};

    // Reserve a new session from the resource limit.
    KScopedResourceReservation session_reservation(GetCurrentProcessPointer(m_kernel),
                                                   Svc::LimitableResource::SessionCountMax);
    R_UNLESS(session_reservation.Succeeded(), ResultLimitReached);

    // Allocate a session normally.
    // TODO: Dynamic resource limits
    session = KLightSession::Create(m_kernel);

    // Check that we successfully created a session.
    R_UNLESS(session != nullptr, ResultOutOfResource);

    // Update the session counts.
    {
        ON_RESULT_FAILURE {
            session->Close();
        };

        // Atomically increment the number of sessions.
        s32 new_sessions;
        {
            const auto max = m_max_sessions;
            auto cur_sessions = m_num_sessions.load(std::memory_order_acquire);
            do {
                R_UNLESS(cur_sessions < max, ResultOutOfSessions);
                new_sessions = cur_sessions + 1;
            } while (!m_num_sessions.compare_exchange_weak(cur_sessions, new_sessions,
                                                           std::memory_order_relaxed));
        }

        // Atomically update the peak session tracking.
        {
            auto peak = m_peak_sessions.load(std::memory_order_acquire);
            do {
                if (peak >= new_sessions) {
                    break;
                }
            } while (!m_peak_sessions.compare_exchange_weak(peak, new_sessions,
                                                            std::memory_order_relaxed));
        }
    }

    // Initialize the session.
    session->Initialize(this, m_parent->GetName());

    // Commit the session reservation.
    session_reservation.Commit();

    // Register the session.
    KLightSession::Register(m_kernel, session);
    ON_RESULT_FAILURE {
        session->GetClientSession().Close();
        session->GetServerSession().Close();
    };

    // Enqueue the session with our parent.
    R_TRY(m_parent->EnqueueSession(std::addressof(session->GetServerSession())));

    // We succeeded, so set the output.
    *out = std::addressof(session->GetClientSession());
    R_SUCCEED();
}

} // namespace Kernel


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

#pragma once

#include <memory>

#include "common/common_types.h"
#include "core/hle/kernel/k_synchronization_object.h"
#include "core/hle/result.h"

namespace Kernel {

class KLightClientSession;
class KClientSession;
class KernelCore;
class KPort;

class KClientPort final : public KSynchronizationObject {
    KERNEL_AUTOOBJECT_TRAITS(KClientPort, KSynchronizationObject);

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

    void Initialize(KPort* parent, s32 max_sessions);
    void OnSessionFinalized();
    void OnServerClosed();

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

    s32 GetNumSessions() const {
        return m_num_sessions;
    }
    s32 GetPeakSessions() const {
        return m_peak_sessions;
    }
    s32 GetMaxSessions() const {
        return m_max_sessions;
    }

    bool IsLight() const;
    bool IsServerClosed() const;

    // Overridden virtual functions.
    void Destroy() override;
    bool IsSignaled() const override;

    Result CreateSession(KClientSession** out);
    Result CreateLightSession(KLightClientSession** out);

private:
    std::atomic<s32> m_num_sessions{};
    std::atomic<s32> m_peak_sessions{};
    s32 m_max_sessions{};
    KPort* m_parent{};
};

} // 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 "common/scope_exit.h"
#include "core/hle/kernel/k_client_session.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/result.h"

namespace Kernel {

KClientSession::KClientSession(KernelCore& kernel) : KAutoObject{kernel} {}
KClientSession::~KClientSession() = default;

void KClientSession::Destroy() {
    m_parent->OnClientClosed();
    m_parent->Close();
}

void KClientSession::OnServerClosed() {}

Result KClientSession::SendSyncRequest(uintptr_t address, size_t size) {
    // Create a session request.
    KSessionRequest* request = KSessionRequest::Create(m_kernel);
    R_UNLESS(request != nullptr, ResultOutOfResource);
    SCOPE_EXIT {
        request->Close();
    };

    // Initialize the request.
    request->Initialize(nullptr, address, size);

    // Send the request.
    R_RETURN(m_parent->OnRequest(request));
}

Result KClientSession::SendAsyncRequest(KEvent* event, uintptr_t address, size_t size) {
    // Create a session request.
    KSessionRequest* request = KSessionRequest::Create(m_kernel);
    R_UNLESS(request != nullptr, ResultOutOfResource);
    SCOPE_EXIT {
        request->Close();
    };

    // Initialize the request.
    request->Initialize(event, address, size);

    // Send the request.
    R_RETURN(m_parent->OnRequest(request));
}

} // namespace Kernel


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

#pragma once

#include <string>

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

namespace Kernel {

class KernelCore;
class KSession;

class KClientSession final : public KAutoObject {
    KERNEL_AUTOOBJECT_TRAITS(KClientSession, KAutoObject);

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

    void Initialize(KSession* parent) {
        // Set member variables.
        m_parent = parent;
    }

    void Destroy() override;

    KSession* GetParent() const {
        return m_parent;
    }

    Result SendSyncRequest(uintptr_t address, size_t size);
    Result SendAsyncRequest(KEvent* event, uintptr_t address, size_t size);

    void OnServerClosed();

private:
    KSession* m_parent{};
};

} // namespace Kernel


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

#include "common/alignment.h"
#include "common/common_types.h"
#include "core/device_memory.h"
#include "core/hle/kernel/k_code_memory.h"
#include "core/hle/kernel/k_light_lock.h"
#include "core/hle/kernel/k_memory_block.h"
#include "core/hle/kernel/k_page_group.h"
#include "core/hle/kernel/k_page_table.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/slab_helpers.h"
#include "core/hle/kernel/svc_types.h"
#include "core/hle/result.h"

namespace Kernel {

KCodeMemory::KCodeMemory(KernelCore& kernel)
    : KAutoObjectWithSlabHeapAndContainer{kernel}, m_lock(kernel) {}

Result KCodeMemory::Initialize(Core::DeviceMemory& device_memory, KProcessAddress addr,
                               size_t size) {
    // Set members.
    m_owner = GetCurrentProcessPointer(m_kernel);

    // Get the owner page table.
    auto& page_table = m_owner->GetPageTable();

    // Construct the page group.
    m_page_group.emplace(m_kernel, page_table.GetBlockInfoManager());

    // Lock the memory.
    R_TRY(page_table.LockForCodeMemory(std::addressof(*m_page_group), addr, size))

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

    // Set remaining tracking members.
    m_owner->Open();
    m_address = addr;
    m_is_initialized = true;
    m_is_owner_mapped = false;
    m_is_mapped = false;

    // We succeeded.
    R_SUCCEED();
}

void KCodeMemory::Finalize() {
    // Unlock.
    if (!m_is_mapped && !m_is_owner_mapped) {
        const size_t size = m_page_group->GetNumPages() * PageSize;
        m_owner->GetPageTable().UnlockForCodeMemory(m_address, size, *m_page_group);
    }

    // Close the page group.
    m_page_group->Close();
    m_page_group->Finalize();

    // Close our reference to our owner.
    m_owner->Close();
}

Result KCodeMemory::Map(KProcessAddress address, size_t size) {
    // Validate the size.
    R_UNLESS(m_page_group->GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);

    // Lock ourselves.
    KScopedLightLock lk(m_lock);

    // Ensure we're not already mapped.
    R_UNLESS(!m_is_mapped, ResultInvalidState);

    // Map the memory.
    R_TRY(GetCurrentProcess(m_kernel).GetPageTable().MapPageGroup(
        address, *m_page_group, KMemoryState::CodeOut, KMemoryPermission::UserReadWrite));

    // Mark ourselves as mapped.
    m_is_mapped = true;

    R_SUCCEED();
}

Result KCodeMemory::Unmap(KProcessAddress address, size_t size) {
    // Validate the size.
    R_UNLESS(m_page_group->GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);

    // Lock ourselves.
    KScopedLightLock lk(m_lock);

    // Unmap the memory.
    R_TRY(GetCurrentProcess(m_kernel).GetPageTable().UnmapPageGroup(address, *m_page_group,
                                                                    KMemoryState::CodeOut));

    // Mark ourselves as unmapped.
    m_is_mapped = false;

    R_SUCCEED();
}

Result KCodeMemory::MapToOwner(KProcessAddress address, size_t size, Svc::MemoryPermission perm) {
    // Validate the size.
    R_UNLESS(m_page_group->GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);

    // Lock ourselves.
    KScopedLightLock lk(m_lock);

    // Ensure we're not already mapped.
    R_UNLESS(!m_is_owner_mapped, ResultInvalidState);

    // Convert the memory permission.
    KMemoryPermission k_perm{};
    switch (perm) {
    case Svc::MemoryPermission::Read:
        k_perm = KMemoryPermission::UserRead;
        break;
    case Svc::MemoryPermission::ReadExecute:
        k_perm = KMemoryPermission::UserReadExecute;
        break;
    default:
        // Already validated by ControlCodeMemory svc
        UNREACHABLE();
    }

    // Map the memory.
    R_TRY(m_owner->GetPageTable().MapPageGroup(address, *m_page_group, KMemoryState::GeneratedCode,
                                               k_perm));

    // Mark ourselves as mapped.
    m_is_owner_mapped = true;

    R_SUCCEED();
}

Result KCodeMemory::UnmapFromOwner(KProcessAddress address, size_t size) {
    // Validate the size.
    R_UNLESS(m_page_group->GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);

    // Lock ourselves.
    KScopedLightLock lk(m_lock);

    // Unmap the memory.
    R_TRY(m_owner->GetPageTable().UnmapPageGroup(address, *m_page_group,
                                                 KMemoryState::GeneratedCode));

    // Mark ourselves as unmapped.
    m_is_owner_mapped = false;

    R_SUCCEED();
}

} // namespace Kernel


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

#pragma once

#include <optional>

#include "core/device_memory.h"
#include "core/hle/kernel/k_auto_object.h"
#include "core/hle/kernel/k_light_lock.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/kernel/svc_types.h"
#include "core/hle/result.h"

namespace Kernel {

enum class CodeMemoryOperation : u32 {
    Map = 0,
    MapToOwner = 1,
    Unmap = 2,
    UnmapFromOwner = 3,
};

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

public:
    explicit KCodeMemory(KernelCore& kernel);

    Result Initialize(Core::DeviceMemory& device_memory, KProcessAddress address, size_t size);
    void Finalize() override;

    Result Map(KProcessAddress address, size_t size);
    Result Unmap(KProcessAddress address, size_t size);
    Result MapToOwner(KProcessAddress address, size_t size, Svc::MemoryPermission perm);
    Result UnmapFromOwner(KProcessAddress address, size_t size);

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

    KProcess* GetOwner() const override {
        return m_owner;
    }
    KProcessAddress GetSourceAddress() const {
        return m_address;
    }
    size_t GetSize() const {
        return m_is_initialized ? m_page_group->GetNumPages() * PageSize : 0;
    }

private:
    std::optional<KPageGroup> m_page_group{};
    KProcess* m_owner{};
    KProcessAddress m_address{};
    KLightLock m_lock;
    bool m_is_initialized{};
    bool m_is_owner_mapped{};
    bool m_is_mapped{};
};

} // namespace Kernel


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

#include "core/arm/exclusive_monitor.h"
#include "core/core.h"
#include "core/hle/kernel/k_condition_variable.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.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/svc_common.h"
#include "core/hle/kernel/svc_results.h"
#include "core/memory.h"

namespace Kernel {

namespace {

bool ReadFromUser(KernelCore& kernel, u32* out, KProcessAddress address) {
    *out = GetCurrentMemory(kernel).Read32(GetInteger(address));
    return true;
}

bool WriteToUser(KernelCore& kernel, KProcessAddress address, const u32* p) {
    GetCurrentMemory(kernel).Write32(GetInteger(address), *p);
    return true;
}

bool UpdateLockAtomic(KernelCore& kernel, u32* out, KProcessAddress address, u32 if_zero,
                      u32 new_orr_mask) {
    auto& monitor = GetCurrentProcess(kernel).GetExclusiveMonitor();
    const auto current_core = kernel.CurrentPhysicalCoreIndex();

    u32 expected{};

    while (true) {
        // Load the value from the address.
        expected = monitor.ExclusiveRead32(current_core, GetInteger(address));

        // Orr in the new mask.
        u32 value = expected | new_orr_mask;

        // If the value is zero, use the if_zero value, otherwise use the newly orr'd value.
        if (!expected) {
            value = if_zero;
        }

        // Try to store.
        if (monitor.ExclusiveWrite32(current_core, GetInteger(address), value)) {
            break;
        }

        // If we failed to store, try again.
    }

    // We're done.
    *out = expected;
    return true;
}

class ThreadQueueImplForKConditionVariableWaitForAddress final : public KThreadQueue {
public:
    explicit ThreadQueueImplForKConditionVariableWaitForAddress(KernelCore& kernel)
        : KThreadQueue(kernel) {}

    void CancelWait(KThread* waiting_thread, Result wait_result, bool cancel_timer_task) override {
        // Remove the thread as a waiter from its owner.
        waiting_thread->GetLockOwner()->RemoveWaiter(waiting_thread);

        // Invoke the base cancel wait handler.
        KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task);
    }
};

class ThreadQueueImplForKConditionVariableWaitConditionVariable final : public KThreadQueue {
private:
    KConditionVariable::ThreadTree* m_tree;

public:
    explicit ThreadQueueImplForKConditionVariableWaitConditionVariable(
        KernelCore& kernel, KConditionVariable::ThreadTree* t)
        : KThreadQueue(kernel), m_tree(t) {}

    void CancelWait(KThread* waiting_thread, Result wait_result, bool cancel_timer_task) override {
        // Remove the thread as a waiter from its owner.
        if (KThread* owner = waiting_thread->GetLockOwner(); owner != nullptr) {
            owner->RemoveWaiter(waiting_thread);
        }

        // If the thread is waiting on a condvar, remove it from the tree.
        if (waiting_thread->IsWaitingForConditionVariable()) {
            m_tree->erase(m_tree->iterator_to(*waiting_thread));
            waiting_thread->ClearConditionVariable();
        }

        // Invoke the base cancel wait handler.
        KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task);
    }
};

} // namespace

KConditionVariable::KConditionVariable(Core::System& system)
    : m_system{system}, m_kernel{system.Kernel()} {}

KConditionVariable::~KConditionVariable() = default;

Result KConditionVariable::SignalToAddress(KernelCore& kernel, KProcessAddress addr) {
    KThread* owner_thread = GetCurrentThreadPointer(kernel);

    // Signal the address.
    {
        KScopedSchedulerLock sl(kernel);

        // Remove waiter thread.
        bool has_waiters{};
        KThread* const next_owner_thread =
            owner_thread->RemoveUserWaiterByKey(std::addressof(has_waiters), addr);

        // Determine the next tag.
        u32 next_value{};
        if (next_owner_thread != nullptr) {
            next_value = next_owner_thread->GetAddressKeyValue();
            if (has_waiters) {
                next_value |= Svc::HandleWaitMask;
            }
        }

        // Synchronize memory before proceeding.
        std::atomic_thread_fence(std::memory_order_seq_cst);

        // Write the value to userspace.
        Result result{ResultSuccess};
        if (WriteToUser(kernel, addr, std::addressof(next_value))) [[likely]] {
            result = ResultSuccess;
        } else {
            result = ResultInvalidCurrentMemory;
        }

        // If necessary, signal the next owner thread.
        if (next_owner_thread != nullptr) {
            next_owner_thread->EndWait(result);
        }

        R_RETURN(result);
    }
}

Result KConditionVariable::WaitForAddress(KernelCore& kernel, Handle handle, KProcessAddress addr,
                                          u32 value) {
    KThread* cur_thread = GetCurrentThreadPointer(kernel);
    ThreadQueueImplForKConditionVariableWaitForAddress wait_queue(kernel);

    // Wait for the address.
    KThread* owner_thread{};
    {
        KScopedSchedulerLock sl(kernel);

        // Check if the thread should terminate.
        R_UNLESS(!cur_thread->IsTerminationRequested(), ResultTerminationRequested);

        // Read the tag from userspace.
        u32 test_tag{};
        R_UNLESS(ReadFromUser(kernel, std::addressof(test_tag), addr), ResultInvalidCurrentMemory);

        // If the tag isn't the handle (with wait mask), we're done.
        R_SUCCEED_IF(test_tag != (handle | Svc::HandleWaitMask));

        // Get the lock owner thread.
        owner_thread = GetCurrentProcess(kernel)
                           .GetHandleTable()
                           .GetObjectWithoutPseudoHandle<KThread>(handle)
                           .ReleasePointerUnsafe();
        R_UNLESS(owner_thread != nullptr, ResultInvalidHandle);

        // Update the lock.
        cur_thread->SetUserAddressKey(addr, value);
        owner_thread->AddWaiter(cur_thread);

        // Begin waiting.
        cur_thread->BeginWait(std::addressof(wait_queue));
        cur_thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::ConditionVar);
    }

    // Close our reference to the owner thread, now that the wait is over.
    owner_thread->Close();

    // Get the wait result.
    R_RETURN(cur_thread->GetWaitResult());
}

void KConditionVariable::SignalImpl(KThread* thread) {
    // Check pre-conditions.
    ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel));

    // Update the tag.
    KProcessAddress address = thread->GetAddressKey();
    u32 own_tag = thread->GetAddressKeyValue();

    u32 prev_tag{};
    bool can_access{};
    {
        // NOTE: If scheduler lock is not held here, interrupt disable is required.
        // KScopedInterruptDisable di;

        // TODO(bunnei): We should call CanAccessAtomic(..) here.
        can_access = true;
        if (can_access) [[likely]] {
            UpdateLockAtomic(m_kernel, std::addressof(prev_tag), address, own_tag,
                             Svc::HandleWaitMask);
        }
    }

    if (can_access) [[likely]] {
        if (prev_tag == Svc::InvalidHandle) {
            // If nobody held the lock previously, we're all good.
            thread->EndWait(ResultSuccess);
        } else {
            // Get the previous owner.
            KThread* owner_thread = GetCurrentProcess(m_kernel)
                                        .GetHandleTable()
                                        .GetObjectWithoutPseudoHandle<KThread>(
                                            static_cast<Handle>(prev_tag & ~Svc::HandleWaitMask))
                                        .ReleasePointerUnsafe();

            if (owner_thread) [[likely]] {
                // Add the thread as a waiter on the owner.
                owner_thread->AddWaiter(thread);
                owner_thread->Close();
            } else {
                // The lock was tagged with a thread that doesn't exist.
                thread->EndWait(ResultInvalidState);
            }
        }
    } else {
        // If the address wasn't accessible, note so.
        thread->EndWait(ResultInvalidCurrentMemory);
    }
}

void KConditionVariable::Signal(u64 cv_key, s32 count) {
    // Perform signaling.
    s32 num_waiters{};
    {
        KScopedSchedulerLock sl(m_kernel);

        auto it = m_tree.nfind_key({cv_key, -1});
        while ((it != m_tree.end()) && (count <= 0 || num_waiters < count) &&
               (it->GetConditionVariableKey() == cv_key)) {
            KThread* target_thread = std::addressof(*it);

            it = m_tree.erase(it);
            target_thread->ClearConditionVariable();

            this->SignalImpl(target_thread);

            ++num_waiters;
        }

        // If we have no waiters, clear the has waiter flag.
        if (it == m_tree.end() || it->GetConditionVariableKey() != cv_key) {
            const u32 has_waiter_flag{};
            WriteToUser(m_kernel, cv_key, std::addressof(has_waiter_flag));
        }
    }
}

Result KConditionVariable::Wait(KProcessAddress addr, u64 key, u32 value, s64 timeout) {
    // Prepare to wait.
    KThread* cur_thread = GetCurrentThreadPointer(m_kernel);
    KHardwareTimer* timer{};
    ThreadQueueImplForKConditionVariableWaitConditionVariable wait_queue(m_kernel,
                                                                         std::addressof(m_tree));

    {
        KScopedSchedulerLockAndSleep slp(m_kernel, std::addressof(timer), cur_thread, timeout);

        // Check that the thread isn't terminating.
        if (cur_thread->IsTerminationRequested()) {
            slp.CancelSleep();
            R_THROW(ResultTerminationRequested);
        }

        // Update the value and process for the next owner.
        {
            // Remove waiter thread.
            bool has_waiters{};
            KThread* next_owner_thread =
                cur_thread->RemoveUserWaiterByKey(std::addressof(has_waiters), addr);

            // Update for the next owner thread.
            u32 next_value{};
            if (next_owner_thread != nullptr) {
                // Get the next tag value.
                next_value = next_owner_thread->GetAddressKeyValue();
                if (has_waiters) {
                    next_value |= Svc::HandleWaitMask;
                }

                // Wake up the next owner.
                next_owner_thread->EndWait(ResultSuccess);
            }

            // Write to the cv key.
            {
                const u32 has_waiter_flag = 1;
                WriteToUser(m_kernel, key, std::addressof(has_waiter_flag));
                std::atomic_thread_fence(std::memory_order_seq_cst);
            }

            // Write the value to userspace.
            if (!WriteToUser(m_kernel, addr, std::addressof(next_value))) {
                slp.CancelSleep();
                R_THROW(ResultInvalidCurrentMemory);
            }
        }

        // If timeout is zero, time out.
        R_UNLESS(timeout != 0, ResultTimedOut);

        // Update condition variable tracking.
        cur_thread->SetConditionVariable(std::addressof(m_tree), addr, key, value);
        m_tree.insert(*cur_thread);

        // Begin waiting.
        wait_queue.SetHardwareTimer(timer);
        cur_thread->BeginWait(std::addressof(wait_queue));
        cur_thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::ConditionVar);
    }

    // Get the wait result.
    R_RETURN(cur_thread->GetWaitResult());
}

} // namespace Kernel


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

#pragma once

#include "common/assert.h"

#include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/k_typed_address.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/result.h"

namespace Core {
class System;
}

namespace Kernel {

class KConditionVariable {
public:
    using ThreadTree = typename KThread::ConditionVariableThreadTreeType;

    explicit KConditionVariable(Core::System& system);
    ~KConditionVariable();

    // Arbitration.
    static Result SignalToAddress(KernelCore& kernel, KProcessAddress addr);
    static Result WaitForAddress(KernelCore& kernel, Handle handle, KProcessAddress addr,
                                 u32 value);

    // Condition variable.
    void Signal(u64 cv_key, s32 count);
    Result Wait(KProcessAddress addr, u64 key, u32 value, s64 timeout);

private:
    void SignalImpl(KThread* thread);

private:
    Core::System& m_system;
    KernelCore& m_kernel;
    ThreadTree m_tree{};
};

inline void BeforeUpdatePriority(KernelCore& kernel, KConditionVariable::ThreadTree* tree,
                                 KThread* thread) {
    ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(kernel));

    tree->erase(tree->iterator_to(*thread));
}

inline void AfterUpdatePriority(KernelCore& kernel, KConditionVariable::ThreadTree* tree,
                                KThread* thread) {
    ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(kernel));

    tree->insert(*thread);
}

} // namespace Kernel


code:
// 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/slab_helpers.h"

namespace Kernel {

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

public:
    explicit KDebug(KernelCore& kernel) : KAutoObjectWithSlabHeapAndContainer{kernel} {}

    static void PostDestroy(uintptr_t arg) {}
};

} // namespace Kernel


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

#include "common/assert.h"
#include "core/core.h"
#include "core/hle/kernel/k_device_address_space.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/svc_results.h"

namespace Kernel {

KDeviceAddressSpace::KDeviceAddressSpace(KernelCore& kernel)
    : KAutoObjectWithSlabHeapAndContainer(kernel), m_lock(kernel), m_is_initialized(false) {}
KDeviceAddressSpace::~KDeviceAddressSpace() = default;

void KDeviceAddressSpace::Initialize() {
    // This just forwards to the device page table manager.
    // KDevicePageTable::Initialize();
}

// Member functions.
Result KDeviceAddressSpace::Initialize(u64 address, u64 size) {
    // Initialize the device page table.
    // R_TRY(m_table.Initialize(address, size));

    // Set member variables.
    m_space_address = address;
    m_space_size = size;
    m_is_initialized = true;

    R_SUCCEED();
}

void KDeviceAddressSpace::Finalize() {
    // Finalize the table.
    // m_table.Finalize();
}

Result KDeviceAddressSpace::Attach(Svc::DeviceName device_name) {
    // Lock the address space.
    KScopedLightLock lk(m_lock);

    // Attach.
    // R_RETURN(m_table.Attach(device_name, m_space_address, m_space_size));
    R_SUCCEED();
}

Result KDeviceAddressSpace::Detach(Svc::DeviceName device_name) {
    // Lock the address space.
    KScopedLightLock lk(m_lock);

    // Detach.
    // R_RETURN(m_table.Detach(device_name));
    R_SUCCEED();
}

Result KDeviceAddressSpace::Map(KProcessPageTable* page_table, KProcessAddress process_address,
                                size_t size, u64 device_address, u32 option, bool is_aligned) {
    // Check that the address falls within the space.
    R_UNLESS((m_space_address <= device_address &&
              device_address + size - 1 <= m_space_address + m_space_size - 1),
             ResultInvalidCurrentMemory);

    // Decode the option.
    const Svc::MapDeviceAddressSpaceOption option_pack{option};
    const auto device_perm = option_pack.permission.Value();
    const auto flags = option_pack.flags.Value();
    const auto reserved = option_pack.reserved.Value();

    // Validate the option.
    // TODO: It is likely that this check for flags == none is only on NX board.
    R_UNLESS(flags == Svc::MapDeviceAddressSpaceFlag::None, ResultInvalidEnumValue);
    R_UNLESS(reserved == 0, ResultInvalidEnumValue);

    // Lock the address space.
    KScopedLightLock lk(m_lock);

    // Lock the page table to prevent concurrent device mapping operations.
    // KScopedLightLock pt_lk = page_table->AcquireDeviceMapLock();

    // Lock the pages.
    bool is_io{};
    R_TRY(page_table->LockForMapDeviceAddressSpace(std::addressof(is_io), process_address, size,
                                                   ConvertToKMemoryPermission(device_perm),
                                                   is_aligned, true));

    // Ensure that if we fail, we don't keep unmapped pages locked.
    ON_RESULT_FAILURE {
        ASSERT(page_table->UnlockForDeviceAddressSpace(process_address, size) == ResultSuccess);
    };

    // Check that the io status is allowable.
    if (is_io) {
        R_UNLESS(static_cast<u32>(flags & Svc::MapDeviceAddressSpaceFlag::NotIoRegister) == 0,
                 ResultInvalidCombination);
    }

    // Map the pages.
    {
        // Perform the mapping.
        // R_TRY(m_table.Map(page_table, process_address, size, device_address, device_perm,
        //                   is_aligned, is_io));

        // Ensure that we unmap the pages if we fail to update the protections.
        // NOTE: Nintendo does not check the result of this unmap call.
        // ON_RESULT_FAILURE { m_table.Unmap(device_address, size); };

        // Update the protections in accordance with how much we mapped.
        // R_TRY(page_table->UnlockForDeviceAddressSpacePartialMap(process_address, size));
    }

    // We succeeded.
    R_SUCCEED();
}

Result KDeviceAddressSpace::Unmap(KProcessPageTable* page_table, KProcessAddress process_address,
                                  size_t size, u64 device_address) {
    // Check that the address falls within the space.
    R_UNLESS((m_space_address <= device_address &&
              device_address + size - 1 <= m_space_address + m_space_size - 1),
             ResultInvalidCurrentMemory);

    // Lock the address space.
    KScopedLightLock lk(m_lock);

    // Lock the page table to prevent concurrent device mapping operations.
    // KScopedLightLock pt_lk = page_table->AcquireDeviceMapLock();

    // Lock the pages.
    R_TRY(page_table->LockForUnmapDeviceAddressSpace(process_address, size, true));

    // Unmap the pages.
    {
        // If we fail to unmap, we want to do a partial unlock.
        // ON_RESULT_FAILURE {
        //     ASSERT(page_table->UnlockForDeviceAddressSpacePartialMap(process_address, size) ==
        //            ResultSuccess);
        // };

        // Perform the unmap.
        // R_TRY(m_table.Unmap(page_table, process_address, size, device_address));
    }

    // Unlock the pages.
    ASSERT(page_table->UnlockForDeviceAddressSpace(process_address, size) == ResultSuccess);

    R_SUCCEED();
}

} // namespace Kernel


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

#pragma once

#include <string>

#include "core/hle/kernel/k_process_page_table.h"
#include "core/hle/kernel/k_typed_address.h"
#include "core/hle/kernel/slab_helpers.h"
#include "core/hle/result.h"

namespace Kernel {

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

public:
    explicit KDeviceAddressSpace(KernelCore& kernel);
    ~KDeviceAddressSpace();

    Result Initialize(u64 address, u64 size);
    void Finalize() override;

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

    Result Attach(Svc::DeviceName device_name);
    Result Detach(Svc::DeviceName device_name);

    Result MapByForce(KProcessPageTable* page_table, KProcessAddress process_address, size_t size,
                      u64 device_address, u32 option) {
        R_RETURN(this->Map(page_table, process_address, size, device_address, option, false));
    }

    Result MapAligned(KProcessPageTable* page_table, KProcessAddress process_address, size_t size,
                      u64 device_address, u32 option) {
        R_RETURN(this->Map(page_table, process_address, size, device_address, option, true));
    }

    Result Unmap(KProcessPageTable* page_table, KProcessAddress process_address, size_t size,
                 u64 device_address);

    static void Initialize();

private:
    Result Map(KProcessPageTable* page_table, KProcessAddress process_address, size_t size,
               u64 device_address, u32 option, bool is_aligned);

private:
    KLightLock m_lock;
    // KDevicePageTable m_table;
    u64 m_space_address{};
    u64 m_space_size{};
    bool m_is_initialized{};
};

} // namespace Kernel


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

#pragma once

#include <vector>

#include "common/alignment.h"
#include "core/hle/kernel/k_page_bitmap.h"
#include "core/hle/kernel/k_spin_lock.h"
#include "core/hle/kernel/k_typed_address.h"
#include "core/hle/kernel/memory_types.h"
#include "core/hle/kernel/svc_results.h"

namespace Kernel {

class KDynamicPageManager {
public:
    class PageBuffer {
    private:
        u8 m_buffer[PageSize];
    };
    static_assert(sizeof(PageBuffer) == PageSize);

public:
    KDynamicPageManager() = default;

    template <typename T>
    T* GetPointer(KVirtualAddress addr) {
        return reinterpret_cast<T*>(m_backing_memory.data() + (addr - m_address));
    }

    template <typename T>
    const T* GetPointer(KVirtualAddress addr) const {
        return reinterpret_cast<T*>(m_backing_memory.data() + (addr - m_address));
    }

    Result Initialize(KVirtualAddress memory, size_t size, size_t align) {
        // We need to have positive size.
        R_UNLESS(size > 0, ResultOutOfMemory);
        m_backing_memory.resize(size);

        // Set addresses.
        m_address = memory;
        m_aligned_address = Common::AlignDown(GetInteger(memory), align);

        // Calculate extents.
        const size_t managed_size = m_address + size - m_aligned_address;
        const size_t overhead_size = Common::AlignUp(
            KPageBitmap::CalculateManagementOverheadSize(managed_size / sizeof(PageBuffer)),
            sizeof(PageBuffer));
        R_UNLESS(overhead_size < size, ResultOutOfMemory);

        // Set tracking fields.
        m_size = Common::AlignDown(size - overhead_size, sizeof(PageBuffer));
        m_count = m_size / sizeof(PageBuffer);

        // Clear the management region.
        u64* management_ptr = GetPointer<u64>(m_address + size - overhead_size);
        std::memset(management_ptr, 0, overhead_size);

        // Initialize the bitmap.
        const size_t allocatable_region_size =
            (m_address + size - overhead_size) - m_aligned_address;
        ASSERT(allocatable_region_size >= sizeof(PageBuffer));

        m_page_bitmap.Initialize(management_ptr, allocatable_region_size / sizeof(PageBuffer));

        // Free the pages to the bitmap.
        for (size_t i = 0; i < m_count; i++) {
            // Ensure the freed page is all-zero.
            std::memset(GetPointer<PageBuffer>(m_address) + i, 0, PageSize);

            // Set the bit for the free page.
            m_page_bitmap.SetBit((m_address + (i * sizeof(PageBuffer)) - m_aligned_address) /
                                 sizeof(PageBuffer));
        }

        R_SUCCEED();
    }

    KVirtualAddress GetAddress() const {
        return m_address;
    }
    size_t GetSize() const {
        return m_size;
    }
    size_t GetUsed() const {
        return m_used;
    }
    size_t GetPeak() const {
        return m_peak;
    }
    size_t GetCount() const {
        return m_count;
    }

    PageBuffer* Allocate() {
        // Take the lock.
        // TODO(bunnei): We should disable interrupts here via KScopedInterruptDisable.
        KScopedSpinLock lk(m_lock);

        // Find a random free block.
        s64 soffset = m_page_bitmap.FindFreeBlock(true);
        if (soffset < 0) [[unlikely]] {
            return nullptr;
        }

        const size_t offset = static_cast<size_t>(soffset);

        // Update our tracking.
        m_page_bitmap.ClearBit(offset);
        m_peak = std::max(m_peak, (++m_used));

        return GetPointer<PageBuffer>(m_aligned_address) + offset;
    }

    PageBuffer* Allocate(size_t count) {
        // Take the lock.
        // TODO(bunnei): We should disable interrupts here via KScopedInterruptDisable.
        KScopedSpinLock lk(m_lock);

        // Find a random free block.
        s64 soffset = m_page_bitmap.FindFreeRange(count);
        if (soffset < 0) [[likely]] {
            return nullptr;
        }

        const size_t offset = static_cast<size_t>(soffset);

        // Update our tracking.
        m_page_bitmap.ClearRange(offset, count);
        m_used += count;
        m_peak = std::max(m_peak, m_used);

        return GetPointer<PageBuffer>(m_aligned_address) + offset;
    }

    void Free(PageBuffer* pb) {
        // Ensure all pages in the heap are zero.
        std::memset(pb, 0, PageSize);

        // Take the lock.
        // TODO(bunnei): We should disable interrupts here via KScopedInterruptDisable.
        KScopedSpinLock lk(m_lock);

        // Set the bit for the free page.
        size_t offset =
            (reinterpret_cast<uint64_t>(pb) - GetInteger(m_aligned_address)) / sizeof(PageBuffer);
        m_page_bitmap.SetBit(offset);

        // Decrement our used count.
        --m_used;
    }

private:
    KSpinLock m_lock;
    KPageBitmap m_page_bitmap;
    size_t m_used{};
    size_t m_peak{};
    size_t m_count{};
    KVirtualAddress m_address{};
    KVirtualAddress m_aligned_address{};
    size_t m_size{};

    // TODO(bunnei): Back by host memory until we emulate kernel virtual address space.
    std::vector<u8> m_backing_memory;
};

} // namespace Kernel


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

#pragma once

#include "common/common_funcs.h"
#include "core/hle/kernel/k_dynamic_slab_heap.h"
#include "core/hle/kernel/k_memory_block.h"
#include "core/hle/kernel/k_page_group.h"

namespace Kernel {

template <typename T, bool ClearNode = false>
class KDynamicResourceManager {
    YUZU_NON_COPYABLE(KDynamicResourceManager);
    YUZU_NON_MOVEABLE(KDynamicResourceManager);

public:
    using DynamicSlabType = KDynamicSlabHeap<T, ClearNode>;

public:
    constexpr KDynamicResourceManager() = default;

    constexpr size_t GetSize() const {
        return m_slab_heap->GetSize();
    }
    constexpr size_t GetUsed() const {
        return m_slab_heap->GetUsed();
    }
    constexpr size_t GetPeak() const {
        return m_slab_heap->GetPeak();
    }
    constexpr size_t GetCount() const {
        return m_slab_heap->GetCount();
    }

    void Initialize(KDynamicPageManager* page_allocator, DynamicSlabType* slab_heap) {
        m_page_allocator = page_allocator;
        m_slab_heap = slab_heap;
    }

    T* Allocate() const {
        return m_slab_heap->Allocate(m_page_allocator);
    }

    void Free(T* t) const {
        m_slab_heap->Free(t);
    }

private:
    KDynamicPageManager* m_page_allocator{};
    DynamicSlabType* m_slab_heap{};
};

class KBlockInfoManager : public KDynamicResourceManager<KBlockInfo> {};
class KMemoryBlockSlabManager : public KDynamicResourceManager<KMemoryBlock> {};

using KBlockInfoSlabHeap = typename KBlockInfoManager::DynamicSlabType;
using KMemoryBlockSlabHeap = typename KMemoryBlockSlabManager::DynamicSlabType;

} // namespace Kernel


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

#pragma once

#include <atomic>

#include "common/common_funcs.h"
#include "core/hle/kernel/k_dynamic_page_manager.h"
#include "core/hle/kernel/k_slab_heap.h"

namespace Kernel {

template <typename T, bool ClearNode = false>
class KDynamicSlabHeap : protected impl::KSlabHeapImpl {
    YUZU_NON_COPYABLE(KDynamicSlabHeap);
    YUZU_NON_MOVEABLE(KDynamicSlabHeap);

public:
    constexpr KDynamicSlabHeap() = default;

    constexpr KVirtualAddress GetAddress() const {
        return m_address;
    }
    constexpr size_t GetSize() const {
        return m_size;
    }
    constexpr size_t GetUsed() const {
        return m_used.load();
    }
    constexpr size_t GetPeak() const {
        return m_peak.load();
    }
    constexpr size_t GetCount() const {
        return m_count.load();
    }

    constexpr bool IsInRange(KVirtualAddress addr) const {
        return this->GetAddress() <= addr && addr <= this->GetAddress() + this->GetSize() - 1;
    }

    void Initialize(KDynamicPageManager* page_allocator, size_t num_objects) {
        ASSERT(page_allocator != nullptr);

        // Initialize members.
        m_address = page_allocator->GetAddress();
        m_size = page_allocator->GetSize();

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

        // Allocate until we have the correct number of objects.
        while (m_count.load() < num_objects) {
            auto* allocated = reinterpret_cast<T*>(page_allocator->Allocate());
            ASSERT(allocated != nullptr);

            for (size_t i = 0; i < sizeof(PageBuffer) / sizeof(T); i++) {
                KSlabHeapImpl::Free(allocated + i);
            }

            m_count += sizeof(PageBuffer) / sizeof(T);
        }
    }

    T* Allocate(KDynamicPageManager* page_allocator) {
        T* allocated = static_cast<T*>(KSlabHeapImpl::Allocate());

        // If we successfully allocated and we should clear the node, do so.
        if constexpr (ClearNode) {
            if (allocated != nullptr) [[likely]] {
                reinterpret_cast<KSlabHeapImpl::Node*>(allocated)->next = nullptr;
            }
        }

        // If we fail to allocate, try to get a new page from our next allocator.
        if (allocated == nullptr) [[unlikely]] {
            if (page_allocator != nullptr) {
                allocated = reinterpret_cast<T*>(page_allocator->Allocate());
                if (allocated != nullptr) {
                    // If we succeeded in getting a page, free the rest to our slab.
                    for (size_t i = 1; i < sizeof(PageBuffer) / sizeof(T); i++) {
                        KSlabHeapImpl::Free(allocated + i);
                    }
                    m_count += sizeof(PageBuffer) / sizeof(T);
                }
            }
        }

        if (allocated != nullptr) [[likely]] {
            // Construct the object.
            std::construct_at(allocated);

            // Update our tracking.
            const size_t used = ++m_used;
            size_t peak = m_peak.load();
            while (peak < used) {
                if (m_peak.compare_exchange_weak(peak, used, std::memory_order_relaxed)) {
                    break;
                }
            }
        }

        return allocated;
    }

    void Free(T* t) {
        KSlabHeapImpl::Free(t);
        --m_used;
    }

private:
    using PageBuffer = KDynamicPageManager::PageBuffer;

private:
    std::atomic<size_t> m_used{};
    std::atomic<size_t> m_peak{};
    std::atomic<size_t> m_count{};
    KVirtualAddress m_address{};
    size_t m_size{};
};

} // namespace Kernel


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

#include "core/hle/kernel/k_event.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_resource_limit.h"

namespace Kernel {

KEvent::KEvent(KernelCore& kernel)
    : KAutoObjectWithSlabHeapAndContainer{kernel}, m_readable_event{kernel} {}

KEvent::~KEvent() = default;

void KEvent::Initialize(KProcess* owner) {
    // Create our readable event.
    KAutoObject::Create(std::addressof(m_readable_event));

    // Initialize our readable event.
    m_readable_event.Initialize(this);

    // Set our owner process.
    // HACK: this should never be nullptr, but service threads don't have a
    // proper parent process yet.
    if (owner != nullptr) {
        m_owner = owner;
        m_owner->Open();
    }

    // Mark initialized.
    m_initialized = true;
}

void KEvent::Finalize() {
    KAutoObjectWithSlabHeapAndContainer<KEvent, KAutoObjectWithList>::Finalize();
}

Result KEvent::Signal() {
    KScopedSchedulerLock sl{m_kernel};

    R_SUCCEED_IF(m_readable_event_destroyed);

    return m_readable_event.Signal();
}

Result KEvent::Clear() {
    KScopedSchedulerLock sl{m_kernel};

    R_SUCCEED_IF(m_readable_event_destroyed);

    return m_readable_event.Clear();
}

void KEvent::PostDestroy(uintptr_t arg) {
    // Release the event count resource the owner process holds.
    KProcess* owner = reinterpret_cast<KProcess*>(arg);

    if (owner != nullptr) {
        owner->GetResourceLimit()->Release(LimitableResource::EventCountMax, 1);
        owner->Close();
    }
}

} // namespace Kernel


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

#pragma once

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

namespace Kernel {

class KernelCore;
class KReadableEvent;
class KProcess;

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

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

    void Initialize(KProcess* owner);

    void Finalize() override;

    bool IsInitialized() const override {
        return m_initialized;
    }

    uintptr_t GetPostDestroyArgument() const override {
        return reinterpret_cast<uintptr_t>(m_owner);
    }

    KProcess* GetOwner() const override {
        return m_owner;
    }

    KReadableEvent& GetReadableEvent() {
        return m_readable_event;
    }

    static void PostDestroy(uintptr_t arg);

    Result Signal();
    Result Clear();

    void OnReadableEventDestroyed() {
        m_readable_event_destroyed = true;
    }

private:
    KReadableEvent m_readable_event;
    KProcess* m_owner{};
    bool m_initialized{};
    bool m_readable_event_destroyed{};
};

} // 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/slab_helpers.h"
#include "core/hle/kernel/svc_types.h"

namespace Kernel {

class KEventInfo : public KSlabAllocated<KEventInfo>,
                   public Common::IntrusiveListBaseNode<KEventInfo> {
public:
    struct InfoCreateThread {
        u32 thread_id{};
        uintptr_t tls_address{};
    };

    struct InfoExitProcess {
        Svc::ProcessExitReason reason{};
    };

    struct InfoExitThread {
        Svc::ThreadExitReason reason{};
    };

    struct InfoException {
        Svc::DebugException exception_type{};
        s32 exception_data_count{};
        uintptr_t exception_address{};
        std::array<uintptr_t, 4> exception_data{};
    };

    struct InfoSystemCall {
        s64 tick{};
        s32 id{};
    };

public:
    KEventInfo() = default;
    ~KEventInfo() = default;

public:
    Svc::DebugEvent event{};
    u32 thread_id{};
    u32 flags{};
    bool is_attached{};
    bool continue_flag{};
    bool ignore_continue{};
    bool close_once{};
    union {
        InfoCreateThread create_thread;
        InfoExitProcess exit_process;
        InfoExitThread exit_thread;
        InfoException exception;
        InfoSystemCall system_call;
    } info{};
    KThread* debug_thread{};
};

} // namespace Kernel


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

#include "core/hle/kernel/k_handle_table.h"
#include "core/hle/kernel/k_process.h"

namespace Kernel {

Result KHandleTable::Finalize() {
    // Get the table and clear our record of it.
    u16 saved_table_size = 0;
    {
        KScopedDisableDispatch dd{m_kernel};
        KScopedSpinLock lk(m_lock);

        std::swap(m_table_size, saved_table_size);
    }

    // Close and free all entries.
    for (size_t i = 0; i < saved_table_size; i++) {
        if (KAutoObject* obj = m_objects[i]; obj != nullptr) {
            obj->Close();
        }
    }

    R_SUCCEED();
}

bool KHandleTable::Remove(Handle handle) {
    // Don't allow removal of a pseudo-handle.
    if (Svc::IsPseudoHandle(handle)) [[unlikely]] {
        return false;
    }

    // Handles must not have reserved bits set.
    const auto handle_pack = HandlePack(handle);
    if (handle_pack.reserved != 0) [[unlikely]] {
        return false;
    }

    // Find the object and free the entry.
    KAutoObject* obj = nullptr;
    {
        KScopedDisableDispatch dd{m_kernel};
        KScopedSpinLock lk(m_lock);

        if (this->IsValidHandle(handle)) [[likely]] {
            const auto index = handle_pack.index;

            obj = m_objects[index];
            this->FreeEntry(index);
        } else {
            return false;
        }
    }

    // Close the object.
    m_kernel.UnregisterInUseObject(obj);
    obj->Close();
    return true;
}

Result KHandleTable::Add(Handle* out_handle, KAutoObject* obj) {
    KScopedDisableDispatch dd{m_kernel};
    KScopedSpinLock lk(m_lock);

    // Never exceed our capacity.
    R_UNLESS(m_count < m_table_size, ResultOutOfHandles);

    // Allocate entry, set output handle.
    {
        const auto linear_id = this->AllocateLinearId();
        const auto index = this->AllocateEntry();

        m_entry_infos[index].linear_id = linear_id;
        m_objects[index] = obj;

        obj->Open();

        *out_handle = EncodeHandle(static_cast<u16>(index), linear_id);
    }

    R_SUCCEED();
}

KScopedAutoObject<KAutoObject> KHandleTable::GetObjectForIpc(Handle handle,
                                                             KThread* cur_thread) const {
    // Handle pseudo-handles.
    ASSERT(cur_thread != nullptr);
    if (handle == Svc::PseudoHandle::CurrentProcess) {
        auto* const cur_process = cur_thread->GetOwnerProcess();
        ASSERT(cur_process != nullptr);
        return cur_process;
    }
    if (handle == Svc::PseudoHandle::CurrentThread) {
        return cur_thread;
    }

    return GetObjectForIpcWithoutPseudoHandle(handle);
}

Result KHandleTable::Reserve(Handle* out_handle) {
    KScopedDisableDispatch dd{m_kernel};
    KScopedSpinLock lk(m_lock);

    // Never exceed our capacity.
    R_UNLESS(m_count < m_table_size, ResultOutOfHandles);

    *out_handle = EncodeHandle(static_cast<u16>(this->AllocateEntry()), this->AllocateLinearId());
    R_SUCCEED();
}

void KHandleTable::Unreserve(Handle handle) {
    KScopedDisableDispatch dd{m_kernel};
    KScopedSpinLock lk(m_lock);

    // Unpack the handle.
    const auto handle_pack = HandlePack(handle);
    const auto index = handle_pack.index;
    const auto linear_id = handle_pack.linear_id;
    const auto reserved = handle_pack.reserved;
    ASSERT(reserved == 0);
    ASSERT(linear_id != 0);

    if (index < m_table_size) [[likely]] {
        // NOTE: This code does not check the linear id.
        ASSERT(m_objects[index] == nullptr);
        this->FreeEntry(index);
    }
}

void KHandleTable::Register(Handle handle, KAutoObject* obj) {
    KScopedDisableDispatch dd{m_kernel};
    KScopedSpinLock lk(m_lock);

    // Unpack the handle.
    const auto handle_pack = HandlePack(handle);
    const auto index = handle_pack.index;
    const auto linear_id = handle_pack.linear_id;
    const auto reserved = handle_pack.reserved;
    ASSERT(reserved == 0);
    ASSERT(linear_id != 0);

    if (index < m_table_size) [[likely]] {
        // Set the entry.
        ASSERT(m_objects[index] == nullptr);

        m_entry_infos[index].linear_id = static_cast<u16>(linear_id);
        m_objects[index] = obj;

        obj->Open();
    }
}

} // namespace Kernel


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

#pragma once

#include <array>

#include "common/assert.h"
#include "common/bit_field.h"
#include "common/common_types.h"
#include "core/hle/kernel/k_auto_object.h"
#include "core/hle/kernel/k_spin_lock.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/svc_common.h"
#include "core/hle/kernel/svc_results.h"
#include "core/hle/result.h"

namespace Kernel {

class KernelCore;

class KHandleTable {
    YUZU_NON_COPYABLE(KHandleTable);
    YUZU_NON_MOVEABLE(KHandleTable);

public:
    static constexpr size_t MaxTableSize = 1024;

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

    Result Initialize(s32 size) {
        // Check that the table size is valid.
        R_UNLESS(size <= static_cast<s32>(MaxTableSize), ResultOutOfMemory);

        // Lock.
        KScopedDisableDispatch dd{m_kernel};
        KScopedSpinLock lk(m_lock);

        // Initialize all fields.
        m_max_count = 0;
        m_table_size = static_cast<s16>((size <= 0) ? MaxTableSize : size);
        m_next_linear_id = MinLinearId;
        m_count = 0;
        m_free_head_index = -1;

        // Free all entries.
        for (s32 i = 0; i < static_cast<s32>(m_table_size); ++i) {
            m_objects[i] = nullptr;
            m_entry_infos[i].next_free_index = static_cast<s16>(i - 1);
            m_free_head_index = i;
        }

        R_SUCCEED();
    }

    size_t GetTableSize() const {
        return m_table_size;
    }
    size_t GetCount() const {
        return m_count;
    }
    size_t GetMaxCount() const {
        return m_max_count;
    }

    Result Finalize();
    bool Remove(Handle handle);

    template <typename T = KAutoObject>
    KScopedAutoObject<T> GetObjectWithoutPseudoHandle(Handle handle) const {
        // Lock and look up in table.
        KScopedDisableDispatch dd{m_kernel};
        KScopedSpinLock lk(m_lock);

        if constexpr (std::is_same_v<T, KAutoObject>) {
            return this->GetObjectImpl(handle);
        } else {
            if (auto* obj = this->GetObjectImpl(handle); obj != nullptr) [[likely]] {
                return obj->DynamicCast<T*>();
            } else {
                return nullptr;
            }
        }
    }

    template <typename T = KAutoObject>
    KScopedAutoObject<T> GetObject(Handle handle) const {
        // Handle pseudo-handles.
        if constexpr (std::derived_from<KProcess, T>) {
            if (handle == Svc::PseudoHandle::CurrentProcess) {
                auto* const cur_process = GetCurrentProcessPointer(m_kernel);
                ASSERT(cur_process != nullptr);
                return cur_process;
            }
        } else if constexpr (std::derived_from<KThread, T>) {
            if (handle == Svc::PseudoHandle::CurrentThread) {
                auto* const cur_thread = GetCurrentThreadPointer(m_kernel);
                ASSERT(cur_thread != nullptr);
                return cur_thread;
            }
        }

        return this->template GetObjectWithoutPseudoHandle<T>(handle);
    }

    KScopedAutoObject<KAutoObject> GetObjectForIpcWithoutPseudoHandle(Handle handle) const {
        // Lock and look up in table.
        KScopedDisableDispatch dd{m_kernel};
        KScopedSpinLock lk(m_lock);

        return this->GetObjectImpl(handle);
    }

    KScopedAutoObject<KAutoObject> GetObjectForIpc(Handle handle, KThread* cur_thread) const;

    KScopedAutoObject<KAutoObject> GetObjectByIndex(Handle* out_handle, size_t index) const {
        KScopedDisableDispatch dd{m_kernel};
        KScopedSpinLock lk(m_lock);

        return this->GetObjectByIndexImpl(out_handle, index);
    }

    Result Reserve(Handle* out_handle);
    void Unreserve(Handle handle);

    Result Add(Handle* out_handle, KAutoObject* obj);
    void Register(Handle handle, KAutoObject* obj);

    template <typename T>
    bool GetMultipleObjects(T** out, const Handle* handles, size_t num_handles) const {
        // Try to convert and open all the handles.
        size_t num_opened;
        {
            // Lock the table.
            KScopedDisableDispatch dd{m_kernel};
            KScopedSpinLock lk(m_lock);
            for (num_opened = 0; num_opened < num_handles; num_opened++) {
                // Get the current handle.
                const auto cur_handle = handles[num_opened];

                // Get the object for the current handle.
                KAutoObject* cur_object = this->GetObjectImpl(cur_handle);
                if (cur_object == nullptr) [[unlikely]] {
                    break;
                }

                // Cast the current object to the desired type.
                T* cur_t = cur_object->DynamicCast<T*>();
                if (cur_t == nullptr) [[unlikely]] {
                    break;
                }

                // Open a reference to the current object.
                cur_t->Open();
                out[num_opened] = cur_t;
            }
        }

        // If we converted every object, succeed.
        if (num_opened == num_handles) [[likely]] {
            return true;
        }

        // If we didn't convert entry object, close the ones we opened.
        for (size_t i = 0; i < num_opened; i++) {
            out[i]->Close();
        }

        return false;
    }

private:
    s32 AllocateEntry() {
        ASSERT(m_count < m_table_size);

        const auto index = m_free_head_index;

        m_free_head_index = m_entry_infos[index].GetNextFreeIndex();

        m_max_count = std::max(m_max_count, ++m_count);

        return index;
    }

    void FreeEntry(s32 index) {
        ASSERT(m_count > 0);

        m_objects[index] = nullptr;
        m_entry_infos[index].next_free_index = static_cast<s16>(m_free_head_index);

        m_free_head_index = index;

        --m_count;
    }

    u16 AllocateLinearId() {
        const u16 id = m_next_linear_id++;
        if (m_next_linear_id > MaxLinearId) {
            m_next_linear_id = MinLinearId;
        }
        return id;
    }

    bool IsValidHandle(Handle handle) const {
        // Unpack the handle.
        const auto handle_pack = HandlePack(handle);
        const auto raw_value = handle_pack.raw;
        const auto index = handle_pack.index;
        const auto linear_id = handle_pack.linear_id;
        const auto reserved = handle_pack.reserved;
        ASSERT(reserved == 0);

        // Validate our indexing information.
        if (raw_value == 0) [[unlikely]] {
            return false;
        }
        if (linear_id == 0) [[unlikely]] {
            return false;
        }
        if (index >= m_table_size) [[unlikely]] {
            return false;
        }

        // Check that there's an object, and our serial id is correct.
        if (m_objects[index] == nullptr) [[unlikely]] {
            return false;
        }
        if (m_entry_infos[index].GetLinearId() != linear_id) [[unlikely]] {
            return false;
        }

        return true;
    }

    KAutoObject* GetObjectImpl(Handle handle) const {
        // Handles must not have reserved bits set.
        const auto handle_pack = HandlePack(handle);
        if (handle_pack.reserved != 0) [[unlikely]] {
            return nullptr;
        }

        if (this->IsValidHandle(handle)) [[likely]] {
            return m_objects[handle_pack.index];
        } else {
            return nullptr;
        }
    }

    KAutoObject* GetObjectByIndexImpl(Handle* out_handle, size_t index) const {
        // Index must be in bounds.
        if (index >= m_table_size) [[unlikely]] {
            return nullptr;
        }

        // Ensure entry has an object.
        if (KAutoObject* obj = m_objects[index]; obj != nullptr) {
            *out_handle = EncodeHandle(static_cast<u16>(index), m_entry_infos[index].GetLinearId());
            return obj;
        } else {
            return nullptr;
        }
    }

private:
    union HandlePack {
        constexpr HandlePack() = default;
        constexpr HandlePack(Handle handle) : raw{static_cast<u32>(handle)} {}

        u32 raw{};
        BitField<0, 15, u32> index;
        BitField<15, 15, u32> linear_id;
        BitField<30, 2, u32> reserved;
    };

    static constexpr Handle EncodeHandle(u16 index, u16 linear_id) {
        HandlePack handle{};
        handle.index.Assign(index);
        handle.linear_id.Assign(linear_id);
        handle.reserved.Assign(0);
        return handle.raw;
    }

private:
    static constexpr u16 MinLinearId = 1;
    static constexpr u16 MaxLinearId = 0x7FFF;

    union EntryInfo {
        u16 linear_id;
        s16 next_free_index;

        constexpr u16 GetLinearId() const {
            return linear_id;
        }
        constexpr s32 GetNextFreeIndex() const {
            return next_free_index;
        }
    };

private:
    KernelCore& m_kernel;
    std::array<EntryInfo, MaxTableSize> m_entry_infos{};
    std::array<KAutoObject*, MaxTableSize> m_objects{};
    mutable KSpinLock m_lock;
    s32 m_free_head_index{};
    u16 m_table_size{};
    u16 m_max_count{};
    u16 m_next_linear_id{};
    u16 m_count{};
};

} // namespace Kernel


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

#include "core/core.h"
#include "core/core_timing.h"
#include "core/hle/kernel/k_hardware_timer.h"
#include "core/hle/kernel/k_scheduler.h"

namespace Kernel {

void KHardwareTimer::Initialize() {
    // Create the timing callback to register with CoreTiming.
    m_event_type = Core::Timing::CreateEvent("KHardwareTimer::Callback",
                                             [this](s64, std::chrono::nanoseconds) {
                                                 this->DoTask();
                                                 return std::nullopt;
                                             });
}

void KHardwareTimer::Finalize() {
    m_kernel.System().CoreTiming().UnscheduleEvent(m_event_type);
    m_wakeup_time = std::numeric_limits<s64>::max();
    m_event_type.reset();
}

void KHardwareTimer::DoTask() {
    // Handle the interrupt.
    {
        KScopedSchedulerLock slk{m_kernel};
        KScopedSpinLock lk(this->GetLock());

        //! Ignore this event if needed.
        if (!this->GetInterruptEnabled()) {
            return;
        }

        // Disable the timer interrupt while we handle this.
        // Not necessary due to core timing already having popped this event to call it.
        // this->DisableInterrupt();
        m_wakeup_time = std::numeric_limits<s64>::max();

        if (const s64 next_time = this->DoInterruptTaskImpl(GetTick());
            0 < next_time && next_time <= m_wakeup_time) {
            // We have a next time, so we should set the time to interrupt and turn the interrupt
            // on.
            this->EnableInterrupt(next_time);
        }
    }

    // Clear the timer interrupt.
    // Kernel::GetInterruptManager().ClearInterrupt(KInterruptName_NonSecurePhysicalTimer,
    //                                              GetCurrentCoreId());
}

void KHardwareTimer::EnableInterrupt(s64 wakeup_time) {
    this->DisableInterrupt();

    m_wakeup_time = wakeup_time;
    m_kernel.System().CoreTiming().ScheduleEvent(std::chrono::nanoseconds{m_wakeup_time},
                                                 m_event_type, true);
}

void KHardwareTimer::DisableInterrupt() {
    m_kernel.System().CoreTiming().UnscheduleEvent(m_event_type,
                                                   Core::Timing::UnscheduleEventType::NoWait);
    m_wakeup_time = std::numeric_limits<s64>::max();
}

s64 KHardwareTimer::GetTick() const {
    return m_kernel.System().CoreTiming().GetGlobalTimeNs().count();
}

bool KHardwareTimer::GetInterruptEnabled() {
    return m_wakeup_time != std::numeric_limits<s64>::max();
}

} // namespace Kernel


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

#pragma once

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

namespace Core::Timing {
struct EventType;
} // namespace Core::Timing

namespace Kernel {

class KHardwareTimer : /* public KInterruptTask, */ public KHardwareTimerBase {
public:
    explicit KHardwareTimer(KernelCore& kernel) : KHardwareTimerBase{kernel} {}

    // Public API.
    void Initialize();
    void Finalize();

    s64 GetTick() const;

    void RegisterAbsoluteTask(KTimerTask* task, s64 task_time) {
        KScopedDisableDispatch dd{m_kernel};
        KScopedSpinLock lk{this->GetLock()};

        if (this->RegisterAbsoluteTaskImpl(task, task_time)) {
            if (task_time <= m_wakeup_time) {
                this->EnableInterrupt(task_time);
            }
        }
    }

private:
    void EnableInterrupt(s64 wakeup_time);
    void DisableInterrupt();
    bool GetInterruptEnabled();
    void DoTask();

private:
    // Absolute time in nanoseconds
    s64 m_wakeup_time{std::numeric_limits<s64>::max()};
    std::shared_ptr<Core::Timing::EventType> m_event_type{};
};

} // namespace Kernel


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

#pragma once

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

namespace Kernel {

class KHardwareTimerBase {
public:
    explicit KHardwareTimerBase(KernelCore& kernel) : m_kernel{kernel} {}

    void CancelTask(KTimerTask* task) {
        KScopedDisableDispatch dd{m_kernel};
        KScopedSpinLock lk{m_lock};

        if (const s64 task_time = task->GetTime(); task_time > 0) {
            this->RemoveTaskFromTree(task);
        }
    }

protected:
    KSpinLock& GetLock() {
        return m_lock;
    }

    s64 DoInterruptTaskImpl(s64 cur_time) {
        // We want to handle all tasks, returning the next time that a task is scheduled.
        while (true) {
            // Get the next task. If there isn't one, return 0.
            KTimerTask* task = m_next_task;
            if (task == nullptr) {
                return 0;
            }

            // If the task needs to be done in the future, do it in the future and not now.
            if (const s64 task_time = task->GetTime(); task_time > cur_time) {
                return task_time;
            }

            // Remove the task from the tree of tasks, and update our next task.
            this->RemoveTaskFromTree(task);

            // Handle the task.
            task->OnTimer();
        }
    }

    bool RegisterAbsoluteTaskImpl(KTimerTask* task, s64 task_time) {
        ASSERT(task_time > 0);

        // Set the task's time, and insert it into our tree.
        task->SetTime(task_time);
        m_task_tree.insert(*task);

        // Update our next task if relevant.
        if (m_next_task != nullptr && m_next_task->GetTime() <= task_time) {
            return false;
        }
        m_next_task = task;
        return true;
    }

private:
    void RemoveTaskFromTree(KTimerTask* task) {
        // Erase from the tree.
        auto it = m_task_tree.erase(m_task_tree.iterator_to(*task));

        // Clear the task's scheduled time.
        task->SetTime(0);

        // Update our next task if relevant.
        if (m_next_task == task) {
            m_next_task = (it != m_task_tree.end()) ? std::addressof(*it) : nullptr;
        }
    }

protected:
    KernelCore& m_kernel;

private:
    using TimerTaskTree = Common::IntrusiveRedBlackTreeBaseTraits<KTimerTask>::TreeType<KTimerTask>;

    KSpinLock m_lock{};
    TimerTaskTree m_task_tree{};
    KTimerTask* m_next_task{};
};

} // namespace Kernel


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

#include "core/hle/kernel/k_interrupt_manager.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/physical_core.h"

namespace Kernel::KInterruptManager {

void HandleInterrupt(KernelCore& kernel, s32 core_id) {
    // Acknowledge the interrupt.
    kernel.PhysicalCore(core_id).ClearInterrupt();

    auto& current_thread = GetCurrentThread(kernel);

    if (auto* process = GetCurrentProcessPointer(kernel); process) {
        // If the user disable count is set, we may need to pin the current thread.
        if (current_thread.GetUserDisableCount() && !process->GetPinnedThread(core_id)) {
            KScopedSchedulerLock sl{kernel};

            // Pin the current thread.
            process->PinCurrentThread();

            // Set the interrupt flag for the thread.
            GetCurrentThread(kernel).SetInterruptFlag();
        }
    }

    // Request interrupt scheduling.
    kernel.CurrentScheduler()->RequestScheduleOnInterrupt();
}

void SendInterProcessorInterrupt(KernelCore& kernel, u64 core_mask) {
    for (std::size_t core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; ++core_id) {
        if (core_mask & (1ULL << core_id)) {
            kernel.PhysicalCore(core_id).Interrupt();
        }
    }
}

} // namespace Kernel::KInterruptManager


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

#pragma once

#include "common/common_types.h"

namespace Kernel {

class KernelCore;

namespace KInterruptManager {
void HandleInterrupt(KernelCore& kernel, s32 core_id);
void SendInterProcessorInterrupt(KernelCore& kernel, u64 core_mask);

} // namespace KInterruptManager

} // namespace Kernel


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

#include "core/hle/kernel/k_light_client_session.h"
#include "core/hle/kernel/k_light_session.h"
#include "core/hle/kernel/k_thread.h"

namespace Kernel {

KLightClientSession::KLightClientSession(KernelCore& kernel) : KAutoObject(kernel) {}

KLightClientSession::~KLightClientSession() = default;

void KLightClientSession::Destroy() {
    m_parent->OnClientClosed();
}

void KLightClientSession::OnServerClosed() {}

Result KLightClientSession::SendSyncRequest(u32* data) {
    // Get the request thread.
    KThread* cur_thread = GetCurrentThreadPointer(m_kernel);

    // Set the light data.
    cur_thread->SetLightSessionData(data);

    // Send the request.
    R_RETURN(m_parent->OnRequest(cur_thread));
}

} // namespace Kernel


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

#pragma once

#include "core/hle/kernel/k_auto_object.h"
#include "core/hle/result.h"

namespace Kernel {

class KLightSession;

class KLightClientSession final : public KAutoObject {
    KERNEL_AUTOOBJECT_TRAITS(KLightClientSession, KAutoObject);

public:
    explicit KLightClientSession(KernelCore& kernel);
    ~KLightClientSession();

    void Initialize(KLightSession* parent) {
        // Set member variables.
        m_parent = parent;
    }

    virtual void Destroy() override;

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

    Result SendSyncRequest(u32* data);

    void OnServerClosed();

private:
    KLightSession* m_parent;
};

} // namespace Kernel


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

#include "core/hle/kernel/k_light_condition_variable.h"
#include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h"
#include "core/hle/kernel/k_thread_queue.h"
#include "core/hle/kernel/svc_results.h"

namespace Kernel {

namespace {

class ThreadQueueImplForKLightConditionVariable final : public KThreadQueue {
public:
    ThreadQueueImplForKLightConditionVariable(KernelCore& kernel, KThread::WaiterList* wl,
                                              bool term)
        : KThreadQueue(kernel), m_wait_list(wl), m_allow_terminating_thread(term) {}

    void CancelWait(KThread* waiting_thread, Result wait_result, bool cancel_timer_task) override {
        // Only process waits if we're allowed to.
        if (ResultTerminationRequested == wait_result && m_allow_terminating_thread) {
            return;
        }

        // Remove the thread from the waiting thread from the light condition variable.
        m_wait_list->erase(m_wait_list->iterator_to(*waiting_thread));

        // Invoke the base cancel wait handler.
        KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task);
    }

private:
    KThread::WaiterList* m_wait_list;
    bool m_allow_terminating_thread;
};

} // namespace

void KLightConditionVariable::Wait(KLightLock* lock, s64 timeout, bool allow_terminating_thread) {
    // Create thread queue.
    KThread* owner = GetCurrentThreadPointer(m_kernel);
    KHardwareTimer* timer{};

    ThreadQueueImplForKLightConditionVariable wait_queue(m_kernel, std::addressof(m_wait_list),
                                                         allow_terminating_thread);

    // Sleep the thread.
    {
        KScopedSchedulerLockAndSleep lk(m_kernel, std::addressof(timer), owner, timeout);

        if (!allow_terminating_thread && owner->IsTerminationRequested()) {
            lk.CancelSleep();
            return;
        }

        lock->Unlock();

        // Add the thread to the queue.
        m_wait_list.push_back(*owner);

        // Begin waiting.
        wait_queue.SetHardwareTimer(timer);
        owner->BeginWait(std::addressof(wait_queue));
    }

    // Re-acquire the lock.
    lock->Lock();
}

void KLightConditionVariable::Broadcast() {
    KScopedSchedulerLock lk(m_kernel);

    // Signal all threads.
    for (auto it = m_wait_list.begin(); it != m_wait_list.end(); it = m_wait_list.erase(it)) {
        it->EndWait(ResultSuccess);
    }
}

} // namespace Kernel


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

#pragma once

#include "common/common_types.h"
#include "core/hle/kernel/k_thread.h"

namespace Kernel {

class KernelCore;
class KLightLock;

class KLightConditionVariable {
public:
    explicit KLightConditionVariable(KernelCore& kernel) : m_kernel{kernel} {}

    void Wait(KLightLock* lock, s64 timeout = -1, bool allow_terminating_thread = true);
    void Broadcast();

private:
    KernelCore& m_kernel;
    KThread::WaiterList m_wait_list{};
};
} // namespace Kernel


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

#include "core/hle/kernel/k_light_lock.h"
#include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/k_thread_queue.h"
#include "core/hle/kernel/kernel.h"

namespace Kernel {

namespace {

class ThreadQueueImplForKLightLock final : public KThreadQueue {
public:
    explicit ThreadQueueImplForKLightLock(KernelCore& kernel) : KThreadQueue(kernel) {}

    void CancelWait(KThread* waiting_thread, Result wait_result, bool cancel_timer_task) override {
        // Remove the thread as a waiter from its owner.
        if (KThread* owner = waiting_thread->GetLockOwner(); owner != nullptr) {
            owner->RemoveWaiter(waiting_thread);
        }

        // Invoke the base cancel wait handler.
        KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task);
    }
};

} // namespace

void KLightLock::Lock() {
    const uintptr_t cur_thread = reinterpret_cast<uintptr_t>(GetCurrentThreadPointer(m_kernel));

    while (true) {
        uintptr_t old_tag = m_tag.load(std::memory_order_relaxed);

        while (!m_tag.compare_exchange_weak(old_tag, (old_tag == 0) ? cur_thread : (old_tag | 1),
                                            std::memory_order_acquire)) {
        }

        if (old_tag == 0 || this->LockSlowPath(old_tag | 1, cur_thread)) {
            break;
        }
    }
}

void KLightLock::Unlock() {
    const uintptr_t cur_thread = reinterpret_cast<uintptr_t>(GetCurrentThreadPointer(m_kernel));

    uintptr_t expected = cur_thread;
    if (!m_tag.compare_exchange_strong(expected, 0, std::memory_order_release)) {
        this->UnlockSlowPath(cur_thread);
    }
}

bool KLightLock::LockSlowPath(uintptr_t _owner, uintptr_t _cur_thread) {
    KThread* cur_thread = reinterpret_cast<KThread*>(_cur_thread);
    ThreadQueueImplForKLightLock wait_queue(m_kernel);

    // Pend the current thread waiting on the owner thread.
    {
        KScopedSchedulerLock sl{m_kernel};

        // Ensure we actually have locking to do.
        if (m_tag.load(std::memory_order_relaxed) != _owner) {
            return false;
        }

        // Add the current thread as a waiter on the owner.
        KThread* owner_thread = reinterpret_cast<KThread*>(_owner & ~1ULL);
        cur_thread->SetKernelAddressKey(reinterpret_cast<uintptr_t>(std::addressof(m_tag)));
        owner_thread->AddWaiter(cur_thread);

        // Begin waiting to hold the lock.
        cur_thread->BeginWait(std::addressof(wait_queue));

        if (owner_thread->IsSuspended()) {
            owner_thread->ContinueIfHasKernelWaiters();
        }
    }

    return true;
}

void KLightLock::UnlockSlowPath(uintptr_t _cur_thread) {
    KThread* owner_thread = reinterpret_cast<KThread*>(_cur_thread);

    // Unlock.
    {
        KScopedSchedulerLock sl(m_kernel);

        // Get the next owner.
        bool has_waiters;
        KThread* next_owner = owner_thread->RemoveKernelWaiterByKey(
            std::addressof(has_waiters), reinterpret_cast<uintptr_t>(std::addressof(m_tag)));

        // Pass the lock to the next owner.
        uintptr_t next_tag = 0;
        if (next_owner != nullptr) {
            next_tag =
                reinterpret_cast<uintptr_t>(next_owner) | static_cast<uintptr_t>(has_waiters);

            next_owner->EndWait(ResultSuccess);

            if (next_owner->IsSuspended()) {
                next_owner->ContinueIfHasKernelWaiters();
            }
        }

        // We may have unsuspended in the process of acquiring the lock, so we'll re-suspend now if
        // so.
        if (owner_thread->IsSuspended()) {
            owner_thread->TrySuspend();
        }

        // Write the new tag value.
        m_tag.store(next_tag, std::memory_order_release);
    }
}

bool KLightLock::IsLockedByCurrentThread() const {
    return (m_tag.load() | 1ULL) ==
           (reinterpret_cast<uintptr_t>(GetCurrentThreadPointer(m_kernel)) | 1ULL);
}

} // namespace Kernel


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

#pragma once

#include <atomic>

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

namespace Kernel {

class KernelCore;

class KLightLock {
public:
    explicit KLightLock(KernelCore& kernel) : m_kernel{kernel} {}

    void Lock();

    void Unlock();

    bool LockSlowPath(uintptr_t owner, uintptr_t cur_thread);

    void UnlockSlowPath(uintptr_t cur_thread);

    bool IsLocked() const {
        return m_tag.load() != 0;
    }

    bool IsLockedByCurrentThread() const;

private:
    std::atomic<uintptr_t> m_tag{};
    KernelCore& m_kernel;
};

using KScopedLightLock = KScopedLock<KLightLock>;

} // namespace Kernel


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

#include "core/hle/kernel/k_light_server_session.h"
#include "core/hle/kernel/k_light_session.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/k_thread_queue.h"
#include "core/hle/kernel/svc_results.h"

namespace Kernel {

namespace {

constexpr u64 InvalidThreadId = std::numeric_limits<u64>::max();

class ThreadQueueImplForKLightServerSessionRequest final : public KThreadQueue {
private:
    KThread::WaiterList* m_wait_list;

public:
    ThreadQueueImplForKLightServerSessionRequest(KernelCore& kernel, KThread::WaiterList* wl)
        : KThreadQueue(kernel), m_wait_list(wl) {}

    virtual void EndWait(KThread* waiting_thread, Result wait_result) override {
        // Remove the thread from our wait list.
        m_wait_list->erase(m_wait_list->iterator_to(*waiting_thread));

        // Invoke the base end wait handler.
        KThreadQueue::EndWait(waiting_thread, wait_result);
    }

    virtual void CancelWait(KThread* waiting_thread, Result wait_result,
                            bool cancel_timer_task) override {
        // Remove the thread from our wait list.
        m_wait_list->erase(m_wait_list->iterator_to(*waiting_thread));

        // Invoke the base cancel wait handler.
        KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task);
    }
};

class ThreadQueueImplForKLightServerSessionReceive final : public KThreadQueue {
private:
    KThread** m_server_thread;

public:
    ThreadQueueImplForKLightServerSessionReceive(KernelCore& kernel, KThread** st)
        : KThreadQueue(kernel), m_server_thread(st) {}

    virtual void EndWait(KThread* waiting_thread, Result wait_result) override {
        // Clear the server thread.
        *m_server_thread = nullptr;

        // Set the waiting thread as not cancelable.
        waiting_thread->ClearCancellable();

        // Invoke the base end wait handler.
        KThreadQueue::EndWait(waiting_thread, wait_result);
    }

    virtual void CancelWait(KThread* waiting_thread, Result wait_result,
                            bool cancel_timer_task) override {
        // Clear the server thread.
        *m_server_thread = nullptr;

        // Set the waiting thread as not cancelable.
        waiting_thread->ClearCancellable();

        // Invoke the base cancel wait handler.
        KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task);
    }
};

} // namespace

KLightServerSession::KLightServerSession(KernelCore& kernel) : KAutoObject(kernel) {}
KLightServerSession::~KLightServerSession() = default;

void KLightServerSession::Destroy() {
    this->CleanupRequests();

    m_parent->OnServerClosed();
}

void KLightServerSession::OnClientClosed() {
    this->CleanupRequests();
}

Result KLightServerSession::OnRequest(KThread* request_thread) {
    ThreadQueueImplForKLightServerSessionRequest wait_queue(m_kernel,
                                                            std::addressof(m_request_list));

    // Send the request.
    {
        // Lock the scheduler.
        KScopedSchedulerLock sl(m_kernel);

        // Check that the server isn't closed.
        R_UNLESS(!m_parent->IsServerClosed(), ResultSessionClosed);

        // Check that the request thread isn't terminating.
        R_UNLESS(!request_thread->IsTerminationRequested(), ResultTerminationRequested);

        // Add the request thread to our list.
        m_request_list.push_back(*request_thread);

        // Begin waiting on the request.
        request_thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::IPC);
        request_thread->BeginWait(std::addressof(wait_queue));

        // If we have a server thread, end its wait.
        if (m_server_thread != nullptr) {
            m_server_thread->EndWait(ResultSuccess);
        }
    }

    // NOTE: Nintendo returns GetCurrentThread().GetWaitResult() here.
    // This is technically incorrect, although it doesn't cause problems in practice
    // because this is only ever called with request_thread = GetCurrentThreadPointer().
    R_RETURN(request_thread->GetWaitResult());
}

Result KLightServerSession::ReplyAndReceive(u32* data) {
    // Set the server context.
    GetCurrentThread(m_kernel).SetLightSessionData(data);

    // Reply, if we need to.
    if (data[0] & KLightSession::ReplyFlag) {
        KScopedSchedulerLock sl(m_kernel);

        // Check that we're open.
        R_UNLESS(!m_parent->IsClientClosed(), ResultSessionClosed);
        R_UNLESS(!m_parent->IsServerClosed(), ResultSessionClosed);

        // Check that we have a request to reply to.
        R_UNLESS(m_current_request != nullptr, ResultInvalidState);

        // Check that the server thread id is correct.
        R_UNLESS(m_server_thread_id == GetCurrentThread(m_kernel).GetId(), ResultInvalidState);

        // If we can reply, do so.
        if (!m_current_request->IsTerminationRequested()) {
            std::memcpy(m_current_request->GetLightSessionData(),
                        GetCurrentThread(m_kernel).GetLightSessionData(), KLightSession::DataSize);
            m_current_request->EndWait(ResultSuccess);
        }

        // Close our current request.
        m_current_request->Close();

        // Clear our current request.
        m_current_request = nullptr;
        m_server_thread_id = InvalidThreadId;
    }

    // Create the wait queue for our receive.
    ThreadQueueImplForKLightServerSessionReceive wait_queue(m_kernel,
                                                            std::addressof(m_server_thread));

    // Receive.
    while (true) {
        // Try to receive a request.
        {
            KScopedSchedulerLock sl(m_kernel);

            // Check that we aren't already receiving.
            R_UNLESS(m_server_thread == nullptr, ResultInvalidState);
            R_UNLESS(m_server_thread_id == InvalidThreadId, ResultInvalidState);

            // Check that we're open.
            R_UNLESS(!m_parent->IsClientClosed(), ResultSessionClosed);
            R_UNLESS(!m_parent->IsServerClosed(), ResultSessionClosed);

            // Check that we're not terminating.
            R_UNLESS(!GetCurrentThread(m_kernel).IsTerminationRequested(),
                     ResultTerminationRequested);

            // If we have a request available, use it.
            if (auto head = m_request_list.begin(); head != m_request_list.end()) {
                // Set our current request.
                m_current_request = std::addressof(*head);
                m_current_request->Open();

                // Set our server thread id.
                m_server_thread_id = GetCurrentThread(m_kernel).GetId();

                // Copy the client request data.
                std::memcpy(GetCurrentThread(m_kernel).GetLightSessionData(),
                            m_current_request->GetLightSessionData(), KLightSession::DataSize);

                // We successfully received.
                R_SUCCEED();
            }

            // We need to wait for a request to come in.

            // Check if we were cancelled.
            if (GetCurrentThread(m_kernel).IsWaitCancelled()) {
                GetCurrentThread(m_kernel).ClearWaitCancelled();
                R_THROW(ResultCancelled);
            }

            // Mark ourselves as cancellable.
            GetCurrentThread(m_kernel).SetCancellable();

            // Wait for a request to come in.
            m_server_thread = GetCurrentThreadPointer(m_kernel);
            GetCurrentThread(m_kernel).SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::IPC);
            GetCurrentThread(m_kernel).BeginWait(std::addressof(wait_queue));
        }

        // We waited to receive a request; if our wait failed, return the failing result.
        R_TRY(GetCurrentThread(m_kernel).GetWaitResult());
    }
}

void KLightServerSession::CleanupRequests() {
    // Cleanup all pending requests.
    {
        KScopedSchedulerLock sl(m_kernel);

        // Handle the current request.
        if (m_current_request != nullptr) {
            // Reply to the current request.
            if (!m_current_request->IsTerminationRequested()) {
                m_current_request->EndWait(ResultSessionClosed);
            }

            // Clear our current request.
            m_current_request->Close();
            m_current_request = nullptr;
            m_server_thread_id = InvalidThreadId;
        }

        // Reply to all other requests.
        for (auto& thread : m_request_list) {
            thread.EndWait(ResultSessionClosed);
        }

        // Wait up our server thread, if we have one.
        if (m_server_thread != nullptr) {
            m_server_thread->EndWait(ResultSessionClosed);
        }
    }
}

} // namespace Kernel


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

#pragma once

#include "core/hle/kernel/k_auto_object.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/result.h"

namespace Kernel {

class KLightSession;

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

private:
    KLightSession* m_parent{};
    KThread::WaiterList m_request_list{};
    KThread* m_current_request{};
    u64 m_server_thread_id{std::numeric_limits<u64>::max()};
    KThread* m_server_thread{};

public:
    explicit KLightServerSession(KernelCore& kernel);
    ~KLightServerSession();

    void Initialize(KLightSession* parent) {
        // Set member variables. */
        m_parent = parent;
    }

    virtual void Destroy() override;

    constexpr const KLightSession* GetParent() const {
        return m_parent;
    }

    Result OnRequest(KThread* request_thread);
    Result ReplyAndReceive(u32* data);

    void OnClientClosed();

private:
    void CleanupRequests();
};

} // namespace Kernel


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

#include "core/hle/kernel/k_client_port.h"
#include "core/hle/kernel/k_light_client_session.h"
#include "core/hle/kernel/k_light_server_session.h"
#include "core/hle/kernel/k_light_session.h"
#include "core/hle/kernel/k_process.h"

namespace Kernel {

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

void KLightSession::Initialize(KClientPort* client_port, uintptr_t name) {
    // Increment reference count.
    // Because reference count is one on creation, this will result
    // in a reference count of two. Thus, when both server and client are closed
    // this object will be destroyed.
    this->Open();

    // Create our sub sessions.
    KAutoObject::Create(std::addressof(m_server));
    KAutoObject::Create(std::addressof(m_client));

    // Initialize our sub sessions.
    m_server.Initialize(this);
    m_client.Initialize(this);

    // Set state and name.
    m_state = State::Normal;
    m_name = name;

    // Set our owner process.
    m_process = GetCurrentProcessPointer(m_kernel);
    m_process->Open();

    // Set our port.
    m_port = client_port;
    if (m_port != nullptr) {
        m_port->Open();
    }

    // Mark initialized.
    m_initialized = true;
}

void KLightSession::Finalize() {
    if (m_port != nullptr) {
        m_port->OnSessionFinalized();
        m_port->Close();
    }
}

void KLightSession::OnServerClosed() {
    if (m_state == State::Normal) {
        m_state = State::ServerClosed;
        m_client.OnServerClosed();
    }

    this->Close();
}

void KLightSession::OnClientClosed() {
    if (m_state == State::Normal) {
        m_state = State::ClientClosed;
        m_server.OnClientClosed();
    }

    this->Close();
}

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

} // namespace Kernel


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

#pragma once

#include "core/hle/kernel/k_light_client_session.h"
#include "core/hle/kernel/k_light_server_session.h"
#include "core/hle/kernel/slab_helpers.h"
#include "core/hle/result.h"

namespace Kernel {

class KClientPort;
class KProcess;

// TODO: SupportDynamicExpansion for SlabHeap
class KLightSession final
    : public KAutoObjectWithSlabHeapAndContainer<KLightSession, KAutoObjectWithList> {
    KERNEL_AUTOOBJECT_TRAITS(KLightSession, KAutoObject);

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

public:
    static constexpr size_t DataSize = sizeof(u32) * 7;
    static constexpr u32 ReplyFlag = (1U << 31);

private:
    KLightServerSession m_server;
    KLightClientSession m_client;
    State m_state{State::Invalid};
    KClientPort* m_port{};
    uintptr_t m_name{};
    KProcess* m_process{};
    bool m_initialized{};

public:
    explicit KLightSession(KernelCore& kernel);
    ~KLightSession();

    void Initialize(KClientPort* client_port, uintptr_t name);
    void Finalize() override;

    bool IsInitialized() const override {
        return m_initialized;
    }
    uintptr_t GetPostDestroyArgument() const override {
        return reinterpret_cast<uintptr_t>(m_process);
    }

    static void PostDestroy(uintptr_t arg);

    void OnServerClosed();
    void OnClientClosed();

    bool IsServerClosed() const {
        return m_state != State::Normal;
    }
    bool IsClientClosed() const {
        return m_state != State::Normal;
    }

    Result OnRequest(KThread* request_thread) {
        R_RETURN(m_server.OnRequest(request_thread));
    }

    KLightClientSession& GetClientSession() {
        return m_client;
    }
    KLightServerSession& GetServerSession() {
        return m_server;
    }
    const KLightClientSession& GetClientSession() const {
        return m_client;
    }
    const KLightServerSession& GetServerSession() const {
        return m_server;
    }
};

} // namespace Kernel