aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.HLE/HOS/Kernel/SvcThreadSync.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Ryujinx.HLE/HOS/Kernel/SvcThreadSync.cs')
-rw-r--r--Ryujinx.HLE/HOS/Kernel/SvcThreadSync.cs523
1 files changed, 523 insertions, 0 deletions
diff --git a/Ryujinx.HLE/HOS/Kernel/SvcThreadSync.cs b/Ryujinx.HLE/HOS/Kernel/SvcThreadSync.cs
new file mode 100644
index 00000000..7097d0f7
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/SvcThreadSync.cs
@@ -0,0 +1,523 @@
+using ChocolArm64.State;
+using Ryujinx.HLE.Logging;
+using System;
+
+using static Ryujinx.HLE.HOS.ErrorCode;
+
+namespace Ryujinx.HLE.HOS.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;
+
+ Device.Log.PrintDebug(LogClass.KernelSvc,
+ "OwnerThreadHandle = 0x" + OwnerThreadHandle.ToString("x8") + ", " +
+ "MutexAddress = 0x" + MutexAddress .ToString("x16") + ", " +
+ "WaitThreadHandle = 0x" + WaitThreadHandle .ToString("x8"));
+
+ if (IsPointingInsideKernel(MutexAddress))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid mutex address 0x{MutexAddress:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+
+ return;
+ }
+
+ if (IsAddressNotWordAligned(MutexAddress))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Unaligned mutex address 0x{MutexAddress:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
+
+ return;
+ }
+
+ KThread OwnerThread = Process.HandleTable.GetData<KThread>(OwnerThreadHandle);
+
+ if (OwnerThread == null)
+ {
+ Device.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)
+ {
+ Device.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;
+
+ Device.Log.PrintDebug(LogClass.KernelSvc, "MutexAddress = 0x" + MutexAddress.ToString("x16"));
+
+ if (IsPointingInsideKernel(MutexAddress))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid mutex address 0x{MutexAddress:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+
+ return;
+ }
+
+ if (IsAddressNotWordAligned(MutexAddress))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Unaligned mutex address 0x{MutexAddress:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
+
+ 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;
+
+ Device.Log.PrintDebug(LogClass.KernelSvc,
+ "MutexAddress = 0x" + MutexAddress .ToString("x16") + ", " +
+ "CondVarAddress = 0x" + CondVarAddress.ToString("x16") + ", " +
+ "ThreadHandle = 0x" + ThreadHandle .ToString("x8") + ", " +
+ "Timeout = 0x" + Timeout .ToString("x16"));
+
+ if (IsPointingInsideKernel(MutexAddress))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid mutex address 0x{MutexAddress:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+
+ return;
+ }
+
+ if (IsAddressNotWordAligned(MutexAddress))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Unaligned mutex address 0x{MutexAddress:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
+
+ return;
+ }
+
+ KThread Thread = Process.HandleTable.GetData<KThread>(ThreadHandle);
+
+ if (Thread == null)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{ThreadHandle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+
+ return;
+ }
+
+ KThread WaitThread = Process.GetThread(ThreadState.Tpidr);
+
+ if (!CondVarWait(WaitThread, 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;
+
+ Device.Log.PrintDebug(LogClass.KernelSvc,
+ "CondVarAddress = 0x" + CondVarAddress.ToString("x16") + ", " +
+ "Count = 0x" + Count .ToString("x8"));
+
+ KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
+
+ CondVarSignal(ThreadState, CurrThread, CondVarAddress, Count);
+
+ ThreadState.X0 = 0;
+ }
+
+ private void MutexLock(
+ KThread CurrThread,
+ KThread WaitThread,
+ int OwnerThreadHandle,
+ int WaitThreadHandle,
+ long MutexAddress)
+ {
+ lock (Process.ThreadSyncLock)
+ {
+ int MutexValue = Memory.ReadInt32(MutexAddress);
+
+ Device.Log.PrintDebug(LogClass.KernelSvc, "MutexValue = 0x" + MutexValue.ToString("x8"));
+
+ if (MutexValue != (OwnerThreadHandle | MutexHasListenersMask))
+ {
+ return;
+ }
+
+ CurrThread.WaitHandle = WaitThreadHandle;
+ CurrThread.MutexAddress = MutexAddress;
+
+ InsertWaitingMutexThreadUnsafe(OwnerThreadHandle, WaitThread);
+ }
+
+ Device.Log.PrintDebug(LogClass.KernelSvc, "Entering wait state...");
+
+ Process.Scheduler.EnterWait(CurrThread);
+ }
+
+ private void SvcWaitForAddress(AThreadState ThreadState)
+ {
+ long Address = (long)ThreadState.X0;
+ ArbitrationType Type = (ArbitrationType)ThreadState.X1;
+ int Value = (int)ThreadState.X2;
+ ulong Timeout = ThreadState.X3;
+
+ Device.Log.PrintDebug(LogClass.KernelSvc,
+ "Address = 0x" + Address.ToString("x16") + ", " +
+ "ArbitrationType = 0x" + Type .ToString() + ", " +
+ "Value = 0x" + Value .ToString("x8") + ", " +
+ "Timeout = 0x" + Timeout.ToString("x16"));
+
+ if (IsPointingInsideKernel(Address))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid address 0x{Address:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+
+ return;
+ }
+
+ if (IsAddressNotWordAligned(Address))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Unaligned address 0x{Address:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
+
+ return;
+ }
+
+ switch (Type)
+ {
+ case ArbitrationType.WaitIfLessThan:
+ ThreadState.X0 = AddressArbiter.WaitForAddressIfLessThan(Process, ThreadState, Memory, Address, Value, Timeout, false);
+ break;
+
+ case ArbitrationType.DecrementAndWaitIfLessThan:
+ ThreadState.X0 = AddressArbiter.WaitForAddressIfLessThan(Process, ThreadState, Memory, Address, Value, Timeout, true);
+ break;
+
+ case ArbitrationType.WaitIfEqual:
+ ThreadState.X0 = AddressArbiter.WaitForAddressIfEqual(Process, ThreadState, Memory, Address, Value, Timeout);
+ break;
+
+ default:
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidEnumValue);
+ break;
+ }
+ }
+
+ 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, int Count) = PopMutexThreadUnsafe(CurrThread, MutexAddress);
+
+ if (OwnerThread == CurrThread)
+ {
+ throw new InvalidOperationException();
+ }
+
+ if (OwnerThread != null)
+ {
+ //Remove all waiting mutex from the old owner,
+ //and insert then on the new owner.
+ UpdateMutexOwnerUnsafe(CurrThread, OwnerThread, MutexAddress);
+
+ CurrThread.UpdatePriority();
+
+ int HasListeners = Count >= 2 ? MutexHasListenersMask : 0;
+
+ Memory.WriteInt32ToSharedAddr(MutexAddress, HasListeners | OwnerThread.WaitHandle);
+
+ OwnerThread.WaitHandle = 0;
+ OwnerThread.MutexAddress = 0;
+ OwnerThread.CondVarAddress = 0;
+ OwnerThread.MutexOwner = null;
+
+ OwnerThread.UpdatePriority();
+
+ Process.Scheduler.WakeUp(OwnerThread);
+
+ Device.Log.PrintDebug(LogClass.KernelSvc, "Gave mutex to thread id " + OwnerThread.ThreadId + "!");
+ }
+ else
+ {
+ Memory.WriteInt32ToSharedAddr(MutexAddress, 0);
+
+ Device.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)
+ {
+ MutexUnlock(WaitThread, MutexAddress);
+
+ WaitThread.CondVarSignaled = false;
+
+ Process.ThreadArbiterList.Add(WaitThread);
+ }
+
+ Device.Log.PrintDebug(LogClass.KernelSvc, "Entering wait state...");
+
+ if (Timeout != ulong.MaxValue)
+ {
+ Process.Scheduler.EnterWait(WaitThread, NsTimeConverter.GetTimeMs(Timeout));
+
+ lock (Process.ThreadSyncLock)
+ {
+ if (!WaitThread.CondVarSignaled || WaitThread.MutexOwner != null)
+ {
+ if (WaitThread.MutexOwner != null)
+ {
+ WaitThread.MutexOwner.MutexWaiters.Remove(WaitThread);
+ WaitThread.MutexOwner.UpdatePriority();
+
+ WaitThread.MutexOwner = null;
+ }
+
+ Process.ThreadArbiterList.Remove(WaitThread);
+
+ Device.Log.PrintDebug(LogClass.KernelSvc, "Timed out...");
+
+ return false;
+ }
+ }
+ }
+ else
+ {
+ Process.Scheduler.EnterWait(WaitThread);
+ }
+
+ return true;
+ }
+
+ private void CondVarSignal(
+ AThreadState ThreadState,
+ KThread CurrThread,
+ long CondVarAddress,
+ int Count)
+ {
+ lock (Process.ThreadSyncLock)
+ {
+ while (Count == -1 || Count-- > 0)
+ {
+ KThread WaitThread = PopCondVarThreadUnsafe(CondVarAddress);
+
+ if (WaitThread == null)
+ {
+ Device.Log.PrintDebug(LogClass.KernelSvc, "No more threads to wake up!");
+
+ break;
+ }
+
+ WaitThread.CondVarSignaled = true;
+
+ long MutexAddress = WaitThread.MutexAddress;
+
+ Memory.SetExclusive(ThreadState, MutexAddress);
+
+ int MutexValue = Memory.ReadInt32(MutexAddress);
+
+ while (MutexValue != 0)
+ {
+ if (Memory.TestExclusive(ThreadState, MutexAddress))
+ {
+ //Wait until the lock is released.
+ InsertWaitingMutexThreadUnsafe(MutexValue & ~MutexHasListenersMask, WaitThread);
+
+ Memory.WriteInt32(MutexAddress, MutexValue | MutexHasListenersMask);
+
+ Memory.ClearExclusiveForStore(ThreadState);
+
+ break;
+ }
+
+ Memory.SetExclusive(ThreadState, MutexAddress);
+
+ MutexValue = Memory.ReadInt32(MutexAddress);
+ }
+
+ Device.Log.PrintDebug(LogClass.KernelSvc, "MutexValue = 0x" + MutexValue.ToString("x8"));
+
+ if (MutexValue == 0)
+ {
+ //Give the lock to this thread.
+ Memory.WriteInt32ToSharedAddr(MutexAddress, WaitThread.WaitHandle);
+
+ WaitThread.WaitHandle = 0;
+ WaitThread.MutexAddress = 0;
+ WaitThread.CondVarAddress = 0;
+
+ WaitThread.MutexOwner?.UpdatePriority();
+
+ WaitThread.MutexOwner = null;
+
+ Process.Scheduler.WakeUp(WaitThread);
+ }
+ }
+ }
+ }
+
+ private void UpdateMutexOwnerUnsafe(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.
+ for (int Index = 0; Index < CurrThread.MutexWaiters.Count; Index++)
+ {
+ KThread Thread = CurrThread.MutexWaiters[Index];
+
+ if (Thread.MutexAddress == MutexAddress)
+ {
+ CurrThread.MutexWaiters.RemoveAt(Index--);
+
+ InsertWaitingMutexThreadUnsafe(NewOwner, Thread);
+ }
+ }
+ }
+
+ private void InsertWaitingMutexThreadUnsafe(int OwnerThreadHandle, KThread WaitThread)
+ {
+ KThread OwnerThread = Process.HandleTable.GetData<KThread>(OwnerThreadHandle);
+
+ if (OwnerThread == null)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{OwnerThreadHandle:x8}!");
+
+ return;
+ }
+
+ InsertWaitingMutexThreadUnsafe(OwnerThread, WaitThread);
+ }
+
+ private void InsertWaitingMutexThreadUnsafe(KThread OwnerThread, KThread WaitThread)
+ {
+ WaitThread.MutexOwner = OwnerThread;
+
+ if (!OwnerThread.MutexWaiters.Contains(WaitThread))
+ {
+ OwnerThread.MutexWaiters.Add(WaitThread);
+
+ OwnerThread.UpdatePriority();
+ }
+ }
+
+ private (KThread, int) PopMutexThreadUnsafe(KThread OwnerThread, long MutexAddress)
+ {
+ int Count = 0;
+
+ KThread WakeThread = null;
+
+ foreach (KThread Thread in OwnerThread.MutexWaiters)
+ {
+ if (Thread.MutexAddress != MutexAddress)
+ {
+ continue;
+ }
+
+ if (WakeThread == null || Thread.ActualPriority < WakeThread.ActualPriority)
+ {
+ WakeThread = Thread;
+ }
+
+ Count++;
+ }
+
+ if (WakeThread != null)
+ {
+ OwnerThread.MutexWaiters.Remove(WakeThread);
+ }
+
+ return (WakeThread, Count);
+ }
+
+ private KThread PopCondVarThreadUnsafe(long CondVarAddress)
+ {
+ KThread WakeThread = null;
+
+ foreach (KThread Thread in Process.ThreadArbiterList)
+ {
+ if (Thread.CondVarAddress != CondVarAddress)
+ {
+ continue;
+ }
+
+ if (WakeThread == null || Thread.ActualPriority < WakeThread.ActualPriority)
+ {
+ WakeThread = Thread;
+ }
+ }
+
+ if (WakeThread != null)
+ {
+ Process.ThreadArbiterList.Remove(WakeThread);
+ }
+
+ return WakeThread;
+ }
+
+ private bool IsPointingInsideKernel(long Address)
+ {
+ return ((ulong)Address + 0x1000000000) < 0xffffff000;
+ }
+
+ private bool IsAddressNotWordAligned(long Address)
+ {
+ return (Address & 3) != 0;
+ }
+ }
+} \ No newline at end of file