Browse Source

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.
bunnei 4 years ago
parent
commit
615fb40416

+ 36 - 0
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();

+ 3 - 0
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) {

+ 37 - 0
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();
     }
 }
 

+ 13 - 0
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{};