aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.HLE/HOS/Kernel/Threading
diff options
context:
space:
mode:
authorgdkchan <gab.dark.100@gmail.com>2018-12-18 03:33:36 -0200
committerGitHub <noreply@github.com>2018-12-18 03:33:36 -0200
commit0039bb639493b2d1e2764cae380311ba8e87704b (patch)
tree63a912a95c8261775c2acb8a5b9ca0f10ad4ae33 /Ryujinx.HLE/HOS/Kernel/Threading
parent2534a7f10c627810e6e0272b4cc9758e90f733c1 (diff)
Refactor SVC handler (#540)
* Refactor SVC handler * Get rid of KernelErr * Split kernel code files into multiple folders
Diffstat (limited to 'Ryujinx.HLE/HOS/Kernel/Threading')
-rw-r--r--Ryujinx.HLE/HOS/Kernel/Threading/ArbitrationType.cs9
-rw-r--r--Ryujinx.HLE/HOS/Kernel/Threading/HleCoreManager.cs66
-rw-r--r--Ryujinx.HLE/HOS/Kernel/Threading/HleScheduler.cs149
-rw-r--r--Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs654
-rw-r--r--Ryujinx.HLE/HOS/Kernel/Threading/KConditionVariable.cs71
-rw-r--r--Ryujinx.HLE/HOS/Kernel/Threading/KCoreContext.cs81
-rw-r--r--Ryujinx.HLE/HOS/Kernel/Threading/KCriticalSection.cs93
-rw-r--r--Ryujinx.HLE/HOS/Kernel/Threading/KEvent.cs14
-rw-r--r--Ryujinx.HLE/HOS/Kernel/Threading/KReadableEvent.cs64
-rw-r--r--Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs234
-rw-r--r--Ryujinx.HLE/HOS/Kernel/Threading/KSchedulingData.cs207
-rw-r--r--Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs136
-rw-r--r--Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs1030
-rw-r--r--Ryujinx.HLE/HOS/Kernel/Threading/KWritableEvent.cs24
-rw-r--r--Ryujinx.HLE/HOS/Kernel/Threading/SignalType.cs9
-rw-r--r--Ryujinx.HLE/HOS/Kernel/Threading/ThreadSchedState.cs19
-rw-r--r--Ryujinx.HLE/HOS/Kernel/Threading/ThreadType.cs10
17 files changed, 2870 insertions, 0 deletions
diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/ArbitrationType.cs b/Ryujinx.HLE/HOS/Kernel/Threading/ArbitrationType.cs
new file mode 100644
index 00000000..89c1bf1f
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/Threading/ArbitrationType.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.HLE.HOS.Kernel.Threading
+{
+ enum ArbitrationType
+ {
+ WaitIfLessThan = 0,
+ DecrementAndWaitIfLessThan = 1,
+ WaitIfEqual = 2
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/HleCoreManager.cs b/Ryujinx.HLE/HOS/Kernel/Threading/HleCoreManager.cs
new file mode 100644
index 00000000..c2597990
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/Threading/HleCoreManager.cs
@@ -0,0 +1,66 @@
+using System.Collections.Concurrent;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Kernel.Threading
+{
+ class HleCoreManager
+ {
+ private class PausableThread
+ {
+ public ManualResetEvent Event { get; private set; }
+
+ public bool IsExiting { get; set; }
+
+ public PausableThread()
+ {
+ Event = new ManualResetEvent(false);
+ }
+ }
+
+ private ConcurrentDictionary<Thread, PausableThread> _threads;
+
+ public HleCoreManager()
+ {
+ _threads = new ConcurrentDictionary<Thread, PausableThread>();
+ }
+
+ public void Set(Thread thread)
+ {
+ GetThread(thread).Event.Set();
+ }
+
+ public void Reset(Thread thread)
+ {
+ GetThread(thread).Event.Reset();
+ }
+
+ public void Wait(Thread thread)
+ {
+ PausableThread pausableThread = GetThread(thread);
+
+ if (!pausableThread.IsExiting)
+ {
+ pausableThread.Event.WaitOne();
+ }
+ }
+
+ public void Exit(Thread thread)
+ {
+ GetThread(thread).IsExiting = true;
+ }
+
+ private PausableThread GetThread(Thread thread)
+ {
+ return _threads.GetOrAdd(thread, (key) => new PausableThread());
+ }
+
+ public void RemoveThread(Thread thread)
+ {
+ if (_threads.TryRemove(thread, out PausableThread pausableThread))
+ {
+ pausableThread.Event.Set();
+ pausableThread.Event.Dispose();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/HleScheduler.cs b/Ryujinx.HLE/HOS/Kernel/Threading/HleScheduler.cs
new file mode 100644
index 00000000..835c2a2f
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/Threading/HleScheduler.cs
@@ -0,0 +1,149 @@
+using System;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Kernel.Threading
+{
+ partial class KScheduler
+ {
+ private const int RoundRobinTimeQuantumMs = 10;
+
+ private int _currentCore;
+
+ public bool MultiCoreScheduling { get; set; }
+
+ public HleCoreManager CoreManager { get; private set; }
+
+ private bool _keepPreempting;
+
+ public void StartAutoPreemptionThread()
+ {
+ Thread preemptionThread = new Thread(PreemptCurrentThread);
+
+ _keepPreempting = true;
+
+ preemptionThread.Start();
+ }
+
+ public void ContextSwitch()
+ {
+ lock (CoreContexts)
+ {
+ if (MultiCoreScheduling)
+ {
+ int selectedCount = 0;
+
+ for (int core = 0; core < CpuCoresCount; core++)
+ {
+ KCoreContext coreContext = CoreContexts[core];
+
+ if (coreContext.ContextSwitchNeeded && (coreContext.CurrentThread?.Context.IsCurrentThread() ?? false))
+ {
+ coreContext.ContextSwitch();
+ }
+
+ if (coreContext.CurrentThread?.Context.IsCurrentThread() ?? false)
+ {
+ selectedCount++;
+ }
+ }
+
+ if (selectedCount == 0)
+ {
+ CoreManager.Reset(Thread.CurrentThread);
+ }
+ else if (selectedCount == 1)
+ {
+ CoreManager.Set(Thread.CurrentThread);
+ }
+ else
+ {
+ throw new InvalidOperationException("Thread scheduled in more than one core!");
+ }
+ }
+ else
+ {
+ KThread currentThread = CoreContexts[_currentCore].CurrentThread;
+
+ bool hasThreadExecuting = currentThread != null;
+
+ if (hasThreadExecuting)
+ {
+ //If this is not the thread that is currently executing, we need
+ //to request an interrupt to allow safely starting another thread.
+ if (!currentThread.Context.IsCurrentThread())
+ {
+ currentThread.Context.RequestInterrupt();
+
+ return;
+ }
+
+ CoreManager.Reset(currentThread.Context.Work);
+ }
+
+ //Advance current core and try picking a thread,
+ //keep advancing if it is null.
+ for (int core = 0; core < 4; core++)
+ {
+ _currentCore = (_currentCore + 1) % CpuCoresCount;
+
+ KCoreContext coreContext = CoreContexts[_currentCore];
+
+ coreContext.UpdateCurrentThread();
+
+ if (coreContext.CurrentThread != null)
+ {
+ coreContext.CurrentThread.ClearExclusive();
+
+ CoreManager.Set(coreContext.CurrentThread.Context.Work);
+
+ coreContext.CurrentThread.Context.Execute();
+
+ break;
+ }
+ }
+
+ //If nothing was running before, then we are on a "external"
+ //HLE thread, we don't need to wait.
+ if (!hasThreadExecuting)
+ {
+ return;
+ }
+ }
+ }
+
+ CoreManager.Wait(Thread.CurrentThread);
+ }
+
+ private void PreemptCurrentThread()
+ {
+ //Preempts current thread every 10 milliseconds on a round-robin fashion,
+ //when multi core scheduling is disabled, to try ensuring that all threads
+ //gets a chance to run.
+ while (_keepPreempting)
+ {
+ lock (CoreContexts)
+ {
+ KThread currentThread = CoreContexts[_currentCore].CurrentThread;
+
+ currentThread?.Context.RequestInterrupt();
+ }
+
+ PreemptThreads();
+
+ Thread.Sleep(RoundRobinTimeQuantumMs);
+ }
+ }
+
+ public void ExitThread(KThread thread)
+ {
+ thread.Context.StopExecution();
+
+ CoreManager.Exit(thread.Context.Work);
+ }
+
+ public void RemoveThread(KThread thread)
+ {
+ CoreManager.RemoveThread(thread.Context.Work);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs
new file mode 100644
index 00000000..faeea5c5
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs
@@ -0,0 +1,654 @@
+using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.HLE.HOS.Kernel.Process;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Ryujinx.HLE.HOS.Kernel.Threading
+{
+ class KAddressArbiter
+ {
+ private const int HasListenersMask = 0x40000000;
+
+ private Horizon _system;
+
+ public List<KThread> CondVarThreads;
+ public List<KThread> ArbiterThreads;
+
+ public KAddressArbiter(Horizon system)
+ {
+ _system = system;
+
+ CondVarThreads = new List<KThread>();
+ ArbiterThreads = new List<KThread>();
+ }
+
+ public KernelResult ArbitrateLock(int ownerHandle, ulong mutexAddress, int requesterHandle)
+ {
+ KThread currentThread = _system.Scheduler.GetCurrentThread();
+
+ _system.CriticalSection.Enter();
+
+ currentThread.SignaledObj = null;
+ currentThread.ObjSyncResult = KernelResult.Success;
+
+ KProcess currentProcess = _system.Scheduler.GetCurrentProcess();
+
+ if (!KernelTransfer.UserToKernelInt32(_system, mutexAddress, out int mutexValue))
+ {
+ _system.CriticalSection.Leave();
+
+ return KernelResult.InvalidMemState;
+ }
+
+ if (mutexValue != (ownerHandle | HasListenersMask))
+ {
+ _system.CriticalSection.Leave();
+
+ return 0;
+ }
+
+ KThread mutexOwner = currentProcess.HandleTable.GetObject<KThread>(ownerHandle);
+
+ if (mutexOwner == null)
+ {
+ _system.CriticalSection.Leave();
+
+ return KernelResult.InvalidHandle;
+ }
+
+ currentThread.MutexAddress = mutexAddress;
+ currentThread.ThreadHandleForUserMutex = requesterHandle;
+
+ mutexOwner.AddMutexWaiter(currentThread);
+
+ currentThread.Reschedule(ThreadSchedState.Paused);
+
+ _system.CriticalSection.Leave();
+ _system.CriticalSection.Enter();
+
+ if (currentThread.MutexOwner != null)
+ {
+ currentThread.MutexOwner.RemoveMutexWaiter(currentThread);
+ }
+
+ _system.CriticalSection.Leave();
+
+ return (KernelResult)currentThread.ObjSyncResult;
+ }
+
+ public KernelResult ArbitrateUnlock(ulong mutexAddress)
+ {
+ _system.CriticalSection.Enter();
+
+ KThread currentThread = _system.Scheduler.GetCurrentThread();
+
+ (KernelResult result, KThread newOwnerThread) = MutexUnlock(currentThread, mutexAddress);
+
+ if (result != KernelResult.Success && newOwnerThread != null)
+ {
+ newOwnerThread.SignaledObj = null;
+ newOwnerThread.ObjSyncResult = result;
+ }
+
+ _system.CriticalSection.Leave();
+
+ return result;
+ }
+
+ public KernelResult WaitProcessWideKeyAtomic(
+ ulong mutexAddress,
+ ulong condVarAddress,
+ int threadHandle,
+ long timeout)
+ {
+ _system.CriticalSection.Enter();
+
+ KThread currentThread = _system.Scheduler.GetCurrentThread();
+
+ currentThread.SignaledObj = null;
+ currentThread.ObjSyncResult = KernelResult.TimedOut;
+
+ if (currentThread.ShallBeTerminated ||
+ currentThread.SchedFlags == ThreadSchedState.TerminationPending)
+ {
+ _system.CriticalSection.Leave();
+
+ return KernelResult.ThreadTerminating;
+ }
+
+ (KernelResult result, _) = MutexUnlock(currentThread, mutexAddress);
+
+ if (result != KernelResult.Success)
+ {
+ _system.CriticalSection.Leave();
+
+ 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.CriticalSection.Leave();
+
+ if (timeout > 0)
+ {
+ _system.TimeManager.UnscheduleFutureInvocation(currentThread);
+ }
+
+ _system.CriticalSection.Enter();
+
+ if (currentThread.MutexOwner != null)
+ {
+ currentThread.MutexOwner.RemoveMutexWaiter(currentThread);
+ }
+
+ CondVarThreads.Remove(currentThread);
+
+ _system.CriticalSection.Leave();
+
+ return (KernelResult)currentThread.ObjSyncResult;
+ }
+
+ private (KernelResult, 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 = KernelResult.Success;
+
+ newOwnerThread.ReleaseAndResume();
+ }
+
+ KernelResult result = KernelResult.Success;
+
+ if (!KernelTransfer.KernelToUserInt32(_system, mutexAddress, mutexValue))
+ {
+ result = KernelResult.InvalidMemState;
+ }
+
+ return (result, newOwnerThread);
+ }
+
+ public void SignalProcessWideKey(ulong address, int count)
+ {
+ Queue<KThread> signaledThreads = new Queue<KThread>();
+
+ _system.CriticalSection.Enter();
+
+ IOrderedEnumerable<KThread> sortedThreads = CondVarThreads.OrderBy(x => x.DynamicPriority);
+
+ foreach (KThread thread in sortedThreads.Where(x => x.CondVarAddress == address))
+ {
+ TryAcquireMutex(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.CriticalSection.Leave();
+ }
+
+ private KThread TryAcquireMutex(KThread requester)
+ {
+ ulong address = requester.MutexAddress;
+
+ KProcess currentProcess = _system.Scheduler.GetCurrentProcess();
+
+ currentProcess.CpuMemory.SetExclusive(0, (long)address);
+
+ if (!KernelTransfer.UserToKernelInt32(_system, address, out int mutexValue))
+ {
+ //Invalid address.
+ currentProcess.CpuMemory.ClearExclusive(0);
+
+ requester.SignaledObj = null;
+ requester.ObjSyncResult = KernelResult.InvalidMemState;
+
+ return null;
+ }
+
+ while (true)
+ {
+ if (currentProcess.CpuMemory.TestExclusive(0, (long)address))
+ {
+ if (mutexValue != 0)
+ {
+ //Update value to indicate there is a mutex waiter now.
+ currentProcess.CpuMemory.WriteInt32((long)address, mutexValue | HasListenersMask);
+ }
+ else
+ {
+ //No thread owning the mutex, assign to requesting thread.
+ currentProcess.CpuMemory.WriteInt32((long)address, requester.ThreadHandleForUserMutex);
+ }
+
+ currentProcess.CpuMemory.ClearExclusiveForStore(0);
+
+ break;
+ }
+
+ currentProcess.CpuMemory.SetExclusive(0, (long)address);
+
+ mutexValue = currentProcess.CpuMemory.ReadInt32((long)address);
+ }
+
+ if (mutexValue == 0)
+ {
+ //We now own the mutex.
+ requester.SignaledObj = null;
+ requester.ObjSyncResult = KernelResult.Success;
+
+ requester.ReleaseAndResume();
+
+ return null;
+ }
+
+ 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();
+ }
+
+ return mutexOwner;
+ }
+
+ public KernelResult WaitForAddressIfEqual(ulong address, int value, long timeout)
+ {
+ KThread currentThread = _system.Scheduler.GetCurrentThread();
+
+ _system.CriticalSection.Enter();
+
+ if (currentThread.ShallBeTerminated ||
+ currentThread.SchedFlags == ThreadSchedState.TerminationPending)
+ {
+ _system.CriticalSection.Leave();
+
+ return KernelResult.ThreadTerminating;
+ }
+
+ currentThread.SignaledObj = null;
+ currentThread.ObjSyncResult = KernelResult.TimedOut;
+
+ if (!KernelTransfer.UserToKernelInt32(_system, address, out int currentValue))
+ {
+ _system.CriticalSection.Leave();
+
+ return KernelResult.InvalidMemState;
+ }
+
+ if (currentValue == value)
+ {
+ if (timeout == 0)
+ {
+ _system.CriticalSection.Leave();
+
+ return KernelResult.TimedOut;
+ }
+
+ currentThread.MutexAddress = address;
+ currentThread.WaitingInArbitration = true;
+
+ InsertSortedByPriority(ArbiterThreads, currentThread);
+
+ currentThread.Reschedule(ThreadSchedState.Paused);
+
+ if (timeout > 0)
+ {
+ _system.TimeManager.ScheduleFutureInvocation(currentThread, timeout);
+ }
+
+ _system.CriticalSection.Leave();
+
+ if (timeout > 0)
+ {
+ _system.TimeManager.UnscheduleFutureInvocation(currentThread);
+ }
+
+ _system.CriticalSection.Enter();
+
+ if (currentThread.WaitingInArbitration)
+ {
+ ArbiterThreads.Remove(currentThread);
+
+ currentThread.WaitingInArbitration = false;
+ }
+
+ _system.CriticalSection.Leave();
+
+ return (KernelResult)currentThread.ObjSyncResult;
+ }
+
+ _system.CriticalSection.Leave();
+
+ return KernelResult.InvalidState;
+ }
+
+ public KernelResult WaitForAddressIfLessThan(
+ ulong address,
+ int value,
+ bool shouldDecrement,
+ long timeout)
+ {
+ KThread currentThread = _system.Scheduler.GetCurrentThread();
+
+ _system.CriticalSection.Enter();
+
+ if (currentThread.ShallBeTerminated ||
+ currentThread.SchedFlags == ThreadSchedState.TerminationPending)
+ {
+ _system.CriticalSection.Leave();
+
+ return KernelResult.ThreadTerminating;
+ }
+
+ currentThread.SignaledObj = null;
+ currentThread.ObjSyncResult = KernelResult.TimedOut;
+
+ KProcess currentProcess = _system.Scheduler.GetCurrentProcess();
+
+ //If ShouldDecrement is true, do atomic decrement of the value at Address.
+ currentProcess.CpuMemory.SetExclusive(0, (long)address);
+
+ if (!KernelTransfer.UserToKernelInt32(_system, address, out int currentValue))
+ {
+ _system.CriticalSection.Leave();
+
+ return KernelResult.InvalidMemState;
+ }
+
+ if (shouldDecrement)
+ {
+ while (currentValue < value)
+ {
+ if (currentProcess.CpuMemory.TestExclusive(0, (long)address))
+ {
+ currentProcess.CpuMemory.WriteInt32((long)address, currentValue - 1);
+
+ currentProcess.CpuMemory.ClearExclusiveForStore(0);
+
+ break;
+ }
+
+ currentProcess.CpuMemory.SetExclusive(0, (long)address);
+
+ currentValue = currentProcess.CpuMemory.ReadInt32((long)address);
+ }
+ }
+
+ currentProcess.CpuMemory.ClearExclusive(0);
+
+ if (currentValue < value)
+ {
+ if (timeout == 0)
+ {
+ _system.CriticalSection.Leave();
+
+ return KernelResult.TimedOut;
+ }
+
+ currentThread.MutexAddress = address;
+ currentThread.WaitingInArbitration = true;
+
+ InsertSortedByPriority(ArbiterThreads, currentThread);
+
+ currentThread.Reschedule(ThreadSchedState.Paused);
+
+ if (timeout > 0)
+ {
+ _system.TimeManager.ScheduleFutureInvocation(currentThread, timeout);
+ }
+
+ _system.CriticalSection.Leave();
+
+ if (timeout > 0)
+ {
+ _system.TimeManager.UnscheduleFutureInvocation(currentThread);
+ }
+
+ _system.CriticalSection.Enter();
+
+ if (currentThread.WaitingInArbitration)
+ {
+ ArbiterThreads.Remove(currentThread);
+
+ currentThread.WaitingInArbitration = false;
+ }
+
+ _system.CriticalSection.Leave();
+
+ return (KernelResult)currentThread.ObjSyncResult;
+ }
+
+ _system.CriticalSection.Leave();
+
+ return KernelResult.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 KernelResult Signal(ulong address, int count)
+ {
+ _system.CriticalSection.Enter();
+
+ WakeArbiterThreads(address, count);
+
+ _system.CriticalSection.Leave();
+
+ return KernelResult.Success;
+ }
+
+ public KernelResult SignalAndIncrementIfEqual(ulong address, int value, int count)
+ {
+ _system.CriticalSection.Enter();
+
+ KProcess currentProcess = _system.Scheduler.GetCurrentProcess();
+
+ currentProcess.CpuMemory.SetExclusive(0, (long)address);
+
+ if (!KernelTransfer.UserToKernelInt32(_system, address, out int currentValue))
+ {
+ _system.CriticalSection.Leave();
+
+ return KernelResult.InvalidMemState;
+ }
+
+ while (currentValue == value)
+ {
+ if (currentProcess.CpuMemory.TestExclusive(0, (long)address))
+ {
+ currentProcess.CpuMemory.WriteInt32((long)address, currentValue + 1);
+
+ currentProcess.CpuMemory.ClearExclusiveForStore(0);
+
+ break;
+ }
+
+ currentProcess.CpuMemory.SetExclusive(0, (long)address);
+
+ currentValue = currentProcess.CpuMemory.ReadInt32((long)address);
+ }
+
+ currentProcess.CpuMemory.ClearExclusive(0);
+
+ if (currentValue != value)
+ {
+ _system.CriticalSection.Leave();
+
+ return KernelResult.InvalidState;
+ }
+
+ WakeArbiterThreads(address, count);
+
+ _system.CriticalSection.Leave();
+
+ return KernelResult.Success;
+ }
+
+ public KernelResult SignalAndModifyIfEqual(ulong address, int value, int count)
+ {
+ _system.CriticalSection.Enter();
+
+ 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;
+ }
+
+ KProcess currentProcess = _system.Scheduler.GetCurrentProcess();
+
+ currentProcess.CpuMemory.SetExclusive(0, (long)address);
+
+ if (!KernelTransfer.UserToKernelInt32(_system, address, out int currentValue))
+ {
+ _system.CriticalSection.Leave();
+
+ return KernelResult.InvalidMemState;
+ }
+
+ while (currentValue == value)
+ {
+ if (currentProcess.CpuMemory.TestExclusive(0, (long)address))
+ {
+ currentProcess.CpuMemory.WriteInt32((long)address, currentValue + offset);
+
+ currentProcess.CpuMemory.ClearExclusiveForStore(0);
+
+ break;
+ }
+
+ currentProcess.CpuMemory.SetExclusive(0, (long)address);
+
+ currentValue = currentProcess.CpuMemory.ReadInt32((long)address);
+ }
+
+ currentProcess.CpuMemory.ClearExclusive(0);
+
+ if (currentValue != value)
+ {
+ _system.CriticalSection.Leave();
+
+ return KernelResult.InvalidState;
+ }
+
+ WakeArbiterThreads(address, count);
+
+ _system.CriticalSection.Leave();
+
+ return KernelResult.Success;
+ }
+
+ private void WakeArbiterThreads(ulong 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 = KernelResult.Success;
+
+ thread.ReleaseAndResume();
+
+ thread.WaitingInArbitration = false;
+
+ ArbiterThreads.Remove(thread);
+ }
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KConditionVariable.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KConditionVariable.cs
new file mode 100644
index 00000000..41473643
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/Threading/KConditionVariable.cs
@@ -0,0 +1,71 @@
+using System.Collections.Generic;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Kernel.Threading
+{
+ static class KConditionVariable
+ {
+ public static void Wait(Horizon system, LinkedList<KThread> threadList, object mutex, long timeout)
+ {
+ KThread currentThread = system.Scheduler.GetCurrentThread();
+
+ system.CriticalSection.Enter();
+
+ Monitor.Exit(mutex);
+
+ currentThread.Withholder = threadList;
+
+ currentThread.Reschedule(ThreadSchedState.Paused);
+
+ currentThread.WithholderNode = threadList.AddLast(currentThread);
+
+ if (currentThread.ShallBeTerminated ||
+ currentThread.SchedFlags == ThreadSchedState.TerminationPending)
+ {
+ threadList.Remove(currentThread.WithholderNode);
+
+ currentThread.Reschedule(ThreadSchedState.Running);
+
+ currentThread.Withholder = null;
+
+ system.CriticalSection.Leave();
+ }
+ else
+ {
+ if (timeout > 0)
+ {
+ system.TimeManager.ScheduleFutureInvocation(currentThread, timeout);
+ }
+
+ system.CriticalSection.Leave();
+
+ if (timeout > 0)
+ {
+ system.TimeManager.UnscheduleFutureInvocation(currentThread);
+ }
+ }
+
+ Monitor.Enter(mutex);
+ }
+
+ public static void NotifyAll(Horizon system, LinkedList<KThread> threadList)
+ {
+ system.CriticalSection.Enter();
+
+ LinkedListNode<KThread> node = threadList.First;
+
+ for (; node != null; node = threadList.First)
+ {
+ KThread thread = node.Value;
+
+ threadList.Remove(thread.WithholderNode);
+
+ thread.Withholder = null;
+
+ thread.Reschedule(ThreadSchedState.Running);
+ }
+
+ system.CriticalSection.Leave();
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KCoreContext.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KCoreContext.cs
new file mode 100644
index 00000000..81cd8883
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/Threading/KCoreContext.cs
@@ -0,0 +1,81 @@
+using Ryujinx.Common;
+
+namespace Ryujinx.HLE.HOS.Kernel.Threading
+{
+ class KCoreContext
+ {
+ private KScheduler _scheduler;
+
+ private HleCoreManager _coreManager;
+
+ public bool ContextSwitchNeeded { get; private set; }
+
+ public long LastContextSwitchTime { get; private set; }
+
+ public long TotalIdleTimeTicks { get; private set; } //TODO
+
+ public KThread CurrentThread { get; private set; }
+ public KThread SelectedThread { get; private set; }
+
+ public KCoreContext(KScheduler scheduler, HleCoreManager coreManager)
+ {
+ _scheduler = scheduler;
+ _coreManager = coreManager;
+ }
+
+ public void SelectThread(KThread thread)
+ {
+ SelectedThread = thread;
+
+ if (SelectedThread != CurrentThread)
+ {
+ ContextSwitchNeeded = true;
+ }
+ }
+
+ public void UpdateCurrentThread()
+ {
+ ContextSwitchNeeded = false;
+
+ LastContextSwitchTime = PerformanceCounter.ElapsedMilliseconds;
+
+ CurrentThread = SelectedThread;
+
+ if (CurrentThread != null)
+ {
+ long currentTime = PerformanceCounter.ElapsedMilliseconds;
+
+ CurrentThread.TotalTimeRunning += currentTime - CurrentThread.LastScheduledTime;
+ CurrentThread.LastScheduledTime = currentTime;
+ }
+ }
+
+ public void ContextSwitch()
+ {
+ ContextSwitchNeeded = false;
+
+ LastContextSwitchTime = PerformanceCounter.ElapsedMilliseconds;
+
+ if (CurrentThread != null)
+ {
+ _coreManager.Reset(CurrentThread.Context.Work);
+ }
+
+ CurrentThread = SelectedThread;
+
+ if (CurrentThread != null)
+ {
+ long currentTime = PerformanceCounter.ElapsedMilliseconds;
+
+ CurrentThread.TotalTimeRunning += currentTime - CurrentThread.LastScheduledTime;
+ CurrentThread.LastScheduledTime = currentTime;
+
+ CurrentThread.ClearExclusive();
+
+ _coreManager.Set(CurrentThread.Context.Work);
+
+ CurrentThread.Context.Execute();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KCriticalSection.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KCriticalSection.cs
new file mode 100644
index 00000000..841d0d69
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/Threading/KCriticalSection.cs
@@ -0,0 +1,93 @@
+using ChocolArm64;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Kernel.Threading
+{
+ class KCriticalSection
+ {
+ private Horizon _system;
+
+ public object LockObj { get; private set; }
+
+ private int _recursionCount;
+
+ public KCriticalSection(Horizon system)
+ {
+ _system = system;
+
+ LockObj = new object();
+ }
+
+ public void Enter()
+ {
+ Monitor.Enter(LockObj);
+
+ _recursionCount++;
+ }
+
+ public void Leave()
+ {
+ if (_recursionCount == 0)
+ {
+ return;
+ }
+
+ bool doContextSwitch = false;
+
+ if (--_recursionCount == 0)
+ {
+ if (_system.Scheduler.ThreadReselectionRequested)
+ {
+ _system.Scheduler.SelectThreads();
+ }
+
+ Monitor.Exit(LockObj);
+
+ if (_system.Scheduler.MultiCoreScheduling)
+ {
+ lock (_system.Scheduler.CoreContexts)
+ {
+ for (int core = 0; core < KScheduler.CpuCoresCount; core++)
+ {
+ KCoreContext coreContext = _system.Scheduler.CoreContexts[core];
+
+ if (coreContext.ContextSwitchNeeded)
+ {
+ CpuThread currentHleThread = coreContext.CurrentThread?.Context;
+
+ if (currentHleThread == null)
+ {
+ //Nothing is running, we can perform the context switch immediately.
+ coreContext.ContextSwitch();
+ }
+ else if (currentHleThread.IsCurrentThread())
+ {
+ //Thread running on the current core, context switch will block.
+ doContextSwitch = true;
+ }
+ else
+ {
+ //Thread running on another core, request a interrupt.
+ currentHleThread.RequestInterrupt();
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ doContextSwitch = true;
+ }
+ }
+ else
+ {
+ Monitor.Exit(LockObj);
+ }
+
+ if (doContextSwitch)
+ {
+ _system.Scheduler.ContextSwitch();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KEvent.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KEvent.cs
new file mode 100644
index 00000000..5bdb9c1d
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/Threading/KEvent.cs
@@ -0,0 +1,14 @@
+namespace Ryujinx.HLE.HOS.Kernel.Threading
+{
+ class KEvent
+ {
+ public KReadableEvent ReadableEvent { get; private set; }
+ public KWritableEvent WritableEvent { get; private set; }
+
+ public KEvent(Horizon system)
+ {
+ ReadableEvent = new KReadableEvent(system, this);
+ WritableEvent = new KWritableEvent(this);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KReadableEvent.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KReadableEvent.cs
new file mode 100644
index 00000000..9821de35
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/Threading/KReadableEvent.cs
@@ -0,0 +1,64 @@
+using Ryujinx.HLE.HOS.Kernel.Common;
+
+namespace Ryujinx.HLE.HOS.Kernel.Threading
+{
+ class KReadableEvent : KSynchronizationObject
+ {
+ private KEvent _parent;
+
+ private bool _signaled;
+
+ public KReadableEvent(Horizon system, KEvent parent) : base(system)
+ {
+ _parent = parent;
+ }
+
+ public override void Signal()
+ {
+ System.CriticalSection.Enter();
+
+ if (!_signaled)
+ {
+ _signaled = true;
+
+ base.Signal();
+ }
+
+ System.CriticalSection.Leave();
+ }
+
+ public KernelResult Clear()
+ {
+ _signaled = false;
+
+ return KernelResult.Success;
+ }
+
+ public KernelResult ClearIfSignaled()
+ {
+ KernelResult result;
+
+ System.CriticalSection.Enter();
+
+ if (_signaled)
+ {
+ _signaled = false;
+
+ result = KernelResult.Success;
+ }
+ else
+ {
+ result = KernelResult.InvalidState;
+ }
+
+ System.CriticalSection.Leave();
+
+ return result;
+ }
+
+ public override bool IsSignaled()
+ {
+ return _signaled;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs
new file mode 100644
index 00000000..60e15efa
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs
@@ -0,0 +1,234 @@
+using Ryujinx.HLE.HOS.Kernel.Process;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Ryujinx.HLE.HOS.Kernel.Threading
+{
+ partial class KScheduler : IDisposable
+ {
+ public const int PrioritiesCount = 64;
+ public const int CpuCoresCount = 4;
+
+ private const int PreemptionPriorityCores012 = 59;
+ private const int PreemptionPriorityCore3 = 63;
+
+ private Horizon _system;
+
+ public KSchedulingData SchedulingData { get; private set; }
+
+ public KCoreContext[] CoreContexts { get; private set; }
+
+ public bool ThreadReselectionRequested { get; set; }
+
+ public KScheduler(Horizon system)
+ {
+ _system = system;
+
+ SchedulingData = new KSchedulingData();
+
+ CoreManager = new HleCoreManager();
+
+ CoreContexts = new KCoreContext[CpuCoresCount];
+
+ for (int core = 0; core < CpuCoresCount; core++)
+ {
+ CoreContexts[core] = new KCoreContext(this, CoreManager);
+ }
+ }
+
+ private void PreemptThreads()
+ {
+ _system.CriticalSection.Enter();
+
+ PreemptThread(PreemptionPriorityCores012, 0);
+ PreemptThread(PreemptionPriorityCores012, 1);
+ PreemptThread(PreemptionPriorityCores012, 2);
+ PreemptThread(PreemptionPriorityCore3, 3);
+
+ _system.CriticalSection.Leave();
+ }
+
+ private void PreemptThread(int prio, int core)
+ {
+ IEnumerable<KThread> scheduledThreads = SchedulingData.ScheduledThreads(core);
+
+ KThread selectedThread = scheduledThreads.FirstOrDefault(x => x.DynamicPriority == prio);
+
+ //Yield priority queue.
+ if (selectedThread != null)
+ {
+ SchedulingData.Reschedule(prio, core, selectedThread);
+ }
+
+ IEnumerable<KThread> SuitableCandidates()
+ {
+ foreach (KThread thread in SchedulingData.SuggestedThreads(core))
+ {
+ int srcCore = thread.CurrentCore;
+
+ if (srcCore >= 0)
+ {
+ KThread highestPrioSrcCore = SchedulingData.ScheduledThreads(srcCore).FirstOrDefault();
+
+ if (highestPrioSrcCore != null && highestPrioSrcCore.DynamicPriority < 2)
+ {
+ break;
+ }
+
+ if (highestPrioSrcCore == thread)
+ {
+ continue;
+ }
+ }
+
+ //If the candidate was scheduled after the current thread, then it's not worth it.
+ if (selectedThread == null || selectedThread.LastScheduledTime >= thread.LastScheduledTime)
+ {
+ yield return thread;
+ }
+ }
+ }
+
+ //Select candidate threads that could run on this core.
+ //Only take into account threads that are not yet selected.
+ KThread dst = SuitableCandidates().FirstOrDefault(x => x.DynamicPriority == prio);
+
+ if (dst != null)
+ {
+ SchedulingData.TransferToCore(prio, core, dst);
+
+ selectedThread = dst;
+ }
+
+ //If the priority of the currently selected thread is lower than preemption priority,
+ //then allow threads with lower priorities to be selected aswell.
+ if (selectedThread != null && selectedThread.DynamicPriority > prio)
+ {
+ Func<KThread, bool> predicate = x => x.DynamicPriority >= selectedThread.DynamicPriority;
+
+ dst = SuitableCandidates().FirstOrDefault(predicate);
+
+ if (dst != null)
+ {
+ SchedulingData.TransferToCore(dst.DynamicPriority, core, dst);
+ }
+ }
+
+ ThreadReselectionRequested = true;
+ }
+
+ public void SelectThreads()
+ {
+ ThreadReselectionRequested = false;
+
+ for (int core = 0; core < CpuCoresCount; core++)
+ {
+ KThread thread = SchedulingData.ScheduledThreads(core).FirstOrDefault();
+
+ CoreContexts[core].SelectThread(thread);
+ }
+
+ for (int core = 0; core < CpuCoresCount; core++)
+ {
+ //If the core is not idle (there's already a thread running on it),
+ //then we don't need to attempt load balancing.
+ if (SchedulingData.ScheduledThreads(core).Any())
+ {
+ continue;
+ }
+
+ int[] srcCoresHighestPrioThreads = new int[CpuCoresCount];
+
+ int srcCoresHighestPrioThreadsCount = 0;
+
+ KThread dst = null;
+
+ //Select candidate threads that could run on this core.
+ //Give preference to threads that are not yet selected.
+ foreach (KThread thread in SchedulingData.SuggestedThreads(core))
+ {
+ if (thread.CurrentCore < 0 || thread != CoreContexts[thread.CurrentCore].SelectedThread)
+ {
+ dst = thread;
+
+ break;
+ }
+
+ srcCoresHighestPrioThreads[srcCoresHighestPrioThreadsCount++] = thread.CurrentCore;
+ }
+
+ //Not yet selected candidate found.
+ if (dst != null)
+ {
+ //Priorities < 2 are used for the kernel message dispatching
+ //threads, we should skip load balancing entirely.
+ if (dst.DynamicPriority >= 2)
+ {
+ SchedulingData.TransferToCore(dst.DynamicPriority, core, dst);
+
+ CoreContexts[core].SelectThread(dst);
+ }
+
+ continue;
+ }
+
+ //All candiates are already selected, choose the best one
+ //(the first one that doesn't make the source core idle if moved).
+ for (int index = 0; index < srcCoresHighestPrioThreadsCount; index++)
+ {
+ int srcCore = srcCoresHighestPrioThreads[index];
+
+ KThread src = SchedulingData.ScheduledThreads(srcCore).ElementAtOrDefault(1);
+
+ if (src != null)
+ {
+ //Run the second thread on the queue on the source core,
+ //move the first one to the current core.
+ KThread origSelectedCoreSrc = CoreContexts[srcCore].SelectedThread;
+
+ CoreContexts[srcCore].SelectThread(src);
+
+ SchedulingData.TransferToCore(origSelectedCoreSrc.DynamicPriority, core, origSelectedCoreSrc);
+
+ CoreContexts[core].SelectThread(origSelectedCoreSrc);
+ }
+ }
+ }
+ }
+
+ public KThread GetCurrentThread()
+ {
+ lock (CoreContexts)
+ {
+ for (int core = 0; core < CpuCoresCount; core++)
+ {
+ if (CoreContexts[core].CurrentThread?.Context.IsCurrentThread() ?? false)
+ {
+ return CoreContexts[core].CurrentThread;
+ }
+ }
+ }
+
+ throw new InvalidOperationException("Current thread is not scheduled!");
+ }
+
+ public KProcess GetCurrentProcess()
+ {
+ return GetCurrentThread().Owner;
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _keepPreempting = false;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KSchedulingData.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KSchedulingData.cs
new file mode 100644
index 00000000..83c4a079
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/Threading/KSchedulingData.cs
@@ -0,0 +1,207 @@
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Kernel.Threading
+{
+ class KSchedulingData
+ {
+ private LinkedList<KThread>[][] _scheduledThreadsPerPrioPerCore;
+ private LinkedList<KThread>[][] _suggestedThreadsPerPrioPerCore;
+
+ private long[] _scheduledPrioritiesPerCore;
+ private long[] _suggestedPrioritiesPerCore;
+
+ public KSchedulingData()
+ {
+ _suggestedThreadsPerPrioPerCore = new LinkedList<KThread>[KScheduler.PrioritiesCount][];
+ _scheduledThreadsPerPrioPerCore = new LinkedList<KThread>[KScheduler.PrioritiesCount][];
+
+ for (int prio = 0; prio < KScheduler.PrioritiesCount; prio++)
+ {
+ _suggestedThreadsPerPrioPerCore[prio] = new LinkedList<KThread>[KScheduler.CpuCoresCount];
+ _scheduledThreadsPerPrioPerCore[prio] = new LinkedList<KThread>[KScheduler.CpuCoresCount];
+
+ for (int core = 0; core < KScheduler.CpuCoresCount; core++)
+ {
+ _suggestedThreadsPerPrioPerCore[prio][core] = new LinkedList<KThread>();
+ _scheduledThreadsPerPrioPerCore[prio][core] = new LinkedList<KThread>();
+ }
+ }
+
+ _scheduledPrioritiesPerCore = new long[KScheduler.CpuCoresCount];
+ _suggestedPrioritiesPerCore = new long[KScheduler.CpuCoresCount];
+ }
+
+ public IEnumerable<KThread> SuggestedThreads(int core)
+ {
+ return Iterate(_suggestedThreadsPerPrioPerCore, _suggestedPrioritiesPerCore, core);
+ }
+
+ public IEnumerable<KThread> ScheduledThreads(int core)
+ {
+ return Iterate(_scheduledThreadsPerPrioPerCore, _scheduledPrioritiesPerCore, core);
+ }
+
+ private IEnumerable<KThread> Iterate(LinkedList<KThread>[][] listPerPrioPerCore, long[] prios, int core)
+ {
+ long prioMask = prios[core];
+
+ int prio = CountTrailingZeros(prioMask);
+
+ prioMask &= ~(1L << prio);
+
+ while (prio < KScheduler.PrioritiesCount)
+ {
+ LinkedList<KThread> list = listPerPrioPerCore[prio][core];
+
+ LinkedListNode<KThread> node = list.First;
+
+ while (node != null)
+ {
+ yield return node.Value;
+
+ node = node.Next;
+ }
+
+ prio = CountTrailingZeros(prioMask);
+
+ prioMask &= ~(1L << prio);
+ }
+ }
+
+ private int CountTrailingZeros(long value)
+ {
+ int count = 0;
+
+ while (((value >> count) & 0xf) == 0 && count < 64)
+ {
+ count += 4;
+ }
+
+ while (((value >> count) & 1) == 0 && count < 64)
+ {
+ count++;
+ }
+
+ return count;
+ }
+
+ public void TransferToCore(int prio, int dstCore, KThread thread)
+ {
+ bool schedulable = thread.DynamicPriority < KScheduler.PrioritiesCount;
+
+ int srcCore = thread.CurrentCore;
+
+ thread.CurrentCore = dstCore;
+
+ if (srcCore == dstCore || !schedulable)
+ {
+ return;
+ }
+
+ if (srcCore >= 0)
+ {
+ Unschedule(prio, srcCore, thread);
+ }
+
+ if (dstCore >= 0)
+ {
+ Unsuggest(prio, dstCore, thread);
+ Schedule(prio, dstCore, thread);
+ }
+
+ if (srcCore >= 0)
+ {
+ Suggest(prio, srcCore, thread);
+ }
+ }
+
+ public void Suggest(int prio, int core, KThread thread)
+ {
+ if (prio >= KScheduler.PrioritiesCount)
+ {
+ return;
+ }
+
+ thread.SiblingsPerCore[core] = SuggestedQueue(prio, core).AddFirst(thread);
+
+ _suggestedPrioritiesPerCore[core] |= 1L << prio;
+ }
+
+ public void Unsuggest(int prio, int core, KThread thread)
+ {
+ if (prio >= KScheduler.PrioritiesCount)
+ {
+ return;
+ }
+
+ LinkedList<KThread> queue = SuggestedQueue(prio, core);
+
+ queue.Remove(thread.SiblingsPerCore[core]);
+
+ if (queue.First == null)
+ {
+ _suggestedPrioritiesPerCore[core] &= ~(1L << prio);
+ }
+ }
+
+ public void Schedule(int prio, int core, KThread thread)
+ {
+ if (prio >= KScheduler.PrioritiesCount)
+ {
+ return;
+ }
+
+ thread.SiblingsPerCore[core] = ScheduledQueue(prio, core).AddLast(thread);
+
+ _scheduledPrioritiesPerCore[core] |= 1L << prio;
+ }
+
+ public void SchedulePrepend(int prio, int core, KThread thread)
+ {
+ if (prio >= KScheduler.PrioritiesCount)
+ {
+ return;
+ }
+
+ thread.SiblingsPerCore[core] = ScheduledQueue(prio, core).AddFirst(thread);
+
+ _scheduledPrioritiesPerCore[core] |= 1L << prio;
+ }
+
+ public void Reschedule(int prio, int core, KThread thread)
+ {
+ LinkedList<KThread> queue = ScheduledQueue(prio, core);
+
+ queue.Remove(thread.SiblingsPerCore[core]);
+
+ thread.SiblingsPerCore[core] = queue.AddLast(thread);
+ }
+
+ public void Unschedule(int prio, int core, KThread thread)
+ {
+ if (prio >= KScheduler.PrioritiesCount)
+ {
+ return;
+ }
+
+ LinkedList<KThread> queue = ScheduledQueue(prio, core);
+
+ queue.Remove(thread.SiblingsPerCore[core]);
+
+ if (queue.First == null)
+ {
+ _scheduledPrioritiesPerCore[core] &= ~(1L << prio);
+ }
+ }
+
+ private LinkedList<KThread> SuggestedQueue(int prio, int core)
+ {
+ return _suggestedThreadsPerPrioPerCore[prio][core];
+ }
+
+ private LinkedList<KThread> ScheduledQueue(int prio, int core)
+ {
+ return _scheduledThreadsPerPrioPerCore[prio][core];
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs
new file mode 100644
index 00000000..450155ce
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs
@@ -0,0 +1,136 @@
+using Ryujinx.HLE.HOS.Kernel.Common;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Kernel.Threading
+{
+ class KSynchronization
+ {
+ private Horizon _system;
+
+ public KSynchronization(Horizon system)
+ {
+ _system = system;
+ }
+
+ public KernelResult WaitFor(KSynchronizationObject[] syncObjs, long timeout, out int handleIndex)
+ {
+ handleIndex = 0;
+
+ KernelResult result = KernelResult.TimedOut;
+
+ _system.CriticalSection.Enter();
+
+ //Check if objects are already signaled before waiting.
+ for (int index = 0; index < syncObjs.Length; index++)
+ {
+ if (!syncObjs[index].IsSignaled())
+ {
+ continue;
+ }
+
+ handleIndex = index;
+
+ _system.CriticalSection.Leave();
+
+ return 0;
+ }
+
+ if (timeout == 0)
+ {
+ _system.CriticalSection.Leave();
+
+ return result;
+ }
+
+ KThread currentThread = _system.Scheduler.GetCurrentThread();
+
+ if (currentThread.ShallBeTerminated ||
+ currentThread.SchedFlags == ThreadSchedState.TerminationPending)
+ {
+ result = KernelResult.ThreadTerminating;
+ }
+ else if (currentThread.SyncCancelled)
+ {
+ currentThread.SyncCancelled = false;
+
+ result = KernelResult.Cancelled;
+ }
+ else
+ {
+ LinkedListNode<KThread>[] syncNodes = new LinkedListNode<KThread>[syncObjs.Length];
+
+ for (int index = 0; index < syncObjs.Length; index++)
+ {
+ syncNodes[index] = syncObjs[index].AddWaitingThread(currentThread);
+ }
+
+ currentThread.WaitingSync = true;
+ currentThread.SignaledObj = null;
+ currentThread.ObjSyncResult = result;
+
+ currentThread.Reschedule(ThreadSchedState.Paused);
+
+ if (timeout > 0)
+ {
+ _system.TimeManager.ScheduleFutureInvocation(currentThread, timeout);
+ }
+
+ _system.CriticalSection.Leave();
+
+ currentThread.WaitingSync = false;
+
+ if (timeout > 0)
+ {
+ _system.TimeManager.UnscheduleFutureInvocation(currentThread);
+ }
+
+ _system.CriticalSection.Enter();
+
+ result = currentThread.ObjSyncResult;
+
+ handleIndex = -1;
+
+ for (int index = 0; index < syncObjs.Length; index++)
+ {
+ syncObjs[index].RemoveWaitingThread(syncNodes[index]);
+
+ if (syncObjs[index] == currentThread.SignaledObj)
+ {
+ handleIndex = index;
+ }
+ }
+ }
+
+ _system.CriticalSection.Leave();
+
+ return result;
+ }
+
+ public void SignalObject(KSynchronizationObject syncObj)
+ {
+ _system.CriticalSection.Enter();
+
+ if (syncObj.IsSignaled())
+ {
+ LinkedListNode<KThread> node = syncObj.WaitingThreads.First;
+
+ while (node != null)
+ {
+ KThread thread = node.Value;
+
+ if ((thread.SchedFlags & ThreadSchedState.LowMask) == ThreadSchedState.Paused)
+ {
+ thread.SignaledObj = syncObj;
+ thread.ObjSyncResult = KernelResult.Success;
+
+ thread.Reschedule(ThreadSchedState.Running);
+ }
+
+ node = node.Next;
+ }
+ }
+
+ _system.CriticalSection.Leave();
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
new file mode 100644
index 00000000..3ad64024
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
@@ -0,0 +1,1030 @@
+using ChocolArm64;
+using ChocolArm64.Memory;
+using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.HLE.HOS.Kernel.Process;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Ryujinx.HLE.HOS.Kernel.Threading
+{
+ class KThread : KSynchronizationObject, IKFutureSchedulerObject
+ {
+ public CpuThread Context { get; private set; }
+
+ public long AffinityMask { get; set; }
+
+ public long ThreadUid { get; private set; }
+
+ public long TotalTimeRunning { get; set; }
+
+ public KSynchronizationObject SignaledObj { get; set; }
+
+ public ulong CondVarAddress { get; set; }
+
+ private ulong _entrypoint;
+
+ public ulong MutexAddress { get; set; }
+
+ public KProcess Owner { get; private set; }
+
+ private ulong _tlsAddress;
+
+ public long LastScheduledTime { get; set; }
+
+ public LinkedListNode<KThread>[] SiblingsPerCore { get; private set; }
+
+ public LinkedList<KThread> Withholder { get; set; }
+ public LinkedListNode<KThread> WithholderNode { get; set; }
+
+ public LinkedListNode<KThread> ProcessListNode { get; set; }
+
+ private LinkedList<KThread> _mutexWaiters;
+ private LinkedListNode<KThread> _mutexWaiterNode;
+
+ public KThread MutexOwner { get; private set; }
+
+ public int ThreadHandleForUserMutex { get; set; }
+
+ private ThreadSchedState _forcePauseFlags;
+
+ public KernelResult ObjSyncResult { get; set; }
+
+ public int DynamicPriority { get; set; }
+ public int CurrentCore { get; set; }
+ public int BasePriority { get; set; }
+ public int PreferredCore { get; set; }
+
+ private long _affinityMaskOverride;
+ private int _preferredCoreOverride;
+ private int _affinityOverrideCount;
+
+ public ThreadSchedState SchedFlags { get; private set; }
+
+ public bool ShallBeTerminated { get; private set; }
+
+ public bool SyncCancelled { get; set; }
+ public bool WaitingSync { get; set; }
+
+ private bool _hasExited;
+
+ public bool WaitingInArbitration { get; set; }
+
+ private KScheduler _scheduler;
+
+ private KSchedulingData _schedulingData;
+
+ public long LastPc { get; set; }
+
+ public KThread(Horizon system) : base(system)
+ {
+ _scheduler = system.Scheduler;
+ _schedulingData = system.Scheduler.SchedulingData;
+
+ SiblingsPerCore = new LinkedListNode<KThread>[KScheduler.CpuCoresCount];
+
+ _mutexWaiters = new LinkedList<KThread>();
+ }
+
+ public KernelResult Initialize(
+ ulong entrypoint,
+ ulong argsPtr,
+ ulong stackTop,
+ int priority,
+ int defaultCpuCore,
+ KProcess owner,
+ ThreadType type = ThreadType.User)
+ {
+ if ((uint)type > 3)
+ {
+ throw new ArgumentException($"Invalid thread type \"{type}\".");
+ }
+
+ PreferredCore = defaultCpuCore;
+
+ AffinityMask |= 1L << defaultCpuCore;
+
+ SchedFlags = type == ThreadType.Dummy
+ ? ThreadSchedState.Running
+ : ThreadSchedState.None;
+
+ CurrentCore = PreferredCore;
+
+ DynamicPriority = priority;
+ BasePriority = priority;
+
+ ObjSyncResult = KernelResult.ThreadNotStarted;
+
+ _entrypoint = entrypoint;
+
+ if (type == ThreadType.User)
+ {
+ if (owner.AllocateThreadLocalStorage(out _tlsAddress) != KernelResult.Success)
+ {
+ return KernelResult.OutOfMemory;
+ }
+
+ MemoryHelper.FillWithZeros(owner.CpuMemory, (long)_tlsAddress, KTlsPageInfo.TlsEntrySize);
+ }
+
+ bool is64Bits;
+
+ if (owner != null)
+ {
+ Owner = owner;
+
+ owner.IncrementThreadCount();
+
+ is64Bits = (owner.MmuFlags & 1) != 0;
+ }
+ else
+ {
+ is64Bits = true;
+ }
+
+ Context = new CpuThread(owner.Translator, owner.CpuMemory, (long)entrypoint);
+
+ Context.ThreadState.X0 = argsPtr;
+ Context.ThreadState.X31 = stackTop;
+
+ Context.ThreadState.CntfrqEl0 = 19200000;
+ Context.ThreadState.Tpidr = (long)_tlsAddress;
+
+ owner.SubscribeThreadEventHandlers(Context);
+
+ Context.WorkFinished += ThreadFinishedHandler;
+
+ ThreadUid = System.GetThreadUid();
+
+ if (owner != null)
+ {
+ owner.AddThread(this);
+
+ if (owner.IsPaused)
+ {
+ System.CriticalSection.Enter();
+
+ if (ShallBeTerminated || SchedFlags == ThreadSchedState.TerminationPending)
+ {
+ System.CriticalSection.Leave();
+
+ return KernelResult.Success;
+ }
+
+ _forcePauseFlags |= ThreadSchedState.ProcessPauseFlag;
+
+ CombineForcePauseFlags();
+
+ System.CriticalSection.Leave();
+ }
+ }
+
+ return KernelResult.Success;
+ }
+
+ public KernelResult Start()
+ {
+ if (!System.KernelInitialized)
+ {
+ System.CriticalSection.Enter();
+
+ if (!ShallBeTerminated && SchedFlags != ThreadSchedState.TerminationPending)
+ {
+ _forcePauseFlags |= ThreadSchedState.KernelInitPauseFlag;
+
+ CombineForcePauseFlags();
+ }
+
+ System.CriticalSection.Leave();
+ }
+
+ KernelResult result = KernelResult.ThreadTerminating;
+
+ System.CriticalSection.Enter();
+
+ if (!ShallBeTerminated)
+ {
+ KThread currentThread = System.Scheduler.GetCurrentThread();
+
+ while (SchedFlags != ThreadSchedState.TerminationPending &&
+ currentThread.SchedFlags != ThreadSchedState.TerminationPending &&
+ !currentThread.ShallBeTerminated)
+ {
+ if ((SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.None)
+ {
+ result = KernelResult.InvalidState;
+
+ break;
+ }
+
+ if (currentThread._forcePauseFlags == ThreadSchedState.None)
+ {
+ if (Owner != null && _forcePauseFlags != ThreadSchedState.None)
+ {
+ CombineForcePauseFlags();
+ }
+
+ SetNewSchedFlags(ThreadSchedState.Running);
+
+ result = KernelResult.Success;
+
+ break;
+ }
+ else
+ {
+ currentThread.CombineForcePauseFlags();
+
+ System.CriticalSection.Leave();
+ System.CriticalSection.Enter();
+
+ if (currentThread.ShallBeTerminated)
+ {
+ break;
+ }
+ }
+ }
+ }
+
+ System.CriticalSection.Leave();
+
+ return result;
+ }
+
+ public void Exit()
+ {
+ System.CriticalSection.Enter();
+
+ _forcePauseFlags &= ~ThreadSchedState.ForcePauseMask;
+
+ ExitImpl();
+
+ System.CriticalSection.Leave();
+ }
+
+ private void ExitImpl()
+ {
+ System.CriticalSection.Enter();
+
+ SetNewSchedFlags(ThreadSchedState.TerminationPending);
+
+ _hasExited = true;
+
+ Signal();
+
+ System.CriticalSection.Leave();
+ }
+
+ public KernelResult Sleep(long timeout)
+ {
+ System.CriticalSection.Enter();
+
+ if (ShallBeTerminated || SchedFlags == ThreadSchedState.TerminationPending)
+ {
+ System.CriticalSection.Leave();
+
+ return KernelResult.ThreadTerminating;
+ }
+
+ SetNewSchedFlags(ThreadSchedState.Paused);
+
+ if (timeout > 0)
+ {
+ System.TimeManager.ScheduleFutureInvocation(this, timeout);
+ }
+
+ System.CriticalSection.Leave();
+
+ if (timeout > 0)
+ {
+ System.TimeManager.UnscheduleFutureInvocation(this);
+ }
+
+ return 0;
+ }
+
+ public void Yield()
+ {
+ System.CriticalSection.Enter();
+
+ if (SchedFlags != ThreadSchedState.Running)
+ {
+ System.CriticalSection.Leave();
+
+ System.Scheduler.ContextSwitch();
+
+ return;
+ }
+
+ if (DynamicPriority < KScheduler.PrioritiesCount)
+ {
+ //Move current thread to the end of the queue.
+ _schedulingData.Reschedule(DynamicPriority, CurrentCore, this);
+ }
+
+ _scheduler.ThreadReselectionRequested = true;
+
+ System.CriticalSection.Leave();
+
+ System.Scheduler.ContextSwitch();
+ }
+
+ public void YieldWithLoadBalancing()
+ {
+ System.CriticalSection.Enter();
+
+ if (SchedFlags != ThreadSchedState.Running)
+ {
+ System.CriticalSection.Leave();
+
+ System.Scheduler.ContextSwitch();
+
+ return;
+ }
+
+ int prio = DynamicPriority;
+ int core = CurrentCore;
+
+ KThread nextThreadOnCurrentQueue = null;
+
+ if (DynamicPriority < KScheduler.PrioritiesCount)
+ {
+ //Move current thread to the end of the queue.
+ _schedulingData.Reschedule(prio, core, this);
+
+ Func<KThread, bool> predicate = x => x.DynamicPriority == prio;
+
+ nextThreadOnCurrentQueue = _schedulingData.ScheduledThreads(core).FirstOrDefault(predicate);
+ }
+
+ IEnumerable<KThread> SuitableCandidates()
+ {
+ foreach (KThread thread in _schedulingData.SuggestedThreads(core))
+ {
+ int srcCore = thread.CurrentCore;
+
+ if (srcCore >= 0)
+ {
+ KThread selectedSrcCore = _scheduler.CoreContexts[srcCore].SelectedThread;
+
+ if (selectedSrcCore == thread || ((selectedSrcCore?.DynamicPriority ?? 2) < 2))
+ {
+ continue;
+ }
+ }
+
+ //If the candidate was scheduled after the current thread, then it's not worth it,
+ //unless the priority is higher than the current one.
+ if (nextThreadOnCurrentQueue.LastScheduledTime >= thread.LastScheduledTime ||
+ nextThreadOnCurrentQueue.DynamicPriority < thread.DynamicPriority)
+ {
+ yield return thread;
+ }
+ }
+ }
+
+ KThread dst = SuitableCandidates().FirstOrDefault(x => x.DynamicPriority <= prio);
+
+ if (dst != null)
+ {
+ _schedulingData.TransferToCore(dst.DynamicPriority, core, dst);
+
+ _scheduler.ThreadReselectionRequested = true;
+ }
+
+ if (this != nextThreadOnCurrentQueue)
+ {
+ _scheduler.ThreadReselectionRequested = true;
+ }
+
+ System.CriticalSection.Leave();
+
+ System.Scheduler.ContextSwitch();
+ }
+
+ public void YieldAndWaitForLoadBalancing()
+ {
+ System.CriticalSection.Enter();
+
+ if (SchedFlags != ThreadSchedState.Running)
+ {
+ System.CriticalSection.Leave();
+
+ System.Scheduler.ContextSwitch();
+
+ return;
+ }
+
+ int core = CurrentCore;
+
+ _schedulingData.TransferToCore(DynamicPriority, -1, this);
+
+ KThread selectedThread = null;
+
+ if (!_schedulingData.ScheduledThreads(core).Any())
+ {
+ foreach (KThread thread in _schedulingData.SuggestedThreads(core))
+ {
+ if (thread.CurrentCore < 0)
+ {
+ continue;
+ }
+
+ KThread firstCandidate = _schedulingData.ScheduledThreads(thread.CurrentCore).FirstOrDefault();
+
+ if (firstCandidate == thread)
+ {
+ continue;
+ }
+
+ if (firstCandidate == null || firstCandidate.DynamicPriority >= 2)
+ {
+ _schedulingData.TransferToCore(thread.DynamicPriority, core, thread);
+
+ selectedThread = thread;
+ }
+
+ break;
+ }
+ }
+
+ if (selectedThread != this)
+ {
+ _scheduler.ThreadReselectionRequested = true;
+ }
+
+ System.CriticalSection.Leave();
+
+ System.Scheduler.ContextSwitch();
+ }
+
+ public void SetPriority(int priority)
+ {
+ System.CriticalSection.Enter();
+
+ BasePriority = priority;
+
+ UpdatePriorityInheritance();
+
+ System.CriticalSection.Leave();
+ }
+
+ public KernelResult SetActivity(bool pause)
+ {
+ KernelResult result = KernelResult.Success;
+
+ System.CriticalSection.Enter();
+
+ ThreadSchedState lowNibble = SchedFlags & ThreadSchedState.LowMask;
+
+ if (lowNibble != ThreadSchedState.Paused && lowNibble != ThreadSchedState.Running)
+ {
+ System.CriticalSection.Leave();
+
+ return KernelResult.InvalidState;
+ }
+
+ System.CriticalSection.Enter();
+
+ if (!ShallBeTerminated && SchedFlags != ThreadSchedState.TerminationPending)
+ {
+ if (pause)
+ {
+ //Pause, the force pause flag should be clear (thread is NOT paused).
+ if ((_forcePauseFlags & ThreadSchedState.ThreadPauseFlag) == 0)
+ {
+ _forcePauseFlags |= ThreadSchedState.ThreadPauseFlag;
+
+ CombineForcePauseFlags();
+ }
+ else
+ {
+ result = KernelResult.InvalidState;
+ }
+ }
+ else
+ {
+ //Unpause, the force pause flag should be set (thread is paused).
+ if ((_forcePauseFlags & ThreadSchedState.ThreadPauseFlag) != 0)
+ {
+ ThreadSchedState oldForcePauseFlags = _forcePauseFlags;
+
+ _forcePauseFlags &= ~ThreadSchedState.ThreadPauseFlag;
+
+ if ((oldForcePauseFlags & ~ThreadSchedState.ThreadPauseFlag) == ThreadSchedState.None)
+ {
+ ThreadSchedState oldSchedFlags = SchedFlags;
+
+ SchedFlags &= ThreadSchedState.LowMask;
+
+ AdjustScheduling(oldSchedFlags);
+ }
+ }
+ else
+ {
+ result = KernelResult.InvalidState;
+ }
+ }
+ }
+
+ System.CriticalSection.Leave();
+ System.CriticalSection.Leave();
+
+ return result;
+ }
+
+ public void CancelSynchronization()
+ {
+ System.CriticalSection.Enter();
+
+ if ((SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.Paused || !WaitingSync)
+ {
+ SyncCancelled = true;
+ }
+ else if (Withholder != null)
+ {
+ Withholder.Remove(WithholderNode);
+
+ SetNewSchedFlags(ThreadSchedState.Running);
+
+ Withholder = null;
+
+ SyncCancelled = true;
+ }
+ else
+ {
+ SignaledObj = null;
+ ObjSyncResult = KernelResult.Cancelled;
+
+ SetNewSchedFlags(ThreadSchedState.Running);
+
+ SyncCancelled = false;
+ }
+
+ System.CriticalSection.Leave();
+ }
+
+ public KernelResult SetCoreAndAffinityMask(int newCore, long newAffinityMask)
+ {
+ System.CriticalSection.Enter();
+
+ bool useOverride = _affinityOverrideCount != 0;
+
+ //The value -3 is "do not change the preferred core".
+ if (newCore == -3)
+ {
+ newCore = useOverride ? _preferredCoreOverride : PreferredCore;
+
+ if ((newAffinityMask & (1 << newCore)) == 0)
+ {
+ System.CriticalSection.Leave();
+
+ return KernelResult.InvalidCombination;
+ }
+ }
+
+ if (useOverride)
+ {
+ _preferredCoreOverride = newCore;
+ _affinityMaskOverride = newAffinityMask;
+ }
+ else
+ {
+ long oldAffinityMask = AffinityMask;
+
+ PreferredCore = newCore;
+ AffinityMask = newAffinityMask;
+
+ if (oldAffinityMask != newAffinityMask)
+ {
+ int oldCore = CurrentCore;
+
+ if (CurrentCore >= 0 && ((AffinityMask >> CurrentCore) & 1) == 0)
+ {
+ if (PreferredCore < 0)
+ {
+ CurrentCore = HighestSetCore(AffinityMask);
+ }
+ else
+ {
+ CurrentCore = PreferredCore;
+ }
+ }
+
+ AdjustSchedulingForNewAffinity(oldAffinityMask, oldCore);
+ }
+ }
+
+ System.CriticalSection.Leave();
+
+ return KernelResult.Success;
+ }
+
+ private static int HighestSetCore(long mask)
+ {
+ for (int core = KScheduler.CpuCoresCount - 1; core >= 0; core--)
+ {
+ if (((mask >> core) & 1) != 0)
+ {
+ return core;
+ }
+ }
+
+ return -1;
+ }
+
+ private void CombineForcePauseFlags()
+ {
+ ThreadSchedState oldFlags = SchedFlags;
+ ThreadSchedState lowNibble = SchedFlags & ThreadSchedState.LowMask;
+
+ SchedFlags = lowNibble | _forcePauseFlags;
+
+ AdjustScheduling(oldFlags);
+ }
+
+ private void SetNewSchedFlags(ThreadSchedState newFlags)
+ {
+ System.CriticalSection.Enter();
+
+ ThreadSchedState oldFlags = SchedFlags;
+
+ SchedFlags = (oldFlags & ThreadSchedState.HighMask) | newFlags;
+
+ if ((oldFlags & ThreadSchedState.LowMask) != newFlags)
+ {
+ AdjustScheduling(oldFlags);
+ }
+
+ System.CriticalSection.Leave();
+ }
+
+ public void ReleaseAndResume()
+ {
+ System.CriticalSection.Enter();
+
+ if ((SchedFlags & ThreadSchedState.LowMask) == ThreadSchedState.Paused)
+ {
+ if (Withholder != null)
+ {
+ Withholder.Remove(WithholderNode);
+
+ SetNewSchedFlags(ThreadSchedState.Running);
+
+ Withholder = null;
+ }
+ else
+ {
+ SetNewSchedFlags(ThreadSchedState.Running);
+ }
+ }
+
+ System.CriticalSection.Leave();
+ }
+
+ public void Reschedule(ThreadSchedState newFlags)
+ {
+ System.CriticalSection.Enter();
+
+ ThreadSchedState oldFlags = SchedFlags;
+
+ SchedFlags = (oldFlags & ThreadSchedState.HighMask) |
+ (newFlags & ThreadSchedState.LowMask);
+
+ AdjustScheduling(oldFlags);
+
+ System.CriticalSection.Leave();
+ }
+
+ public void AddMutexWaiter(KThread requester)
+ {
+ AddToMutexWaitersList(requester);
+
+ requester.MutexOwner = this;
+
+ UpdatePriorityInheritance();
+ }
+
+ public void RemoveMutexWaiter(KThread thread)
+ {
+ if (thread._mutexWaiterNode?.List != null)
+ {
+ _mutexWaiters.Remove(thread._mutexWaiterNode);
+ }
+
+ thread.MutexOwner = null;
+
+ UpdatePriorityInheritance();
+ }
+
+ public KThread RelinquishMutex(ulong mutexAddress, out int count)
+ {
+ count = 0;
+
+ if (_mutexWaiters.First == null)
+ {
+ return null;
+ }
+
+ KThread newMutexOwner = null;
+
+ LinkedListNode<KThread> currentNode = _mutexWaiters.First;
+
+ do
+ {
+ //Skip all threads that are not waiting for this mutex.
+ while (currentNode != null && currentNode.Value.MutexAddress != mutexAddress)
+ {
+ currentNode = currentNode.Next;
+ }
+
+ if (currentNode == null)
+ {
+ break;
+ }
+
+ LinkedListNode<KThread> nextNode = currentNode.Next;
+
+ _mutexWaiters.Remove(currentNode);
+
+ currentNode.Value.MutexOwner = newMutexOwner;
+
+ if (newMutexOwner != null)
+ {
+ //New owner was already selected, re-insert on new owner list.
+ newMutexOwner.AddToMutexWaitersList(currentNode.Value);
+ }
+ else
+ {
+ //New owner not selected yet, use current thread.
+ newMutexOwner = currentNode.Value;
+ }
+
+ count++;
+
+ currentNode = nextNode;
+ }
+ while (currentNode != null);
+
+ if (newMutexOwner != null)
+ {
+ UpdatePriorityInheritance();
+
+ newMutexOwner.UpdatePriorityInheritance();
+ }
+
+ return newMutexOwner;
+ }
+
+ private void UpdatePriorityInheritance()
+ {
+ //If any of the threads waiting for the mutex has
+ //higher priority than the current thread, then
+ //the current thread inherits that priority.
+ int highestPriority = BasePriority;
+
+ if (_mutexWaiters.First != null)
+ {
+ int waitingDynamicPriority = _mutexWaiters.First.Value.DynamicPriority;
+
+ if (waitingDynamicPriority < highestPriority)
+ {
+ highestPriority = waitingDynamicPriority;
+ }
+ }
+
+ if (highestPriority != DynamicPriority)
+ {
+ int oldPriority = DynamicPriority;
+
+ DynamicPriority = highestPriority;
+
+ AdjustSchedulingForNewPriority(oldPriority);
+
+ if (MutexOwner != null)
+ {
+ //Remove and re-insert to ensure proper sorting based on new priority.
+ MutexOwner._mutexWaiters.Remove(_mutexWaiterNode);
+
+ MutexOwner.AddToMutexWaitersList(this);
+
+ MutexOwner.UpdatePriorityInheritance();
+ }
+ }
+ }
+
+ private void AddToMutexWaitersList(KThread thread)
+ {
+ LinkedListNode<KThread> nextPrio = _mutexWaiters.First;
+
+ int currentPriority = thread.DynamicPriority;
+
+ while (nextPrio != null && nextPrio.Value.DynamicPriority <= currentPriority)
+ {
+ nextPrio = nextPrio.Next;
+ }
+
+ if (nextPrio != null)
+ {
+ thread._mutexWaiterNode = _mutexWaiters.AddBefore(nextPrio, thread);
+ }
+ else
+ {
+ thread._mutexWaiterNode = _mutexWaiters.AddLast(thread);
+ }
+ }
+
+ private void AdjustScheduling(ThreadSchedState oldFlags)
+ {
+ if (oldFlags == SchedFlags)
+ {
+ return;
+ }
+
+ if (oldFlags == ThreadSchedState.Running)
+ {
+ //Was running, now it's stopped.
+ if (CurrentCore >= 0)
+ {
+ _schedulingData.Unschedule(DynamicPriority, CurrentCore, this);
+ }
+
+ for (int core = 0; core < KScheduler.CpuCoresCount; core++)
+ {
+ if (core != CurrentCore && ((AffinityMask >> core) & 1) != 0)
+ {
+ _schedulingData.Unsuggest(DynamicPriority, core, this);
+ }
+ }
+ }
+ else if (SchedFlags == ThreadSchedState.Running)
+ {
+ //Was stopped, now it's running.
+ if (CurrentCore >= 0)
+ {
+ _schedulingData.Schedule(DynamicPriority, CurrentCore, this);
+ }
+
+ for (int core = 0; core < KScheduler.CpuCoresCount; core++)
+ {
+ if (core != CurrentCore && ((AffinityMask >> core) & 1) != 0)
+ {
+ _schedulingData.Suggest(DynamicPriority, core, this);
+ }
+ }
+ }
+
+ _scheduler.ThreadReselectionRequested = true;
+ }
+
+ private void AdjustSchedulingForNewPriority(int oldPriority)
+ {
+ if (SchedFlags != ThreadSchedState.Running)
+ {
+ return;
+ }
+
+ //Remove thread from the old priority queues.
+ if (CurrentCore >= 0)
+ {
+ _schedulingData.Unschedule(oldPriority, CurrentCore, this);
+ }
+
+ for (int core = 0; core < KScheduler.CpuCoresCount; core++)
+ {
+ if (core != CurrentCore && ((AffinityMask >> core) & 1) != 0)
+ {
+ _schedulingData.Unsuggest(oldPriority, core, this);
+ }
+ }
+
+ //Add thread to the new priority queues.
+ KThread currentThread = _scheduler.GetCurrentThread();
+
+ if (CurrentCore >= 0)
+ {
+ if (currentThread == this)
+ {
+ _schedulingData.SchedulePrepend(DynamicPriority, CurrentCore, this);
+ }
+ else
+ {
+ _schedulingData.Schedule(DynamicPriority, CurrentCore, this);
+ }
+ }
+
+ for (int core = 0; core < KScheduler.CpuCoresCount; core++)
+ {
+ if (core != CurrentCore && ((AffinityMask >> core) & 1) != 0)
+ {
+ _schedulingData.Suggest(DynamicPriority, core, this);
+ }
+ }
+
+ _scheduler.ThreadReselectionRequested = true;
+ }
+
+ private void AdjustSchedulingForNewAffinity(long oldAffinityMask, int oldCore)
+ {
+ if (SchedFlags != ThreadSchedState.Running || DynamicPriority >= KScheduler.PrioritiesCount)
+ {
+ return;
+ }
+
+ //Remove from old queues.
+ for (int core = 0; core < KScheduler.CpuCoresCount; core++)
+ {
+ if (((oldAffinityMask >> core) & 1) != 0)
+ {
+ if (core == oldCore)
+ {
+ _schedulingData.Unschedule(DynamicPriority, core, this);
+ }
+ else
+ {
+ _schedulingData.Unsuggest(DynamicPriority, core, this);
+ }
+ }
+ }
+
+ //Insert on new queues.
+ for (int core = 0; core < KScheduler.CpuCoresCount; core++)
+ {
+ if (((AffinityMask >> core) & 1) != 0)
+ {
+ if (core == CurrentCore)
+ {
+ _schedulingData.Schedule(DynamicPriority, core, this);
+ }
+ else
+ {
+ _schedulingData.Suggest(DynamicPriority, core, this);
+ }
+ }
+ }
+
+ _scheduler.ThreadReselectionRequested = true;
+ }
+
+ public override bool IsSignaled()
+ {
+ return _hasExited;
+ }
+
+ public void SetEntryArguments(long argsPtr, int threadHandle)
+ {
+ Context.ThreadState.X0 = (ulong)argsPtr;
+ Context.ThreadState.X1 = (ulong)threadHandle;
+ }
+
+ public void ClearExclusive()
+ {
+ Owner.CpuMemory.ClearExclusive(CurrentCore);
+ }
+
+ public void TimeUp()
+ {
+ ReleaseAndResume();
+ }
+
+ public void PrintGuestStackTrace()
+ {
+ Owner.Debugger.PrintGuestStackTrace(Context.ThreadState);
+ }
+
+ private void ThreadFinishedHandler(object sender, EventArgs e)
+ {
+ System.Scheduler.ExitThread(this);
+
+ Terminate();
+
+ System.Scheduler.RemoveThread(this);
+ }
+
+ public void Terminate()
+ {
+ Owner?.RemoveThread(this);
+
+ if (_tlsAddress != 0 && Owner.FreeThreadLocalStorage(_tlsAddress) != KernelResult.Success)
+ {
+ throw new InvalidOperationException("Unexpected failure freeing thread local storage.");
+ }
+
+ System.CriticalSection.Enter();
+
+ //Wake up all threads that may be waiting for a mutex being held
+ //by this thread.
+ foreach (KThread thread in _mutexWaiters)
+ {
+ thread.MutexOwner = null;
+ thread._preferredCoreOverride = 0;
+ thread.ObjSyncResult = KernelResult.InvalidState;
+
+ thread.ReleaseAndResume();
+ }
+
+ System.CriticalSection.Leave();
+
+ Owner?.DecrementThreadCountAndTerminateIfZero();
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KWritableEvent.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KWritableEvent.cs
new file mode 100644
index 00000000..c9b2f40d
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/Threading/KWritableEvent.cs
@@ -0,0 +1,24 @@
+using Ryujinx.HLE.HOS.Kernel.Common;
+
+namespace Ryujinx.HLE.HOS.Kernel.Threading
+{
+ class KWritableEvent
+ {
+ private KEvent _parent;
+
+ public KWritableEvent(KEvent parent)
+ {
+ _parent = parent;
+ }
+
+ public void Signal()
+ {
+ _parent.ReadableEvent.Signal();
+ }
+
+ public KernelResult Clear()
+ {
+ return _parent.ReadableEvent.Clear();
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/SignalType.cs b/Ryujinx.HLE/HOS/Kernel/Threading/SignalType.cs
new file mode 100644
index 00000000..e72b719b
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/Threading/SignalType.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.HLE.HOS.Kernel.Threading
+{
+ enum SignalType
+ {
+ Signal = 0,
+ SignalAndIncrementIfEqual = 1,
+ SignalAndModifyIfEqual = 2
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/ThreadSchedState.cs b/Ryujinx.HLE/HOS/Kernel/Threading/ThreadSchedState.cs
new file mode 100644
index 00000000..c9eaa6b3
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/Threading/ThreadSchedState.cs
@@ -0,0 +1,19 @@
+namespace Ryujinx.HLE.HOS.Kernel.Threading
+{
+ enum ThreadSchedState : ushort
+ {
+ LowMask = 0xf,
+ HighMask = 0xfff0,
+ ForcePauseMask = 0x70,
+
+ ProcessPauseFlag = 1 << 4,
+ ThreadPauseFlag = 1 << 5,
+ ProcessDebugPauseFlag = 1 << 6,
+ KernelInitPauseFlag = 1 << 8,
+
+ None = 0,
+ Paused = 1,
+ Running = 2,
+ TerminationPending = 3
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/ThreadType.cs b/Ryujinx.HLE/HOS/Kernel/Threading/ThreadType.cs
new file mode 100644
index 00000000..0b44b57f
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/Threading/ThreadType.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.HLE.HOS.Kernel.Threading
+{
+ enum ThreadType
+ {
+ Dummy,
+ Kernel,
+ Kernel2,
+ User
+ }
+} \ No newline at end of file