Refactor SVC handler (#540)

* Refactor SVC handler

* Get rid of KernelErr

* Split kernel code files into multiple folders
This commit is contained in:
gdkchan 2018-12-18 03:33:36 -02:00 committed by GitHub
parent 2534a7f10c
commit 0039bb6394
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
105 changed files with 1894 additions and 1982 deletions

View file

@ -0,0 +1,9 @@
namespace Ryujinx.HLE.HOS.Kernel.Threading
{
enum ArbitrationType
{
WaitIfLessThan = 0,
DecrementAndWaitIfLessThan = 1,
WaitIfEqual = 2
}
}

View file

@ -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();
}
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}
}

View file

@ -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();
}
}
}

View file

@ -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();
}
}
}
}

View file

@ -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();
}
}
}
}

View file

@ -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);
}
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}
}

View file

@ -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];
}
}
}

View file

@ -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();
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -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();
}
}
}

View file

@ -0,0 +1,9 @@
namespace Ryujinx.HLE.HOS.Kernel.Threading
{
enum SignalType
{
Signal = 0,
SignalAndIncrementIfEqual = 1,
SignalAndModifyIfEqual = 2
}
}

View file

@ -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
}
}

View file

@ -0,0 +1,10 @@
namespace Ryujinx.HLE.HOS.Kernel.Threading
{
enum ThreadType
{
Dummy,
Kernel,
Kernel2,
User
}
}