Explorar o código

Kernel: Implemented mutex priority inheritance.

Verified with a hwtest and implemented based on reverse engineering.

Thread A's priority will get bumped to the highest priority among all the threads that are waiting for a mutex that A holds.
Once A releases the mutex and ownership is transferred to B, A's priority will return to normal and B's priority will be bumped.
Subv %!s(int64=8) %!d(string=hai) anos
pai
achega
46572d027d

+ 31 - 8
src/core/hle/kernel/mutex.cpp

@@ -18,13 +18,13 @@ namespace Kernel {
 
 
 /// Returns the number of threads that are waiting for a mutex, and the highest priority one among
 /// Returns the number of threads that are waiting for a mutex, and the highest priority one among
 /// those.
 /// those.
-static std::pair<SharedPtr<Thread>, u32> GetHighestPriorityMutexWaitingThread(VAddr mutex_addr) {
-    auto& thread_list = Core::System::GetInstance().Scheduler().GetThreadList();
+static std::pair<SharedPtr<Thread>, u32> GetHighestPriorityMutexWaitingThread(
+    SharedPtr<Thread> current_thread, VAddr mutex_addr) {
 
 
     SharedPtr<Thread> highest_priority_thread;
     SharedPtr<Thread> highest_priority_thread;
     u32 num_waiters = 0;
     u32 num_waiters = 0;
 
 
-    for (auto& thread : thread_list) {
+    for (auto& thread : current_thread->wait_mutex_threads) {
         if (thread->mutex_wait_address != mutex_addr)
         if (thread->mutex_wait_address != mutex_addr)
             continue;
             continue;
 
 
@@ -40,6 +40,21 @@ static std::pair<SharedPtr<Thread>, u32> GetHighestPriorityMutexWaitingThread(VA
     return {highest_priority_thread, num_waiters};
     return {highest_priority_thread, num_waiters};
 }
 }
 
 
+/// Update the mutex owner field of all threads waiting on the mutex to point to the new owner.
+static void TransferMutexOwnership(VAddr mutex_addr, SharedPtr<Thread> current_thread,
+                                   SharedPtr<Thread> new_owner) {
+    auto threads = current_thread->wait_mutex_threads;
+    for (auto& thread : threads) {
+        if (thread->mutex_wait_address != mutex_addr)
+            continue;
+
+        ASSERT(thread->lock_owner == current_thread);
+        current_thread->RemoveMutexWaiter(thread);
+        if (new_owner != thread)
+            new_owner->AddMutexWaiter(thread);
+    }
+}
+
 ResultCode Mutex::TryAcquire(VAddr address, Handle holding_thread_handle,
 ResultCode Mutex::TryAcquire(VAddr address, Handle holding_thread_handle,
                              Handle requesting_thread_handle) {
                              Handle requesting_thread_handle) {
     // The mutex address must be 4-byte aligned
     // The mutex address must be 4-byte aligned
@@ -65,11 +80,14 @@ ResultCode Mutex::TryAcquire(VAddr address, Handle holding_thread_handle,
         return ERR_INVALID_HANDLE;
         return ERR_INVALID_HANDLE;
 
 
     // Wait until the mutex is released
     // Wait until the mutex is released
-    requesting_thread->mutex_wait_address = address;
-    requesting_thread->wait_handle = requesting_thread_handle;
+    GetCurrentThread()->mutex_wait_address = address;
+    GetCurrentThread()->wait_handle = requesting_thread_handle;
 
 
-    requesting_thread->status = THREADSTATUS_WAIT_MUTEX;
-    requesting_thread->wakeup_callback = nullptr;
+    GetCurrentThread()->status = THREADSTATUS_WAIT_MUTEX;
+    GetCurrentThread()->wakeup_callback = nullptr;
+
+    // Update the lock holder thread's priority to prevent priority inversion.
+    holding_thread->AddMutexWaiter(GetCurrentThread());
 
 
     Core::System::GetInstance().PrepareReschedule();
     Core::System::GetInstance().PrepareReschedule();
 
 
@@ -82,14 +100,18 @@ ResultCode Mutex::Release(VAddr address) {
         return ResultCode(ErrorModule::Kernel, ErrCodes::MisalignedAddress);
         return ResultCode(ErrorModule::Kernel, ErrCodes::MisalignedAddress);
     }
     }
 
 
-    auto [thread, num_waiters] = GetHighestPriorityMutexWaitingThread(address);
+    auto [thread, num_waiters] = GetHighestPriorityMutexWaitingThread(GetCurrentThread(), address);
 
 
     // There are no more threads waiting for the mutex, release it completely.
     // There are no more threads waiting for the mutex, release it completely.
     if (thread == nullptr) {
     if (thread == nullptr) {
+        ASSERT(GetCurrentThread()->wait_mutex_threads.empty());
         Memory::Write32(address, 0);
         Memory::Write32(address, 0);
         return RESULT_SUCCESS;
         return RESULT_SUCCESS;
     }
     }
 
 
+    // Transfer the ownership of the mutex from the previous owner to the new one.
+    TransferMutexOwnership(address, GetCurrentThread(), thread);
+
     u32 mutex_value = thread->wait_handle;
     u32 mutex_value = thread->wait_handle;
 
 
     if (num_waiters >= 2) {
     if (num_waiters >= 2) {
@@ -103,6 +125,7 @@ ResultCode Mutex::Release(VAddr address) {
     ASSERT(thread->status == THREADSTATUS_WAIT_MUTEX);
     ASSERT(thread->status == THREADSTATUS_WAIT_MUTEX);
     thread->ResumeFromWait();
     thread->ResumeFromWait();
 
 
+    thread->lock_owner = nullptr;
     thread->condvar_wait_address = 0;
     thread->condvar_wait_address = 0;
     thread->mutex_wait_address = 0;
     thread->mutex_wait_address = 0;
     thread->wait_handle = 0;
     thread->wait_handle = 0;

+ 9 - 0
src/core/hle/kernel/svc.cpp

@@ -621,6 +621,8 @@ static ResultCode WaitProcessWideKeyAtomic(VAddr mutex_addr, VAddr condition_var
 
 
     current_thread->WakeAfterDelay(nano_seconds);
     current_thread->WakeAfterDelay(nano_seconds);
 
 
+    // Note: Deliberately don't attempt to inherit the lock owner's priority.
+
     Core::System::GetInstance().PrepareReschedule();
     Core::System::GetInstance().PrepareReschedule();
     return RESULT_SUCCESS;
     return RESULT_SUCCESS;
 }
 }
@@ -651,6 +653,11 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
             ASSERT(thread->status == THREADSTATUS_WAIT_MUTEX);
             ASSERT(thread->status == THREADSTATUS_WAIT_MUTEX);
             thread->ResumeFromWait();
             thread->ResumeFromWait();
 
 
+            auto lock_owner = thread->lock_owner;
+            if (lock_owner)
+                lock_owner->RemoveMutexWaiter(thread);
+
+            thread->lock_owner = nullptr;
             thread->mutex_wait_address = 0;
             thread->mutex_wait_address = 0;
             thread->condvar_wait_address = 0;
             thread->condvar_wait_address = 0;
             thread->wait_handle = 0;
             thread->wait_handle = 0;
@@ -666,6 +673,8 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
             // Signal that the mutex now has a waiting thread.
             // Signal that the mutex now has a waiting thread.
             Memory::Write32(thread->mutex_wait_address, mutex_val | Mutex::MutexHasWaitersFlag);
             Memory::Write32(thread->mutex_wait_address, mutex_val | Mutex::MutexHasWaitersFlag);
 
 
+            owner->AddMutexWaiter(thread);
+
             Core::System::GetInstance().PrepareReschedule();
             Core::System::GetInstance().PrepareReschedule();
         }
         }
 
 

+ 39 - 2
src/core/hle/kernel/thread.cpp

@@ -129,6 +129,11 @@ static void ThreadWakeupCallback(u64 thread_handle, int cycles_late) {
         thread->mutex_wait_address = 0;
         thread->mutex_wait_address = 0;
         thread->condvar_wait_address = 0;
         thread->condvar_wait_address = 0;
         thread->wait_handle = 0;
         thread->wait_handle = 0;
+
+        auto lock_owner = thread->lock_owner;
+        // Threads waking up by timeout from WaitProcessWideKey do not perform priority inheritance
+        // and don't have a lock owner.
+        ASSERT(lock_owner == nullptr);
     }
     }
 
 
     if (resume)
     if (resume)
@@ -325,8 +330,8 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point,
 void Thread::SetPriority(u32 priority) {
 void Thread::SetPriority(u32 priority) {
     ASSERT_MSG(priority <= THREADPRIO_LOWEST && priority >= THREADPRIO_HIGHEST,
     ASSERT_MSG(priority <= THREADPRIO_LOWEST && priority >= THREADPRIO_HIGHEST,
                "Invalid priority value.");
                "Invalid priority value.");
-    Core::System::GetInstance().Scheduler().SetThreadPriority(this, priority);
-    nominal_priority = current_priority = priority;
+    nominal_priority = priority;
+    UpdatePriority();
 }
 }
 
 
 void Thread::BoostPriority(u32 priority) {
 void Thread::BoostPriority(u32 priority) {
@@ -376,6 +381,38 @@ VAddr Thread::GetCommandBufferAddress() const {
     return GetTLSAddress() + CommandHeaderOffset;
     return GetTLSAddress() + CommandHeaderOffset;
 }
 }
 
 
+void Thread::AddMutexWaiter(SharedPtr<Thread> thread) {
+    thread->lock_owner = this;
+    wait_mutex_threads.emplace_back(std::move(thread));
+    UpdatePriority();
+}
+
+void Thread::RemoveMutexWaiter(SharedPtr<Thread> thread) {
+    boost::remove_erase(wait_mutex_threads, thread);
+    thread->lock_owner = nullptr;
+    UpdatePriority();
+}
+
+void Thread::UpdatePriority() {
+    // Find the highest priority among all the threads that are waiting for this thread's lock
+    u32 new_priority = nominal_priority;
+    for (const auto& thread : wait_mutex_threads) {
+        if (thread->nominal_priority < new_priority)
+            new_priority = thread->nominal_priority;
+    }
+
+    if (new_priority == current_priority)
+        return;
+
+    Core::System::GetInstance().Scheduler().SetThreadPriority(this, new_priority);
+
+    current_priority = new_priority;
+
+    // Recursively update the priority of the thread that depends on the priority of this one.
+    if (lock_owner)
+        lock_owner->UpdatePriority();
+}
+
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 
 
 /**
 /**

+ 15 - 0
src/core/hle/kernel/thread.h

@@ -109,6 +109,15 @@ public:
      */
      */
     void BoostPriority(u32 priority);
     void BoostPriority(u32 priority);
 
 
+    /// Adds a thread to the list of threads that are waiting for a lock held by this thread.
+    void AddMutexWaiter(SharedPtr<Thread> thread);
+
+    /// Removes a thread from the list of threads that are waiting for a lock held by this thread.
+    void RemoveMutexWaiter(SharedPtr<Thread> thread);
+
+    /// Recalculates the current priority taking into account priority inheritance.
+    void UpdatePriority();
+
     /**
     /**
      * Gets the thread's thread ID
      * Gets the thread's thread ID
      * @return The thread's ID
      * @return The thread's ID
@@ -205,6 +214,12 @@ public:
     // passed to WaitSynchronization1/N.
     // passed to WaitSynchronization1/N.
     std::vector<SharedPtr<WaitObject>> wait_objects;
     std::vector<SharedPtr<WaitObject>> wait_objects;
 
 
+    /// List of threads that are waiting for a mutex that is held by this thread.
+    std::vector<SharedPtr<Thread>> wait_mutex_threads;
+
+    /// Thread that owns the lock that this thread is waiting for.
+    SharedPtr<Thread> lock_owner;
+
     // If waiting on a ConditionVariable, this is the ConditionVariable  address
     // If waiting on a ConditionVariable, this is the ConditionVariable  address
     VAddr condvar_wait_address;
     VAddr condvar_wait_address;
     VAddr mutex_wait_address; ///< If waiting on a Mutex, this is the mutex address
     VAddr mutex_wait_address; ///< If waiting on a Mutex, this is the mutex address