From 615fb40416b9ee10abf40a036b31d1540886a9b8 Mon Sep 17 00:00:00 2001
From: bunnei <bunneidev@gmail.com>
Date: Fri, 21 Jan 2022 17:10:11 -0800
Subject: [PATCH] hle: kernel: KThread: Ensure host (dummy) threads block on
 locking.

- But do not enter the priority queue, as otherwise they will be scheduled.
- Allows dummy threads to use guest synchronization primitives.
---
 src/core/hle/kernel/k_priority_queue.h | 36 +++++++++++++++++++++++++
 src/core/hle/kernel/k_scheduler.cpp    |  3 +++
 src/core/hle/kernel/k_thread.cpp       | 37 ++++++++++++++++++++++++++
 src/core/hle/kernel/k_thread.h         | 13 +++++++++
 4 files changed, 89 insertions(+)

diff --git a/src/core/hle/kernel/k_priority_queue.h b/src/core/hle/kernel/k_priority_queue.h
index f4d71ad7e..0b894c8cf 100644
--- a/src/core/hle/kernel/k_priority_queue.h
+++ b/src/core/hle/kernel/k_priority_queue.h
@@ -45,6 +45,7 @@ concept KPriorityQueueMember = !std::is_reference_v<T> && requires(T & t) {
 
     { t.GetActiveCore() } -> Common::ConvertibleTo<s32>;
     { t.GetPriority() } -> Common::ConvertibleTo<s32>;
+    { t.IsDummyThread() } -> Common::ConvertibleTo<bool>;
 };
 
 template <typename Member, size_t NumCores_, int LowestPriority, int HighestPriority>
@@ -349,24 +350,49 @@ public:
 
     // Mutators.
     constexpr void PushBack(Member* member) {
+        // This is for host (dummy) threads that we do not want to enter the priority queue.
+        if (member->IsDummyThread()) {
+            return;
+        }
+
         this->PushBack(member->GetPriority(), member);
     }
 
     constexpr void Remove(Member* member) {
+        // This is for host (dummy) threads that we do not want to enter the priority queue.
+        if (member->IsDummyThread()) {
+            return;
+        }
+
         this->Remove(member->GetPriority(), member);
     }
 
     constexpr void MoveToScheduledFront(Member* member) {
+        // This is for host (dummy) threads that we do not want to enter the priority queue.
+        if (member->IsDummyThread()) {
+            return;
+        }
+
         this->scheduled_queue.MoveToFront(member->GetPriority(), member->GetActiveCore(), member);
     }
 
     constexpr KThread* MoveToScheduledBack(Member* member) {
+        // This is for host (dummy) threads that we do not want to enter the priority queue.
+        if (member->IsDummyThread()) {
+            return {};
+        }
+
         return this->scheduled_queue.MoveToBack(member->GetPriority(), member->GetActiveCore(),
                                                 member);
     }
 
     // First class fancy operations.
     constexpr void ChangePriority(s32 prev_priority, bool is_running, Member* member) {
+        // This is for host (dummy) threads that we do not want to enter the priority queue.
+        if (member->IsDummyThread()) {
+            return;
+        }
+
         ASSERT(IsValidPriority(prev_priority));
 
         // Remove the member from the queues.
@@ -383,6 +409,11 @@ public:
 
     constexpr void ChangeAffinityMask(s32 prev_core, const AffinityMaskType& prev_affinity,
                                       Member* member) {
+        // This is for host (dummy) threads that we do not want to enter the priority queue.
+        if (member->IsDummyThread()) {
+            return;
+        }
+
         // Get the new information.
         const s32 priority = member->GetPriority();
         const AffinityMaskType& new_affinity = member->GetAffinityMask();
@@ -412,6 +443,11 @@ public:
     }
 
     constexpr void ChangeCore(s32 prev_core, Member* member, bool to_front = false) {
+        // This is for host (dummy) threads that we do not want to enter the priority queue.
+        if (member->IsDummyThread()) {
+            return;
+        }
+
         // Get the new information.
         const s32 new_core = member->GetActiveCore();
         const s32 priority = member->GetPriority();
diff --git a/src/core/hle/kernel/k_scheduler.cpp b/src/core/hle/kernel/k_scheduler.cpp
index 1b2a01869..b32d4f285 100644
--- a/src/core/hle/kernel/k_scheduler.cpp
+++ b/src/core/hle/kernel/k_scheduler.cpp
@@ -406,6 +406,9 @@ void KScheduler::EnableScheduling(KernelCore& kernel, u64 cores_needing_scheduli
     } else {
         RescheduleCores(kernel, cores_needing_scheduling);
     }
+
+    // Special case to ensure dummy threads that are waiting block.
+    current_thread->IfDummyThreadTryWait();
 }
 
 u64 KScheduler::UpdateHighestPriorityThreads(KernelCore& kernel) {
diff --git a/src/core/hle/kernel/k_thread.cpp b/src/core/hle/kernel/k_thread.cpp
index 4cbf12c11..f42abb8a1 100644
--- a/src/core/hle/kernel/k_thread.cpp
+++ b/src/core/hle/kernel/k_thread.cpp
@@ -1075,12 +1075,46 @@ ResultCode KThread::Sleep(s64 timeout) {
     return ResultSuccess;
 }
 
+void KThread::IfDummyThreadTryWait() {
+    if (!IsDummyThread()) {
+        return;
+    }
+
+    if (GetState() != ThreadState::Waiting) {
+        return;
+    }
+
+    // Block until we can grab the lock.
+    KScopedSpinLock lk{dummy_wait_lock};
+}
+
+void KThread::IfDummyThreadBeginWait() {
+    if (!IsDummyThread()) {
+        return;
+    }
+
+    // Ensure the thread will block when IfDummyThreadTryWait is called.
+    dummy_wait_lock.Lock();
+}
+
+void KThread::IfDummyThreadEndWait() {
+    if (!IsDummyThread()) {
+        return;
+    }
+
+    // Ensure the thread will no longer block.
+    dummy_wait_lock.Unlock();
+}
+
 void KThread::BeginWait(KThreadQueue* queue) {
     // Set our state as waiting.
     SetState(ThreadState::Waiting);
 
     // Set our wait queue.
     wait_queue = queue;
+
+    // Special case for dummy threads to ensure they block.
+    IfDummyThreadBeginWait();
 }
 
 void KThread::NotifyAvailable(KSynchronizationObject* signaled_object, ResultCode wait_result_) {
@@ -1106,6 +1140,9 @@ void KThread::EndWait(ResultCode wait_result_) {
         }
 
         wait_queue->EndWait(this, wait_result_);
+
+        // Special case for dummy threads to wakeup if necessary.
+        IfDummyThreadEndWait();
     }
 }
 
diff --git a/src/core/hle/kernel/k_thread.h b/src/core/hle/kernel/k_thread.h
index 77b53a198..d058db62c 100644
--- a/src/core/hle/kernel/k_thread.h
+++ b/src/core/hle/kernel/k_thread.h
@@ -558,6 +558,10 @@ public:
         return thread_type;
     }
 
+    [[nodiscard]] bool IsDummyThread() const {
+        return GetThreadType() == ThreadType::Dummy;
+    }
+
     void SetWaitObjectsForDebugging(const std::span<KSynchronizationObject*>& objects) {
         wait_objects_for_debugging.clear();
         wait_objects_for_debugging.reserve(objects.size());
@@ -632,6 +636,14 @@ public:
         return condvar_key;
     }
 
+    // Dummy threads (used for HLE host threads) cannot wait based on the guest scheduler, and
+    // therefore will not block on guest kernel synchronization primitives. These methods handle
+    // blocking as needed.
+
+    void IfDummyThreadTryWait();
+    void IfDummyThreadBeginWait();
+    void IfDummyThreadEndWait();
+
 private:
     static constexpr size_t PriorityInheritanceCountMax = 10;
     union SyncObjectBuffer {
@@ -750,6 +762,7 @@ private:
     bool resource_limit_release_hint{};
     StackParameters stack_parameters{};
     KSpinLock context_guard{};
+    KSpinLock dummy_wait_lock{};
 
     // For emulation
     std::shared_ptr<Common::Fiber> host_context{};