diff options
| author | gdkchan <gab.dark.100@gmail.com> | 2018-09-18 20:36:43 -0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2018-09-18 20:36:43 -0300 |
| commit | b8133c19971c7a2026af803003fafedbdb70488e (patch) | |
| tree | 84f4630e897ccd3f77b86051241a22a6cf45193d /Ryujinx.HLE/HOS/Kernel/KAddressArbiter.cs | |
| parent | 33e2810ef36fe0cf613aecd4c609f425aed02539 (diff) | |
Thread scheduler rewrite (#393)
* Started to rewrite the thread scheduler
* Add a single core-like scheduling mode, enabled by default
* Clear exclusive monitor on context switch
* Add SetThreadActivity, misc fixes
* Implement WaitForAddress and SignalToAddress svcs, misc fixes
* Misc fixes (on SetActivity and Arbiter), other tweaks
* Rebased
* Add missing null check
* Rename multicore key on config, fix UpdatePriorityInheritance
* Make scheduling data MLQs private
* nit: Ordering
Diffstat (limited to 'Ryujinx.HLE/HOS/Kernel/KAddressArbiter.cs')
| -rw-r--r-- | Ryujinx.HLE/HOS/Kernel/KAddressArbiter.cs | 678 |
1 files changed, 678 insertions, 0 deletions
diff --git a/Ryujinx.HLE/HOS/Kernel/KAddressArbiter.cs b/Ryujinx.HLE/HOS/Kernel/KAddressArbiter.cs new file mode 100644 index 00000000..f2156a5c --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/KAddressArbiter.cs @@ -0,0 +1,678 @@ +using ChocolArm64.Memory; +using System.Collections.Generic; +using System.Linq; + +using static Ryujinx.HLE.HOS.ErrorCode; + +namespace Ryujinx.HLE.HOS.Kernel +{ + class KAddressArbiter + { + private const int HasListenersMask = 0x40000000; + + private Horizon System; + + public List<KThread> CondVarThreads; + public List<KThread> ArbiterThreads; + + public KAddressArbiter(Horizon System) + { + this.System = System; + + CondVarThreads = new List<KThread>(); + ArbiterThreads = new List<KThread>(); + } + + public long ArbitrateLock( + Process Process, + AMemory Memory, + int OwnerHandle, + long MutexAddress, + int RequesterHandle) + { + System.CriticalSectionLock.Lock(); + + KThread CurrentThread = System.Scheduler.GetCurrentThread(); + + CurrentThread.SignaledObj = null; + CurrentThread.ObjSyncResult = 0; + + if (!UserToKernelInt32(Memory, MutexAddress, out int MutexValue)) + { + System.CriticalSectionLock.Unlock(); + + return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);; + } + + if (MutexValue != (OwnerHandle | HasListenersMask)) + { + System.CriticalSectionLock.Unlock(); + + return 0; + } + + KThread MutexOwner = Process.HandleTable.GetData<KThread>(OwnerHandle); + + if (MutexOwner == null) + { + System.CriticalSectionLock.Unlock(); + + return MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); + } + + CurrentThread.MutexAddress = MutexAddress; + CurrentThread.ThreadHandleForUserMutex = RequesterHandle; + + MutexOwner.AddMutexWaiter(CurrentThread); + + CurrentThread.Reschedule(ThreadSchedState.Paused); + + System.CriticalSectionLock.Unlock(); + System.CriticalSectionLock.Lock(); + + if (CurrentThread.MutexOwner != null) + { + CurrentThread.MutexOwner.RemoveMutexWaiter(CurrentThread); + } + + System.CriticalSectionLock.Unlock(); + + return (uint)CurrentThread.ObjSyncResult; + } + + public long ArbitrateUnlock(AMemory Memory, long MutexAddress) + { + System.CriticalSectionLock.Lock(); + + KThread CurrentThread = System.Scheduler.GetCurrentThread(); + + (long Result, KThread NewOwnerThread) = MutexUnlock(Memory, CurrentThread, MutexAddress); + + if (Result != 0 && NewOwnerThread != null) + { + NewOwnerThread.SignaledObj = null; + NewOwnerThread.ObjSyncResult = (int)Result; + } + + System.CriticalSectionLock.Unlock(); + + return Result; + } + + public long WaitProcessWideKeyAtomic( + AMemory Memory, + long MutexAddress, + long CondVarAddress, + int ThreadHandle, + long Timeout) + { + System.CriticalSectionLock.Lock(); + + KThread CurrentThread = System.Scheduler.GetCurrentThread(); + + CurrentThread.SignaledObj = null; + CurrentThread.ObjSyncResult = (int)MakeError(ErrorModule.Kernel, KernelErr.Timeout); + + if (CurrentThread.ShallBeTerminated || + CurrentThread.SchedFlags == ThreadSchedState.TerminationPending) + { + System.CriticalSectionLock.Unlock(); + + return MakeError(ErrorModule.Kernel, KernelErr.ThreadTerminating); + } + + (long Result, _) = MutexUnlock(Memory, CurrentThread, MutexAddress); + + if (Result != 0) + { + System.CriticalSectionLock.Unlock(); + + return Result; + } + + CurrentThread.MutexAddress = MutexAddress; + CurrentThread.ThreadHandleForUserMutex = ThreadHandle; + CurrentThread.CondVarAddress = CondVarAddress; + + CondVarThreads.Add(CurrentThread); + + if (Timeout != 0) + { + CurrentThread.Reschedule(ThreadSchedState.Paused); + + if (Timeout > 0) + { + System.TimeManager.ScheduleFutureInvocation(CurrentThread, Timeout); + } + } + + System.CriticalSectionLock.Unlock(); + + if (Timeout > 0) + { + System.TimeManager.UnscheduleFutureInvocation(CurrentThread); + } + + System.CriticalSectionLock.Lock(); + + if (CurrentThread.MutexOwner != null) + { + CurrentThread.MutexOwner.RemoveMutexWaiter(CurrentThread); + } + + CondVarThreads.Remove(CurrentThread); + + System.CriticalSectionLock.Unlock(); + + return (uint)CurrentThread.ObjSyncResult; + } + + private (long, KThread) MutexUnlock(AMemory Memory, KThread CurrentThread, long MutexAddress) + { + KThread NewOwnerThread = CurrentThread.RelinquishMutex(MutexAddress, out int Count); + + int MutexValue = 0; + + if (NewOwnerThread != null) + { + MutexValue = NewOwnerThread.ThreadHandleForUserMutex; + + if (Count >= 2) + { + MutexValue |= HasListenersMask; + } + + NewOwnerThread.SignaledObj = null; + NewOwnerThread.ObjSyncResult = 0; + + NewOwnerThread.ReleaseAndResume(); + } + + long Result = 0; + + if (!KernelToUserInt32(Memory, MutexAddress, MutexValue)) + { + Result = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); + } + + return (Result, NewOwnerThread); + } + + public void SignalProcessWideKey(Process Process, AMemory Memory, long Address, int Count) + { + Queue<KThread> SignaledThreads = new Queue<KThread>(); + + System.CriticalSectionLock.Lock(); + + IOrderedEnumerable<KThread> SortedThreads = CondVarThreads.OrderBy(x => x.DynamicPriority); + + foreach (KThread Thread in SortedThreads.Where(x => x.CondVarAddress == Address)) + { + TryAcquireMutex(Process, Memory, Thread); + + SignaledThreads.Enqueue(Thread); + + //If the count is <= 0, we should signal all threads waiting. + if (Count >= 1 && --Count == 0) + { + break; + } + } + + while (SignaledThreads.TryDequeue(out KThread Thread)) + { + CondVarThreads.Remove(Thread); + } + + System.CriticalSectionLock.Unlock(); + } + + private KThread TryAcquireMutex(Process Process, AMemory Memory, KThread Requester) + { + long Address = Requester.MutexAddress; + + Memory.SetExclusive(0, Address); + + if (!UserToKernelInt32(Memory, Address, out int MutexValue)) + { + //Invalid address. + Memory.ClearExclusive(0); + + Requester.SignaledObj = null; + Requester.ObjSyncResult = (int)MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); + + return null; + } + + while (true) + { + if (Memory.TestExclusive(0, Address)) + { + if (MutexValue != 0) + { + //Update value to indicate there is a mutex waiter now. + Memory.WriteInt32(Address, MutexValue | HasListenersMask); + } + else + { + //No thread owning the mutex, assign to requesting thread. + Memory.WriteInt32(Address, Requester.ThreadHandleForUserMutex); + } + + Memory.ClearExclusiveForStore(0); + + break; + } + + Memory.SetExclusive(0, Address); + + MutexValue = Memory.ReadInt32(Address); + } + + if (MutexValue == 0) + { + //We now own the mutex. + Requester.SignaledObj = null; + Requester.ObjSyncResult = 0; + + Requester.ReleaseAndResume(); + + return null; + } + + MutexValue &= ~HasListenersMask; + + KThread MutexOwner = Process.HandleTable.GetData<KThread>(MutexValue); + + if (MutexOwner != null) + { + //Mutex already belongs to another thread, wait for it. + MutexOwner.AddMutexWaiter(Requester); + } + else + { + //Invalid mutex owner. + Requester.SignaledObj = null; + Requester.ObjSyncResult = (int)MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); + + Requester.ReleaseAndResume(); + } + + return MutexOwner; + } + + public long WaitForAddressIfEqual(AMemory Memory, long Address, int Value, long Timeout) + { + KThread CurrentThread = System.Scheduler.GetCurrentThread(); + + System.CriticalSectionLock.Lock(); + + if (CurrentThread.ShallBeTerminated || + CurrentThread.SchedFlags == ThreadSchedState.TerminationPending) + { + System.CriticalSectionLock.Unlock(); + + return MakeError(ErrorModule.Kernel, KernelErr.ThreadTerminating); + } + + CurrentThread.SignaledObj = null; + CurrentThread.ObjSyncResult = (int)MakeError(ErrorModule.Kernel, KernelErr.Timeout); + + if (!UserToKernelInt32(Memory, Address, out int CurrentValue)) + { + System.CriticalSectionLock.Unlock(); + + return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); + } + + if (CurrentValue == Value) + { + if (Timeout == 0) + { + System.CriticalSectionLock.Unlock(); + + return MakeError(ErrorModule.Kernel, KernelErr.Timeout); + } + + CurrentThread.MutexAddress = Address; + CurrentThread.WaitingInArbitration = true; + + InsertSortedByPriority(ArbiterThreads, CurrentThread); + + CurrentThread.Reschedule(ThreadSchedState.Paused); + + if (Timeout > 0) + { + System.TimeManager.ScheduleFutureInvocation(CurrentThread, Timeout); + } + + System.CriticalSectionLock.Unlock(); + + if (Timeout > 0) + { + System.TimeManager.UnscheduleFutureInvocation(CurrentThread); + } + + System.CriticalSectionLock.Lock(); + + if (CurrentThread.WaitingInArbitration) + { + ArbiterThreads.Remove(CurrentThread); + + CurrentThread.WaitingInArbitration = false; + } + + System.CriticalSectionLock.Unlock(); + + return CurrentThread.ObjSyncResult; + } + + System.CriticalSectionLock.Unlock(); + + return MakeError(ErrorModule.Kernel, KernelErr.InvalidState); + } + + public long WaitForAddressIfLessThan( + AMemory Memory, + long Address, + int Value, + bool ShouldDecrement, + long Timeout) + { + KThread CurrentThread = System.Scheduler.GetCurrentThread(); + + System.CriticalSectionLock.Lock(); + + if (CurrentThread.ShallBeTerminated || + CurrentThread.SchedFlags == ThreadSchedState.TerminationPending) + { + System.CriticalSectionLock.Unlock(); + + return MakeError(ErrorModule.Kernel, KernelErr.ThreadTerminating); + } + + CurrentThread.SignaledObj = null; + CurrentThread.ObjSyncResult = (int)MakeError(ErrorModule.Kernel, KernelErr.Timeout); + + //If ShouldDecrement is true, do atomic decrement of the value at Address. + Memory.SetExclusive(0, Address); + + if (!UserToKernelInt32(Memory, Address, out int CurrentValue)) + { + System.CriticalSectionLock.Unlock(); + + return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); + } + + if (ShouldDecrement) + { + while (CurrentValue < Value) + { + if (Memory.TestExclusive(0, Address)) + { + Memory.WriteInt32(Address, CurrentValue - 1); + + Memory.ClearExclusiveForStore(0); + + break; + } + + Memory.SetExclusive(0, Address); + + CurrentValue = Memory.ReadInt32(Address); + } + } + + Memory.ClearExclusive(0); + + if (CurrentValue < Value) + { + if (Timeout == 0) + { + System.CriticalSectionLock.Unlock(); + + return MakeError(ErrorModule.Kernel, KernelErr.Timeout); + } + + CurrentThread.MutexAddress = Address; + CurrentThread.WaitingInArbitration = true; + + InsertSortedByPriority(ArbiterThreads, CurrentThread); + + CurrentThread.Reschedule(ThreadSchedState.Paused); + + if (Timeout > 0) + { + System.TimeManager.ScheduleFutureInvocation(CurrentThread, Timeout); + } + + System.CriticalSectionLock.Unlock(); + + if (Timeout > 0) + { + System.TimeManager.UnscheduleFutureInvocation(CurrentThread); + } + + System.CriticalSectionLock.Lock(); + + if (CurrentThread.WaitingInArbitration) + { + ArbiterThreads.Remove(CurrentThread); + + CurrentThread.WaitingInArbitration = false; + } + + System.CriticalSectionLock.Unlock(); + + return CurrentThread.ObjSyncResult; + } + + System.CriticalSectionLock.Unlock(); + + return MakeError(ErrorModule.Kernel, KernelErr.InvalidState); + } + + private void InsertSortedByPriority(List<KThread> Threads, KThread Thread) + { + int NextIndex = -1; + + for (int Index = 0; Index < Threads.Count; Index++) + { + if (Threads[Index].DynamicPriority > Thread.DynamicPriority) + { + NextIndex = Index; + + break; + } + } + + if (NextIndex != -1) + { + Threads.Insert(NextIndex, Thread); + } + else + { + Threads.Add(Thread); + } + } + + public long Signal(long Address, int Count) + { + System.CriticalSectionLock.Lock(); + + WakeArbiterThreads(Address, Count); + + System.CriticalSectionLock.Unlock(); + + return 0; + } + + public long SignalAndIncrementIfEqual(AMemory Memory, long Address, int Value, int Count) + { + System.CriticalSectionLock.Lock(); + + Memory.SetExclusive(0, Address); + + if (!UserToKernelInt32(Memory, Address, out int CurrentValue)) + { + System.CriticalSectionLock.Unlock(); + + return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); + } + + while (CurrentValue == Value) + { + if (Memory.TestExclusive(0, Address)) + { + Memory.WriteInt32(Address, CurrentValue + 1); + + Memory.ClearExclusiveForStore(0); + + break; + } + + Memory.SetExclusive(0, Address); + + CurrentValue = Memory.ReadInt32(Address); + } + + Memory.ClearExclusive(0); + + if (CurrentValue != Value) + { + System.CriticalSectionLock.Unlock(); + + return MakeError(ErrorModule.Kernel, KernelErr.InvalidState); + } + + WakeArbiterThreads(Address, Count); + + System.CriticalSectionLock.Unlock(); + + return 0; + } + + public long SignalAndModifyIfEqual(AMemory Memory, long Address, int Value, int Count) + { + System.CriticalSectionLock.Lock(); + + int Offset; + + //The value is decremented if the number of threads waiting is less + //or equal to the Count of threads to be signaled, or Count is zero + //or negative. It is incremented if there are no threads waiting. + int WaitingCount = 0; + + foreach (KThread Thread in ArbiterThreads.Where(x => x.MutexAddress == Address)) + { + if (++WaitingCount > Count) + { + break; + } + } + + if (WaitingCount > 0) + { + Offset = WaitingCount <= Count || Count <= 0 ? -1 : 0; + } + else + { + Offset = 1; + } + + Memory.SetExclusive(0, Address); + + if (!UserToKernelInt32(Memory, Address, out int CurrentValue)) + { + System.CriticalSectionLock.Unlock(); + + return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); + } + + while (CurrentValue == Value) + { + if (Memory.TestExclusive(0, Address)) + { + Memory.WriteInt32(Address, CurrentValue + Offset); + + Memory.ClearExclusiveForStore(0); + + break; + } + + Memory.SetExclusive(0, Address); + + CurrentValue = Memory.ReadInt32(Address); + } + + Memory.ClearExclusive(0); + + if (CurrentValue != Value) + { + System.CriticalSectionLock.Unlock(); + + return MakeError(ErrorModule.Kernel, KernelErr.InvalidState); + } + + WakeArbiterThreads(Address, Count); + + System.CriticalSectionLock.Unlock(); + + return 0; + } + + private void WakeArbiterThreads(long Address, int Count) + { + Queue<KThread> SignaledThreads = new Queue<KThread>(); + + foreach (KThread Thread in ArbiterThreads.Where(x => x.MutexAddress == Address)) + { + SignaledThreads.Enqueue(Thread); + + //If the count is <= 0, we should signal all threads waiting. + if (Count >= 1 && --Count == 0) + { + break; + } + } + + while (SignaledThreads.TryDequeue(out KThread Thread)) + { + Thread.SignaledObj = null; + Thread.ObjSyncResult = 0; + + Thread.ReleaseAndResume(); + + Thread.WaitingInArbitration = false; + + ArbiterThreads.Remove(Thread); + } + } + + private bool UserToKernelInt32(AMemory Memory, long Address, out int Value) + { + if (Memory.IsMapped(Address)) + { + Value = Memory.ReadInt32(Address); + + return true; + } + + Value = 0; + + return false; + } + + private bool KernelToUserInt32(AMemory Memory, long Address, int Value) + { + if (Memory.IsMapped(Address)) + { + Memory.WriteInt32ToSharedAddr(Address, Value); + + return true; + } + + return false; + } + } +} |
