diff options
author | gdkchan <gab.dark.100@gmail.com> | 2018-12-18 03:33:36 -0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-12-18 03:33:36 -0200 |
commit | 0039bb639493b2d1e2764cae380311ba8e87704b (patch) | |
tree | 63a912a95c8261775c2acb8a5b9ca0f10ad4ae33 /Ryujinx.HLE/HOS/Kernel/Threading | |
parent | 2534a7f10c627810e6e0272b4cc9758e90f733c1 (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')
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 |