aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.HLE/OsHle/Kernel/SvcThreadSync.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Ryujinx.HLE/OsHle/Kernel/SvcThreadSync.cs')
-rw-r--r--Ryujinx.HLE/OsHle/Kernel/SvcThreadSync.cs435
1 files changed, 435 insertions, 0 deletions
diff --git a/Ryujinx.HLE/OsHle/Kernel/SvcThreadSync.cs b/Ryujinx.HLE/OsHle/Kernel/SvcThreadSync.cs
new file mode 100644
index 00000000..030e6e3d
--- /dev/null
+++ b/Ryujinx.HLE/OsHle/Kernel/SvcThreadSync.cs
@@ -0,0 +1,435 @@
+using ChocolArm64.State;
+using Ryujinx.HLE.Logging;
+using Ryujinx.HLE.OsHle.Handles;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+
+using static Ryujinx.HLE.OsHle.ErrorCode;
+
+namespace Ryujinx.HLE.OsHle.Kernel
+{
+ partial class SvcHandler
+ {
+ private const int MutexHasListenersMask = 0x40000000;
+
+ private void SvcArbitrateLock(AThreadState ThreadState)
+ {
+ int OwnerThreadHandle = (int)ThreadState.X0;
+ long MutexAddress = (long)ThreadState.X1;
+ int WaitThreadHandle = (int)ThreadState.X2;
+
+ Ns.Log.PrintDebug(LogClass.KernelSvc,
+ "OwnerThreadHandle = " + OwnerThreadHandle.ToString("x8") + ", " +
+ "MutexAddress = " + MutexAddress .ToString("x16") + ", " +
+ "WaitThreadHandle = " + WaitThreadHandle .ToString("x8"));
+
+ if (IsPointingInsideKernel(MutexAddress))
+ {
+ Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid mutex address 0x{MutexAddress:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
+
+ return;
+ }
+
+ if (IsWordAddressUnaligned(MutexAddress))
+ {
+ Ns.Log.PrintWarning(LogClass.KernelSvc, $"Unaligned mutex address 0x{MutexAddress:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAlignment);
+
+ return;
+ }
+
+ KThread OwnerThread = Process.HandleTable.GetData<KThread>(OwnerThreadHandle);
+
+ if (OwnerThread == null)
+ {
+ Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid owner thread handle 0x{OwnerThreadHandle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+
+ return;
+ }
+
+ KThread WaitThread = Process.HandleTable.GetData<KThread>(WaitThreadHandle);
+
+ if (WaitThread == null)
+ {
+ Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid requesting thread handle 0x{WaitThreadHandle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+
+ return;
+ }
+
+ KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
+
+ MutexLock(CurrThread, WaitThread, OwnerThreadHandle, WaitThreadHandle, MutexAddress);
+
+ ThreadState.X0 = 0;
+ }
+
+ private void SvcArbitrateUnlock(AThreadState ThreadState)
+ {
+ long MutexAddress = (long)ThreadState.X0;
+
+ Ns.Log.PrintDebug(LogClass.KernelSvc, "MutexAddress = " + MutexAddress.ToString("x16"));
+
+ if (IsPointingInsideKernel(MutexAddress))
+ {
+ Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid mutex address 0x{MutexAddress:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
+
+ return;
+ }
+
+ if (IsWordAddressUnaligned(MutexAddress))
+ {
+ Ns.Log.PrintWarning(LogClass.KernelSvc, $"Unaligned mutex address 0x{MutexAddress:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAlignment);
+
+ return;
+ }
+
+ MutexUnlock(Process.GetThread(ThreadState.Tpidr), MutexAddress);
+
+ ThreadState.X0 = 0;
+ }
+
+ private void SvcWaitProcessWideKeyAtomic(AThreadState ThreadState)
+ {
+ long MutexAddress = (long)ThreadState.X0;
+ long CondVarAddress = (long)ThreadState.X1;
+ int ThreadHandle = (int)ThreadState.X2;
+ ulong Timeout = ThreadState.X3;
+
+ Ns.Log.PrintDebug(LogClass.KernelSvc,
+ "MutexAddress = " + MutexAddress .ToString("x16") + ", " +
+ "CondVarAddress = " + CondVarAddress.ToString("x16") + ", " +
+ "ThreadHandle = " + ThreadHandle .ToString("x8") + ", " +
+ "Timeout = " + Timeout .ToString("x16"));
+
+ if (IsPointingInsideKernel(MutexAddress))
+ {
+ Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid mutex address 0x{MutexAddress:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
+
+ return;
+ }
+
+ if (IsWordAddressUnaligned(MutexAddress))
+ {
+ Ns.Log.PrintWarning(LogClass.KernelSvc, $"Unaligned mutex address 0x{MutexAddress:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAlignment);
+
+ return;
+ }
+
+ KThread Thread = Process.HandleTable.GetData<KThread>(ThreadHandle);
+
+ if (Thread == null)
+ {
+ Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{ThreadHandle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+
+ return;
+ }
+
+ KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
+
+ MutexUnlock(CurrThread, MutexAddress);
+
+ if (!CondVarWait(CurrThread, ThreadHandle, MutexAddress, CondVarAddress, Timeout))
+ {
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.Timeout);
+
+ return;
+ }
+
+ ThreadState.X0 = 0;
+ }
+
+ private void SvcSignalProcessWideKey(AThreadState ThreadState)
+ {
+ long CondVarAddress = (long)ThreadState.X0;
+ int Count = (int)ThreadState.X1;
+
+ Ns.Log.PrintDebug(LogClass.KernelSvc,
+ "CondVarAddress = " + CondVarAddress.ToString("x16") + ", " +
+ "Count = " + Count .ToString("x8"));
+
+ KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
+
+ CondVarSignal(CurrThread, CondVarAddress, Count);
+
+ ThreadState.X0 = 0;
+ }
+
+ private void MutexLock(
+ KThread CurrThread,
+ KThread WaitThread,
+ int OwnerThreadHandle,
+ int WaitThreadHandle,
+ long MutexAddress)
+ {
+ lock (Process.ThreadSyncLock)
+ {
+ int MutexValue = Process.Memory.ReadInt32(MutexAddress);
+
+ Ns.Log.PrintDebug(LogClass.KernelSvc, "MutexValue = " + MutexValue.ToString("x8"));
+
+ if (MutexValue != (OwnerThreadHandle | MutexHasListenersMask))
+ {
+ return;
+ }
+
+ CurrThread.WaitHandle = WaitThreadHandle;
+ CurrThread.MutexAddress = MutexAddress;
+
+ InsertWaitingMutexThread(OwnerThreadHandle, WaitThread);
+ }
+
+ Ns.Log.PrintDebug(LogClass.KernelSvc, "Entering wait state...");
+
+ Process.Scheduler.EnterWait(CurrThread);
+ }
+
+ private void MutexUnlock(KThread CurrThread, long MutexAddress)
+ {
+ lock (Process.ThreadSyncLock)
+ {
+ //This is the new thread that will now own the mutex.
+ //If no threads are waiting for the lock, then it should be null.
+ KThread OwnerThread = PopThread(CurrThread.MutexWaiters, x => x.MutexAddress == MutexAddress);
+
+ if (OwnerThread != null)
+ {
+ //Remove all waiting mutex from the old owner,
+ //and insert then on the new owner.
+ UpdateMutexOwner(CurrThread, OwnerThread, MutexAddress);
+
+ CurrThread.UpdatePriority();
+
+ int HasListeners = OwnerThread.MutexWaiters.Count > 0 ? MutexHasListenersMask : 0;
+
+ Process.Memory.WriteInt32(MutexAddress, HasListeners | OwnerThread.WaitHandle);
+
+ OwnerThread.WaitHandle = 0;
+ OwnerThread.MutexAddress = 0;
+ OwnerThread.CondVarAddress = 0;
+
+ OwnerThread.MutexOwner = null;
+
+ OwnerThread.UpdatePriority();
+
+ Process.Scheduler.WakeUp(OwnerThread);
+
+ Ns.Log.PrintDebug(LogClass.KernelSvc, "Gave mutex to thread id " + OwnerThread.ThreadId + "!");
+ }
+ else
+ {
+ Process.Memory.WriteInt32(MutexAddress, 0);
+
+ Ns.Log.PrintDebug(LogClass.KernelSvc, "No threads waiting mutex!");
+ }
+ }
+ }
+
+ private bool CondVarWait(
+ KThread WaitThread,
+ int WaitThreadHandle,
+ long MutexAddress,
+ long CondVarAddress,
+ ulong Timeout)
+ {
+ WaitThread.WaitHandle = WaitThreadHandle;
+ WaitThread.MutexAddress = MutexAddress;
+ WaitThread.CondVarAddress = CondVarAddress;
+
+ lock (Process.ThreadSyncLock)
+ {
+ WaitThread.CondVarSignaled = false;
+
+ Process.ThreadArbiterList.Add(WaitThread);
+ }
+
+ Ns.Log.PrintDebug(LogClass.KernelSvc, "Entering wait state...");
+
+ if (Timeout != ulong.MaxValue)
+ {
+ Process.Scheduler.EnterWait(WaitThread, NsTimeConverter.GetTimeMs(Timeout));
+
+ lock (Process.ThreadSyncLock)
+ {
+ WaitThread.MutexOwner?.MutexWaiters.Remove(WaitThread);
+
+ if (!WaitThread.CondVarSignaled || WaitThread.MutexOwner != null)
+ {
+ WaitThread.MutexOwner = null;
+
+ Process.ThreadArbiterList.Remove(WaitThread);
+
+ Ns.Log.PrintDebug(LogClass.KernelSvc, "Timed out...");
+
+ return false;
+ }
+ }
+ }
+ else
+ {
+ Process.Scheduler.EnterWait(WaitThread);
+ }
+
+ return true;
+ }
+
+ private void CondVarSignal(KThread CurrThread, long CondVarAddress, int Count)
+ {
+ lock (Process.ThreadSyncLock)
+ {
+ while (Count == -1 || Count-- > 0)
+ {
+ KThread WaitThread = PopThread(Process.ThreadArbiterList, x => x.CondVarAddress == CondVarAddress);
+
+ if (WaitThread == null)
+ {
+ Ns.Log.PrintDebug(LogClass.KernelSvc, "No more threads to wake up!");
+
+ break;
+ }
+
+ WaitThread.CondVarSignaled = true;
+
+ AcquireMutexValue(WaitThread.MutexAddress);
+
+ int MutexValue = Process.Memory.ReadInt32(WaitThread.MutexAddress);
+
+ Ns.Log.PrintDebug(LogClass.KernelSvc, "MutexValue = " + MutexValue.ToString("x8"));
+
+ if (MutexValue == 0)
+ {
+ //Give the lock to this thread.
+ Process.Memory.WriteInt32(WaitThread.MutexAddress, WaitThread.WaitHandle);
+
+ WaitThread.WaitHandle = 0;
+ WaitThread.MutexAddress = 0;
+ WaitThread.CondVarAddress = 0;
+
+ WaitThread.MutexOwner?.UpdatePriority();
+
+ WaitThread.MutexOwner = null;
+
+ Process.Scheduler.WakeUp(WaitThread);
+ }
+ else
+ {
+ //Wait until the lock is released.
+ MutexValue &= ~MutexHasListenersMask;
+
+ InsertWaitingMutexThread(MutexValue, WaitThread);
+
+ MutexValue |= MutexHasListenersMask;
+
+ Process.Memory.WriteInt32(WaitThread.MutexAddress, MutexValue);
+ }
+
+ ReleaseMutexValue(WaitThread.MutexAddress);
+ }
+ }
+ }
+
+ private void UpdateMutexOwner(KThread CurrThread, KThread NewOwner, long MutexAddress)
+ {
+ //Go through all threads waiting for the mutex,
+ //and update the MutexOwner field to point to the new owner.
+ lock (Process.ThreadSyncLock)
+ {
+ for (int Index = 0; Index < CurrThread.MutexWaiters.Count; Index++)
+ {
+ KThread Thread = CurrThread.MutexWaiters[Index];
+
+ if (Thread.MutexAddress == MutexAddress)
+ {
+ CurrThread.MutexWaiters.RemoveAt(Index--);
+
+ Thread.MutexOwner = NewOwner;
+
+ InsertWaitingMutexThread(NewOwner, Thread);
+ }
+ }
+ }
+ }
+
+ private void InsertWaitingMutexThread(int OwnerThreadHandle, KThread WaitThread)
+ {
+ KThread OwnerThread = Process.HandleTable.GetData<KThread>(OwnerThreadHandle);
+
+ if (OwnerThread == null)
+ {
+ Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{OwnerThreadHandle:x8}!");
+
+ return;
+ }
+
+ InsertWaitingMutexThread(OwnerThread, WaitThread);
+ }
+
+ private void InsertWaitingMutexThread(KThread OwnerThread, KThread WaitThread)
+ {
+ lock (Process.ThreadSyncLock)
+ {
+ WaitThread.MutexOwner = OwnerThread;
+
+ if (!OwnerThread.MutexWaiters.Contains(WaitThread))
+ {
+ OwnerThread.MutexWaiters.Add(WaitThread);
+
+ OwnerThread.UpdatePriority();
+ }
+ }
+ }
+
+ private KThread PopThread(List<KThread> Threads, Func<KThread, bool> Predicate)
+ {
+ KThread Thread = Threads.OrderBy(x => x.ActualPriority).FirstOrDefault(Predicate);
+
+ if (Thread != null)
+ {
+ Threads.Remove(Thread);
+ }
+
+ return Thread;
+ }
+
+ private void AcquireMutexValue(long MutexAddress)
+ {
+ while (!Process.Memory.AcquireAddress(MutexAddress))
+ {
+ Thread.Yield();
+ }
+ }
+
+ private void ReleaseMutexValue(long MutexAddress)
+ {
+ Process.Memory.ReleaseAddress(MutexAddress);
+ }
+
+ private bool IsPointingInsideKernel(long Address)
+ {
+ return ((ulong)Address + 0x1000000000) < 0xffffff000;
+ }
+
+ private bool IsWordAddressUnaligned(long Address)
+ {
+ return (Address & 3) != 0;
+ }
+ }
+} \ No newline at end of file