diff options
Diffstat (limited to 'src/core')
-rw-r--r-- | src/core/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/core/hle/kernel/k_light_lock.cpp | 130 | ||||
-rw-r--r-- | src/core/hle/kernel/k_light_lock.h | 41 |
3 files changed, 173 insertions, 0 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 843bce150..397cc028f 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -160,6 +160,8 @@ add_library(core STATIC hle/kernel/k_affinity_mask.h hle/kernel/k_condition_variable.cpp hle/kernel/k_condition_variable.h + hle/kernel/k_light_lock.cpp + hle/kernel/k_light_lock.h hle/kernel/k_priority_queue.h hle/kernel/k_scheduler.cpp hle/kernel/k_scheduler.h diff --git a/src/core/hle/kernel/k_light_lock.cpp b/src/core/hle/kernel/k_light_lock.cpp new file mode 100644 index 000000000..08fa65fd5 --- /dev/null +++ b/src/core/hle/kernel/k_light_lock.cpp @@ -0,0 +1,130 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#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/kernel.h" + +namespace Kernel { + +void KLightLock::Lock() { + const uintptr_t cur_thread = reinterpret_cast<uintptr_t>(GetCurrentThreadPointer(kernel)); + const uintptr_t cur_thread_tag = (cur_thread | 1); + + while (true) { + uintptr_t old_tag = tag.load(std::memory_order_relaxed); + + while (!tag.compare_exchange_weak(old_tag, (old_tag == 0) ? cur_thread : old_tag | 1, + std::memory_order_acquire)) { + if ((old_tag | 1) == cur_thread_tag) { + return; + } + } + + if ((old_tag == 0) || ((old_tag | 1) == cur_thread_tag)) { + break; + } + + LockSlowPath(old_tag | 1, cur_thread); + } +} + +void KLightLock::Unlock() { + const uintptr_t cur_thread = reinterpret_cast<uintptr_t>(GetCurrentThreadPointer(kernel)); + uintptr_t expected = cur_thread; + do { + if (expected != cur_thread) { + return UnlockSlowPath(cur_thread); + } + } while (!tag.compare_exchange_weak(expected, 0, std::memory_order_release)); +} + +void KLightLock::LockSlowPath(uintptr_t _owner, uintptr_t _cur_thread) { + KThread* cur_thread = reinterpret_cast<KThread*>(_cur_thread); + + // Pend the current thread waiting on the owner thread. + { + KScopedSchedulerLock sl{kernel}; + + // Ensure we actually have locking to do. + if (tag.load(std::memory_order_relaxed) != _owner) { + return; + } + + // Add the current thread as a waiter on the owner. + KThread* owner_thread = reinterpret_cast<KThread*>(_owner & ~1ul); + cur_thread->SetAddressKey(reinterpret_cast<uintptr_t>(std::addressof(tag))); + owner_thread->AddWaiter(cur_thread); + + // Set thread states. + if (cur_thread->GetState() == ThreadState::Runnable) { + cur_thread->SetState(ThreadState::Waiting); + } else { + KScheduler::SetSchedulerUpdateNeeded(kernel); + } + + if (owner_thread->IsSuspended()) { + owner_thread->ContinueIfHasKernelWaiters(); + KScheduler::SetSchedulerUpdateNeeded(kernel); + } + } + + // We're no longer waiting on the lock owner. + { + KScopedSchedulerLock sl{kernel}; + KThread* owner_thread = cur_thread->GetLockOwner(); + if (owner_thread) { + owner_thread->RemoveWaiter(cur_thread); + } + } +} + +void KLightLock::UnlockSlowPath(uintptr_t _cur_thread) { + KThread* owner_thread = reinterpret_cast<KThread*>(_cur_thread); + + // Unlock. + { + KScopedSchedulerLock sl{kernel}; + + // Get the next owner. + s32 num_waiters = 0; + KThread* next_owner = owner_thread->RemoveWaiterByKey( + std::addressof(num_waiters), reinterpret_cast<uintptr_t>(std::addressof(tag))); + + // Pass the lock to the next owner. + uintptr_t next_tag = 0; + if (next_owner) { + next_tag = reinterpret_cast<uintptr_t>(next_owner); + if (num_waiters > 1) { + next_tag |= 0x1; + } + + if (next_owner->GetState() == ThreadState::Waiting) { + next_owner->SetState(ThreadState::Runnable); + } else { + KScheduler::SetSchedulerUpdateNeeded(kernel); + } + + 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. + tag.store(next_tag); + } +} + +bool KLightLock::IsLockedByCurrentThread() const { + return (tag | 0x1ul) == (reinterpret_cast<uintptr_t>(GetCurrentThreadPointer(kernel)) | 0x1ul); +} + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_light_lock.h b/src/core/hle/kernel/k_light_lock.h new file mode 100644 index 000000000..f4c45f76a --- /dev/null +++ b/src/core/hle/kernel/k_light_lock.h @@ -0,0 +1,41 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <atomic> + +#include "common/common_types.h" +#include "core/hle/kernel/k_scoped_lock.h" + +namespace Kernel { + +class KernelCore; + +class KLightLock { +public: + explicit KLightLock(KernelCore& kernel_) : kernel{kernel_} {} + + void Lock(); + + void Unlock(); + + void LockSlowPath(uintptr_t owner, uintptr_t cur_thread); + + void UnlockSlowPath(uintptr_t cur_thread); + + bool IsLocked() const { + return tag != 0; + } + + bool IsLockedByCurrentThread() const; + +private: + std::atomic<uintptr_t> tag{}; + KernelCore& kernel; +}; + +using KScopedLightLock = KScopedLock<KLightLock>; + +} // namespace Kernel |