diff options
Diffstat (limited to 'src/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs')
-rw-r--r-- | src/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs | 581 |
1 files changed, 581 insertions, 0 deletions
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs new file mode 100644 index 00000000..74867b44 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs @@ -0,0 +1,581 @@ +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.Horizon.Common; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + class KAddressArbiter + { + private const int HasListenersMask = 0x40000000; + + private readonly KernelContext _context; + + private readonly List<KThread> _condVarThreads; + private readonly List<KThread> _arbiterThreads; + + public KAddressArbiter(KernelContext context) + { + _context = context; + + _condVarThreads = new List<KThread>(); + _arbiterThreads = new List<KThread>(); + } + + public Result ArbitrateLock(int ownerHandle, ulong mutexAddress, int requesterHandle) + { + KThread currentThread = KernelStatic.GetCurrentThread(); + + _context.CriticalSection.Enter(); + + if (currentThread.TerminationRequested) + { + _context.CriticalSection.Leave(); + + return KernelResult.ThreadTerminating; + } + + currentThread.SignaledObj = null; + currentThread.ObjSyncResult = Result.Success; + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + if (!KernelTransfer.UserToKernel(out int mutexValue, mutexAddress)) + { + _context.CriticalSection.Leave(); + + return KernelResult.InvalidMemState; + } + + if (mutexValue != (ownerHandle | HasListenersMask)) + { + _context.CriticalSection.Leave(); + + return Result.Success; + } + + KThread mutexOwner = currentProcess.HandleTable.GetObject<KThread>(ownerHandle); + + if (mutexOwner == null) + { + _context.CriticalSection.Leave(); + + return KernelResult.InvalidHandle; + } + + currentThread.MutexAddress = mutexAddress; + currentThread.ThreadHandleForUserMutex = requesterHandle; + + mutexOwner.AddMutexWaiter(currentThread); + + currentThread.Reschedule(ThreadSchedState.Paused); + + _context.CriticalSection.Leave(); + _context.CriticalSection.Enter(); + + if (currentThread.MutexOwner != null) + { + currentThread.MutexOwner.RemoveMutexWaiter(currentThread); + } + + _context.CriticalSection.Leave(); + + return currentThread.ObjSyncResult; + } + + public Result ArbitrateUnlock(ulong mutexAddress) + { + _context.CriticalSection.Enter(); + + KThread currentThread = KernelStatic.GetCurrentThread(); + + (int mutexValue, KThread newOwnerThread) = MutexUnlock(currentThread, mutexAddress); + + Result result = Result.Success; + + if (!KernelTransfer.KernelToUser(mutexAddress, mutexValue)) + { + result = KernelResult.InvalidMemState; + } + + if (result != Result.Success && newOwnerThread != null) + { + newOwnerThread.SignaledObj = null; + newOwnerThread.ObjSyncResult = result; + } + + _context.CriticalSection.Leave(); + + return result; + } + + public Result WaitProcessWideKeyAtomic(ulong mutexAddress, ulong condVarAddress, int threadHandle, long timeout) + { + _context.CriticalSection.Enter(); + + KThread currentThread = KernelStatic.GetCurrentThread(); + + currentThread.SignaledObj = null; + currentThread.ObjSyncResult = KernelResult.TimedOut; + + if (currentThread.TerminationRequested) + { + _context.CriticalSection.Leave(); + + return KernelResult.ThreadTerminating; + } + + (int mutexValue, _) = MutexUnlock(currentThread, mutexAddress); + + KernelTransfer.KernelToUser(condVarAddress, 1); + + if (!KernelTransfer.KernelToUser(mutexAddress, mutexValue)) + { + _context.CriticalSection.Leave(); + + return KernelResult.InvalidMemState; + } + + currentThread.MutexAddress = mutexAddress; + currentThread.ThreadHandleForUserMutex = threadHandle; + currentThread.CondVarAddress = condVarAddress; + + _condVarThreads.Add(currentThread); + + if (timeout != 0) + { + currentThread.Reschedule(ThreadSchedState.Paused); + + if (timeout > 0) + { + _context.TimeManager.ScheduleFutureInvocation(currentThread, timeout); + } + } + + _context.CriticalSection.Leave(); + + if (timeout > 0) + { + _context.TimeManager.UnscheduleFutureInvocation(currentThread); + } + + _context.CriticalSection.Enter(); + + if (currentThread.MutexOwner != null) + { + currentThread.MutexOwner.RemoveMutexWaiter(currentThread); + } + + _condVarThreads.Remove(currentThread); + + _context.CriticalSection.Leave(); + + return currentThread.ObjSyncResult; + } + + private (int, KThread) MutexUnlock(KThread currentThread, ulong 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 = Result.Success; + + newOwnerThread.ReleaseAndResume(); + } + + return (mutexValue, newOwnerThread); + } + + public void SignalProcessWideKey(ulong address, int count) + { + _context.CriticalSection.Enter(); + + WakeThreads(_condVarThreads, count, TryAcquireMutex, x => x.CondVarAddress == address); + + if (!_condVarThreads.Any(x => x.CondVarAddress == address)) + { + KernelTransfer.KernelToUser(address, 0); + } + + _context.CriticalSection.Leave(); + } + + private static void TryAcquireMutex(KThread requester) + { + ulong address = requester.MutexAddress; + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + if (!currentProcess.CpuMemory.IsMapped(address)) + { + // Invalid address. + requester.SignaledObj = null; + requester.ObjSyncResult = KernelResult.InvalidMemState; + + return; + } + + ref int mutexRef = ref currentProcess.CpuMemory.GetRef<int>(address); + + int mutexValue, newMutexValue; + + do + { + mutexValue = mutexRef; + + if (mutexValue != 0) + { + // Update value to indicate there is a mutex waiter now. + newMutexValue = mutexValue | HasListenersMask; + } + else + { + // No thread owning the mutex, assign to requesting thread. + newMutexValue = requester.ThreadHandleForUserMutex; + } + } + while (Interlocked.CompareExchange(ref mutexRef, newMutexValue, mutexValue) != mutexValue); + + if (mutexValue == 0) + { + // We now own the mutex. + requester.SignaledObj = null; + requester.ObjSyncResult = Result.Success; + + requester.ReleaseAndResume(); + + return; + } + + mutexValue &= ~HasListenersMask; + + KThread mutexOwner = currentProcess.HandleTable.GetObject<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 = KernelResult.InvalidHandle; + + requester.ReleaseAndResume(); + } + } + + public Result WaitForAddressIfEqual(ulong address, int value, long timeout) + { + KThread currentThread = KernelStatic.GetCurrentThread(); + + _context.CriticalSection.Enter(); + + if (currentThread.TerminationRequested) + { + _context.CriticalSection.Leave(); + + return KernelResult.ThreadTerminating; + } + + currentThread.SignaledObj = null; + currentThread.ObjSyncResult = KernelResult.TimedOut; + + if (!KernelTransfer.UserToKernel(out int currentValue, address)) + { + _context.CriticalSection.Leave(); + + return KernelResult.InvalidMemState; + } + + if (currentValue == value) + { + if (timeout == 0) + { + _context.CriticalSection.Leave(); + + return KernelResult.TimedOut; + } + + currentThread.MutexAddress = address; + currentThread.WaitingInArbitration = true; + + _arbiterThreads.Add(currentThread); + + currentThread.Reschedule(ThreadSchedState.Paused); + + if (timeout > 0) + { + _context.TimeManager.ScheduleFutureInvocation(currentThread, timeout); + } + + _context.CriticalSection.Leave(); + + if (timeout > 0) + { + _context.TimeManager.UnscheduleFutureInvocation(currentThread); + } + + _context.CriticalSection.Enter(); + + if (currentThread.WaitingInArbitration) + { + _arbiterThreads.Remove(currentThread); + + currentThread.WaitingInArbitration = false; + } + + _context.CriticalSection.Leave(); + + return currentThread.ObjSyncResult; + } + + _context.CriticalSection.Leave(); + + return KernelResult.InvalidState; + } + + public Result WaitForAddressIfLessThan(ulong address, int value, bool shouldDecrement, long timeout) + { + KThread currentThread = KernelStatic.GetCurrentThread(); + + _context.CriticalSection.Enter(); + + if (currentThread.TerminationRequested) + { + _context.CriticalSection.Leave(); + + return KernelResult.ThreadTerminating; + } + + currentThread.SignaledObj = null; + currentThread.ObjSyncResult = KernelResult.TimedOut; + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + if (!KernelTransfer.UserToKernel(out int currentValue, address)) + { + _context.CriticalSection.Leave(); + + return KernelResult.InvalidMemState; + } + + if (shouldDecrement) + { + currentValue = Interlocked.Decrement(ref currentProcess.CpuMemory.GetRef<int>(address)) + 1; + } + + if (currentValue < value) + { + if (timeout == 0) + { + _context.CriticalSection.Leave(); + + return KernelResult.TimedOut; + } + + currentThread.MutexAddress = address; + currentThread.WaitingInArbitration = true; + + _arbiterThreads.Add(currentThread); + + currentThread.Reschedule(ThreadSchedState.Paused); + + if (timeout > 0) + { + _context.TimeManager.ScheduleFutureInvocation(currentThread, timeout); + } + + _context.CriticalSection.Leave(); + + if (timeout > 0) + { + _context.TimeManager.UnscheduleFutureInvocation(currentThread); + } + + _context.CriticalSection.Enter(); + + if (currentThread.WaitingInArbitration) + { + _arbiterThreads.Remove(currentThread); + + currentThread.WaitingInArbitration = false; + } + + _context.CriticalSection.Leave(); + + return currentThread.ObjSyncResult; + } + + _context.CriticalSection.Leave(); + + return KernelResult.InvalidState; + } + + public Result Signal(ulong address, int count) + { + _context.CriticalSection.Enter(); + + WakeArbiterThreads(address, count); + + _context.CriticalSection.Leave(); + + return Result.Success; + } + + public Result SignalAndIncrementIfEqual(ulong address, int value, int count) + { + _context.CriticalSection.Enter(); + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + if (!currentProcess.CpuMemory.IsMapped(address)) + { + _context.CriticalSection.Leave(); + + return KernelResult.InvalidMemState; + } + + ref int valueRef = ref currentProcess.CpuMemory.GetRef<int>(address); + + int currentValue; + + do + { + currentValue = valueRef; + + if (currentValue != value) + { + _context.CriticalSection.Leave(); + + return KernelResult.InvalidState; + } + } + while (Interlocked.CompareExchange(ref valueRef, currentValue + 1, currentValue) != currentValue); + + WakeArbiterThreads(address, count); + + _context.CriticalSection.Leave(); + + return Result.Success; + } + + public Result SignalAndModifyIfEqual(ulong address, int value, int count) + { + _context.CriticalSection.Enter(); + + int addend; + + // 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) + { + if (count <= 0) + { + addend = -2; + } + else if (waitingCount < count) + { + addend = -1; + } + else + { + addend = 0; + } + } + else + { + addend = 1; + } + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + if (!currentProcess.CpuMemory.IsMapped(address)) + { + _context.CriticalSection.Leave(); + + return KernelResult.InvalidMemState; + } + + ref int valueRef = ref currentProcess.CpuMemory.GetRef<int>(address); + + int currentValue; + + do + { + currentValue = valueRef; + + if (currentValue != value) + { + _context.CriticalSection.Leave(); + + return KernelResult.InvalidState; + } + } + while (Interlocked.CompareExchange(ref valueRef, currentValue + addend, currentValue) != currentValue); + + WakeArbiterThreads(address, count); + + _context.CriticalSection.Leave(); + + return Result.Success; + } + + private void WakeArbiterThreads(ulong address, int count) + { + static void RemoveArbiterThread(KThread thread) + { + thread.SignaledObj = null; + thread.ObjSyncResult = Result.Success; + + thread.ReleaseAndResume(); + + thread.WaitingInArbitration = false; + } + + WakeThreads(_arbiterThreads, count, RemoveArbiterThread, x => x.MutexAddress == address); + } + + private static void WakeThreads( + List<KThread> threads, + int count, + Action<KThread> removeCallback, + Func<KThread, bool> predicate) + { + var candidates = threads.Where(predicate).OrderBy(x => x.DynamicPriority); + var toSignal = (count > 0 ? candidates.Take(count) : candidates).ToArray(); + + foreach (KThread thread in toSignal) + { + removeCallback(thread); + threads.Remove(thread); + } + } + } +} |