Thread scheduler rewrite (#393)
* Started to rewrite the thread scheduler * Add a single core-like scheduling mode, enabled by default * Clear exclusive monitor on context switch * Add SetThreadActivity, misc fixes * Implement WaitForAddress and SignalToAddress svcs, misc fixes * Misc fixes (on SetActivity and Arbiter), other tweaks * Rebased * Add missing null check * Rename multicore key on config, fix UpdatePriorityInheritance * Make scheduling data MLQs private * nit: Ordering
This commit is contained in:
parent
33e2810ef3
commit
b8133c1997
57 changed files with 3262 additions and 1540 deletions
|
@ -1,111 +0,0 @@
|
|||
using ChocolArm64.Memory;
|
||||
using ChocolArm64.State;
|
||||
|
||||
using static Ryujinx.HLE.HOS.ErrorCode;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel
|
||||
{
|
||||
static class AddressArbiter
|
||||
{
|
||||
static ulong WaitForAddress(Process Process, AThreadState ThreadState, long Address, ulong Timeout)
|
||||
{
|
||||
KThread CurrentThread = Process.GetThread(ThreadState.Tpidr);
|
||||
|
||||
Process.Scheduler.SetReschedule(CurrentThread.ProcessorId);
|
||||
|
||||
CurrentThread.ArbiterWaitAddress = Address;
|
||||
CurrentThread.ArbiterSignaled = false;
|
||||
|
||||
Process.Scheduler.EnterWait(CurrentThread, NsTimeConverter.GetTimeMs(Timeout));
|
||||
|
||||
if (!CurrentThread.ArbiterSignaled)
|
||||
{
|
||||
return MakeError(ErrorModule.Kernel, KernelErr.Timeout);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static ulong WaitForAddressIfLessThan(Process Process,
|
||||
AThreadState ThreadState,
|
||||
AMemory Memory,
|
||||
long Address,
|
||||
int Value,
|
||||
ulong Timeout,
|
||||
bool ShouldDecrement)
|
||||
{
|
||||
Memory.SetExclusive(ThreadState, Address);
|
||||
|
||||
int CurrentValue = Memory.ReadInt32(Address);
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (Memory.TestExclusive(ThreadState, Address))
|
||||
{
|
||||
if (CurrentValue < Value)
|
||||
{
|
||||
if (ShouldDecrement)
|
||||
{
|
||||
Memory.WriteInt32(Address, CurrentValue - 1);
|
||||
}
|
||||
|
||||
Memory.ClearExclusiveForStore(ThreadState);
|
||||
}
|
||||
else
|
||||
{
|
||||
Memory.ClearExclusiveForStore(ThreadState);
|
||||
|
||||
return MakeError(ErrorModule.Kernel, KernelErr.InvalidState);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
Memory.SetExclusive(ThreadState, Address);
|
||||
|
||||
CurrentValue = Memory.ReadInt32(Address);
|
||||
}
|
||||
|
||||
if (Timeout == 0)
|
||||
{
|
||||
return MakeError(ErrorModule.Kernel, KernelErr.Timeout);
|
||||
}
|
||||
|
||||
return WaitForAddress(Process, ThreadState, Address, Timeout);
|
||||
}
|
||||
|
||||
public static ulong WaitForAddressIfEqual(Process Process,
|
||||
AThreadState ThreadState,
|
||||
AMemory Memory,
|
||||
long Address,
|
||||
int Value,
|
||||
ulong Timeout)
|
||||
{
|
||||
if (Memory.ReadInt32(Address) != Value)
|
||||
{
|
||||
return MakeError(ErrorModule.Kernel, KernelErr.InvalidState);
|
||||
}
|
||||
|
||||
if (Timeout == 0)
|
||||
{
|
||||
return MakeError(ErrorModule.Kernel, KernelErr.Timeout);
|
||||
}
|
||||
|
||||
return WaitForAddress(Process, ThreadState, Address, Timeout);
|
||||
}
|
||||
}
|
||||
|
||||
enum ArbitrationType : int
|
||||
{
|
||||
WaitIfLessThan,
|
||||
DecrementAndWaitIfLessThan,
|
||||
WaitIfEqual
|
||||
}
|
||||
|
||||
enum SignalType : int
|
||||
{
|
||||
Signal,
|
||||
IncrementAndSignalIfEqual,
|
||||
ModifyByWaitingCountAndSignalIfEqual
|
||||
}
|
||||
}
|
9
Ryujinx.HLE/HOS/Kernel/ArbitrationType.cs
Normal file
9
Ryujinx.HLE/HOS/Kernel/ArbitrationType.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
namespace Ryujinx.HLE.HOS.Kernel
|
||||
{
|
||||
enum ArbitrationType
|
||||
{
|
||||
WaitIfLessThan = 0,
|
||||
DecrementAndWaitIfLessThan = 1,
|
||||
WaitIfEqual = 2
|
||||
}
|
||||
}
|
29
Ryujinx.HLE/HOS/Kernel/HleCoreManager.cs
Normal file
29
Ryujinx.HLE/HOS/Kernel/HleCoreManager.cs
Normal file
|
@ -0,0 +1,29 @@
|
|||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel
|
||||
{
|
||||
class HleCoreManager
|
||||
{
|
||||
private ConcurrentDictionary<Thread, ManualResetEvent> Threads;
|
||||
|
||||
public HleCoreManager()
|
||||
{
|
||||
Threads = new ConcurrentDictionary<Thread, ManualResetEvent>();
|
||||
}
|
||||
|
||||
public ManualResetEvent GetThread(Thread Thread)
|
||||
{
|
||||
return Threads.GetOrAdd(Thread, (Key) => new ManualResetEvent(false));
|
||||
}
|
||||
|
||||
public void RemoveThread(Thread Thread)
|
||||
{
|
||||
if (Threads.TryRemove(Thread, out ManualResetEvent Event))
|
||||
{
|
||||
Event.Set();
|
||||
Event.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
140
Ryujinx.HLE/HOS/Kernel/HleScheduler.cs
Normal file
140
Ryujinx.HLE/HOS/Kernel/HleScheduler.cs
Normal file
|
@ -0,0 +1,140 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel
|
||||
{
|
||||
partial class KScheduler
|
||||
{
|
||||
private const int RoundRobinTimeQuantumMs = 10;
|
||||
|
||||
private int CurrentCore;
|
||||
|
||||
public bool MultiCoreScheduling { get; set; }
|
||||
|
||||
private HleCoreManager CoreManager;
|
||||
|
||||
private bool KeepPreempting;
|
||||
|
||||
public void ContextSwitch()
|
||||
{
|
||||
lock (CoreContexts)
|
||||
{
|
||||
if (MultiCoreScheduling)
|
||||
{
|
||||
int SelectedCount = 0;
|
||||
|
||||
for (int Core = 0; Core < KScheduler.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.GetThread(Thread.CurrentThread).Reset();
|
||||
}
|
||||
else if (SelectedCount == 1)
|
||||
{
|
||||
CoreManager.GetThread(Thread.CurrentThread).Set();
|
||||
}
|
||||
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.GetThread(CurrentThread.Context.Work).Reset();
|
||||
}
|
||||
|
||||
//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.GetThread(CoreContext.CurrentThread.Context.Work).Set();
|
||||
|
||||
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.GetThread(Thread.CurrentThread).WaitOne();
|
||||
}
|
||||
|
||||
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 StopThread(KThread Thread)
|
||||
{
|
||||
Thread.Context.StopExecution();
|
||||
|
||||
CoreManager.GetThread(Thread.Context.Work).Set();
|
||||
}
|
||||
|
||||
public void RemoveThread(KThread Thread)
|
||||
{
|
||||
CoreManager.RemoveThread(Thread.Context.Work);
|
||||
}
|
||||
}
|
||||
}
|
7
Ryujinx.HLE/HOS/Kernel/IKFutureSchedulerObject.cs
Normal file
7
Ryujinx.HLE/HOS/Kernel/IKFutureSchedulerObject.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace Ryujinx.HLE.HOS.Kernel
|
||||
{
|
||||
interface IKFutureSchedulerObject
|
||||
{
|
||||
void TimeUp();
|
||||
}
|
||||
}
|
678
Ryujinx.HLE/HOS/Kernel/KAddressArbiter.cs
Normal file
678
Ryujinx.HLE/HOS/Kernel/KAddressArbiter.cs
Normal file
|
@ -0,0 +1,678 @@
|
|||
using ChocolArm64.Memory;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using static Ryujinx.HLE.HOS.ErrorCode;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel
|
||||
{
|
||||
class KAddressArbiter
|
||||
{
|
||||
private const int HasListenersMask = 0x40000000;
|
||||
|
||||
private Horizon System;
|
||||
|
||||
public List<KThread> CondVarThreads;
|
||||
public List<KThread> ArbiterThreads;
|
||||
|
||||
public KAddressArbiter(Horizon System)
|
||||
{
|
||||
this.System = System;
|
||||
|
||||
CondVarThreads = new List<KThread>();
|
||||
ArbiterThreads = new List<KThread>();
|
||||
}
|
||||
|
||||
public long ArbitrateLock(
|
||||
Process Process,
|
||||
AMemory Memory,
|
||||
int OwnerHandle,
|
||||
long MutexAddress,
|
||||
int RequesterHandle)
|
||||
{
|
||||
System.CriticalSectionLock.Lock();
|
||||
|
||||
KThread CurrentThread = System.Scheduler.GetCurrentThread();
|
||||
|
||||
CurrentThread.SignaledObj = null;
|
||||
CurrentThread.ObjSyncResult = 0;
|
||||
|
||||
if (!UserToKernelInt32(Memory, MutexAddress, out int MutexValue))
|
||||
{
|
||||
System.CriticalSectionLock.Unlock();
|
||||
|
||||
return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);;
|
||||
}
|
||||
|
||||
if (MutexValue != (OwnerHandle | HasListenersMask))
|
||||
{
|
||||
System.CriticalSectionLock.Unlock();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
KThread MutexOwner = Process.HandleTable.GetData<KThread>(OwnerHandle);
|
||||
|
||||
if (MutexOwner == null)
|
||||
{
|
||||
System.CriticalSectionLock.Unlock();
|
||||
|
||||
return MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
|
||||
}
|
||||
|
||||
CurrentThread.MutexAddress = MutexAddress;
|
||||
CurrentThread.ThreadHandleForUserMutex = RequesterHandle;
|
||||
|
||||
MutexOwner.AddMutexWaiter(CurrentThread);
|
||||
|
||||
CurrentThread.Reschedule(ThreadSchedState.Paused);
|
||||
|
||||
System.CriticalSectionLock.Unlock();
|
||||
System.CriticalSectionLock.Lock();
|
||||
|
||||
if (CurrentThread.MutexOwner != null)
|
||||
{
|
||||
CurrentThread.MutexOwner.RemoveMutexWaiter(CurrentThread);
|
||||
}
|
||||
|
||||
System.CriticalSectionLock.Unlock();
|
||||
|
||||
return (uint)CurrentThread.ObjSyncResult;
|
||||
}
|
||||
|
||||
public long ArbitrateUnlock(AMemory Memory, long MutexAddress)
|
||||
{
|
||||
System.CriticalSectionLock.Lock();
|
||||
|
||||
KThread CurrentThread = System.Scheduler.GetCurrentThread();
|
||||
|
||||
(long Result, KThread NewOwnerThread) = MutexUnlock(Memory, CurrentThread, MutexAddress);
|
||||
|
||||
if (Result != 0 && NewOwnerThread != null)
|
||||
{
|
||||
NewOwnerThread.SignaledObj = null;
|
||||
NewOwnerThread.ObjSyncResult = (int)Result;
|
||||
}
|
||||
|
||||
System.CriticalSectionLock.Unlock();
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
public long WaitProcessWideKeyAtomic(
|
||||
AMemory Memory,
|
||||
long MutexAddress,
|
||||
long CondVarAddress,
|
||||
int ThreadHandle,
|
||||
long Timeout)
|
||||
{
|
||||
System.CriticalSectionLock.Lock();
|
||||
|
||||
KThread CurrentThread = System.Scheduler.GetCurrentThread();
|
||||
|
||||
CurrentThread.SignaledObj = null;
|
||||
CurrentThread.ObjSyncResult = (int)MakeError(ErrorModule.Kernel, KernelErr.Timeout);
|
||||
|
||||
if (CurrentThread.ShallBeTerminated ||
|
||||
CurrentThread.SchedFlags == ThreadSchedState.TerminationPending)
|
||||
{
|
||||
System.CriticalSectionLock.Unlock();
|
||||
|
||||
return MakeError(ErrorModule.Kernel, KernelErr.ThreadTerminating);
|
||||
}
|
||||
|
||||
(long Result, _) = MutexUnlock(Memory, CurrentThread, MutexAddress);
|
||||
|
||||
if (Result != 0)
|
||||
{
|
||||
System.CriticalSectionLock.Unlock();
|
||||
|
||||
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.CriticalSectionLock.Unlock();
|
||||
|
||||
if (Timeout > 0)
|
||||
{
|
||||
System.TimeManager.UnscheduleFutureInvocation(CurrentThread);
|
||||
}
|
||||
|
||||
System.CriticalSectionLock.Lock();
|
||||
|
||||
if (CurrentThread.MutexOwner != null)
|
||||
{
|
||||
CurrentThread.MutexOwner.RemoveMutexWaiter(CurrentThread);
|
||||
}
|
||||
|
||||
CondVarThreads.Remove(CurrentThread);
|
||||
|
||||
System.CriticalSectionLock.Unlock();
|
||||
|
||||
return (uint)CurrentThread.ObjSyncResult;
|
||||
}
|
||||
|
||||
private (long, KThread) MutexUnlock(AMemory Memory, KThread CurrentThread, long 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 = 0;
|
||||
|
||||
NewOwnerThread.ReleaseAndResume();
|
||||
}
|
||||
|
||||
long Result = 0;
|
||||
|
||||
if (!KernelToUserInt32(Memory, MutexAddress, MutexValue))
|
||||
{
|
||||
Result = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
|
||||
}
|
||||
|
||||
return (Result, NewOwnerThread);
|
||||
}
|
||||
|
||||
public void SignalProcessWideKey(Process Process, AMemory Memory, long Address, int Count)
|
||||
{
|
||||
Queue<KThread> SignaledThreads = new Queue<KThread>();
|
||||
|
||||
System.CriticalSectionLock.Lock();
|
||||
|
||||
IOrderedEnumerable<KThread> SortedThreads = CondVarThreads.OrderBy(x => x.DynamicPriority);
|
||||
|
||||
foreach (KThread Thread in SortedThreads.Where(x => x.CondVarAddress == Address))
|
||||
{
|
||||
TryAcquireMutex(Process, Memory, 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.CriticalSectionLock.Unlock();
|
||||
}
|
||||
|
||||
private KThread TryAcquireMutex(Process Process, AMemory Memory, KThread Requester)
|
||||
{
|
||||
long Address = Requester.MutexAddress;
|
||||
|
||||
Memory.SetExclusive(0, Address);
|
||||
|
||||
if (!UserToKernelInt32(Memory, Address, out int MutexValue))
|
||||
{
|
||||
//Invalid address.
|
||||
Memory.ClearExclusive(0);
|
||||
|
||||
Requester.SignaledObj = null;
|
||||
Requester.ObjSyncResult = (int)MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (Memory.TestExclusive(0, Address))
|
||||
{
|
||||
if (MutexValue != 0)
|
||||
{
|
||||
//Update value to indicate there is a mutex waiter now.
|
||||
Memory.WriteInt32(Address, MutexValue | HasListenersMask);
|
||||
}
|
||||
else
|
||||
{
|
||||
//No thread owning the mutex, assign to requesting thread.
|
||||
Memory.WriteInt32(Address, Requester.ThreadHandleForUserMutex);
|
||||
}
|
||||
|
||||
Memory.ClearExclusiveForStore(0);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
Memory.SetExclusive(0, Address);
|
||||
|
||||
MutexValue = Memory.ReadInt32(Address);
|
||||
}
|
||||
|
||||
if (MutexValue == 0)
|
||||
{
|
||||
//We now own the mutex.
|
||||
Requester.SignaledObj = null;
|
||||
Requester.ObjSyncResult = 0;
|
||||
|
||||
Requester.ReleaseAndResume();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
MutexValue &= ~HasListenersMask;
|
||||
|
||||
KThread MutexOwner = Process.HandleTable.GetData<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 = (int)MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
|
||||
|
||||
Requester.ReleaseAndResume();
|
||||
}
|
||||
|
||||
return MutexOwner;
|
||||
}
|
||||
|
||||
public long WaitForAddressIfEqual(AMemory Memory, long Address, int Value, long Timeout)
|
||||
{
|
||||
KThread CurrentThread = System.Scheduler.GetCurrentThread();
|
||||
|
||||
System.CriticalSectionLock.Lock();
|
||||
|
||||
if (CurrentThread.ShallBeTerminated ||
|
||||
CurrentThread.SchedFlags == ThreadSchedState.TerminationPending)
|
||||
{
|
||||
System.CriticalSectionLock.Unlock();
|
||||
|
||||
return MakeError(ErrorModule.Kernel, KernelErr.ThreadTerminating);
|
||||
}
|
||||
|
||||
CurrentThread.SignaledObj = null;
|
||||
CurrentThread.ObjSyncResult = (int)MakeError(ErrorModule.Kernel, KernelErr.Timeout);
|
||||
|
||||
if (!UserToKernelInt32(Memory, Address, out int CurrentValue))
|
||||
{
|
||||
System.CriticalSectionLock.Unlock();
|
||||
|
||||
return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
|
||||
}
|
||||
|
||||
if (CurrentValue == Value)
|
||||
{
|
||||
if (Timeout == 0)
|
||||
{
|
||||
System.CriticalSectionLock.Unlock();
|
||||
|
||||
return MakeError(ErrorModule.Kernel, KernelErr.Timeout);
|
||||
}
|
||||
|
||||
CurrentThread.MutexAddress = Address;
|
||||
CurrentThread.WaitingInArbitration = true;
|
||||
|
||||
InsertSortedByPriority(ArbiterThreads, CurrentThread);
|
||||
|
||||
CurrentThread.Reschedule(ThreadSchedState.Paused);
|
||||
|
||||
if (Timeout > 0)
|
||||
{
|
||||
System.TimeManager.ScheduleFutureInvocation(CurrentThread, Timeout);
|
||||
}
|
||||
|
||||
System.CriticalSectionLock.Unlock();
|
||||
|
||||
if (Timeout > 0)
|
||||
{
|
||||
System.TimeManager.UnscheduleFutureInvocation(CurrentThread);
|
||||
}
|
||||
|
||||
System.CriticalSectionLock.Lock();
|
||||
|
||||
if (CurrentThread.WaitingInArbitration)
|
||||
{
|
||||
ArbiterThreads.Remove(CurrentThread);
|
||||
|
||||
CurrentThread.WaitingInArbitration = false;
|
||||
}
|
||||
|
||||
System.CriticalSectionLock.Unlock();
|
||||
|
||||
return CurrentThread.ObjSyncResult;
|
||||
}
|
||||
|
||||
System.CriticalSectionLock.Unlock();
|
||||
|
||||
return MakeError(ErrorModule.Kernel, KernelErr.InvalidState);
|
||||
}
|
||||
|
||||
public long WaitForAddressIfLessThan(
|
||||
AMemory Memory,
|
||||
long Address,
|
||||
int Value,
|
||||
bool ShouldDecrement,
|
||||
long Timeout)
|
||||
{
|
||||
KThread CurrentThread = System.Scheduler.GetCurrentThread();
|
||||
|
||||
System.CriticalSectionLock.Lock();
|
||||
|
||||
if (CurrentThread.ShallBeTerminated ||
|
||||
CurrentThread.SchedFlags == ThreadSchedState.TerminationPending)
|
||||
{
|
||||
System.CriticalSectionLock.Unlock();
|
||||
|
||||
return MakeError(ErrorModule.Kernel, KernelErr.ThreadTerminating);
|
||||
}
|
||||
|
||||
CurrentThread.SignaledObj = null;
|
||||
CurrentThread.ObjSyncResult = (int)MakeError(ErrorModule.Kernel, KernelErr.Timeout);
|
||||
|
||||
//If ShouldDecrement is true, do atomic decrement of the value at Address.
|
||||
Memory.SetExclusive(0, Address);
|
||||
|
||||
if (!UserToKernelInt32(Memory, Address, out int CurrentValue))
|
||||
{
|
||||
System.CriticalSectionLock.Unlock();
|
||||
|
||||
return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
|
||||
}
|
||||
|
||||
if (ShouldDecrement)
|
||||
{
|
||||
while (CurrentValue < Value)
|
||||
{
|
||||
if (Memory.TestExclusive(0, Address))
|
||||
{
|
||||
Memory.WriteInt32(Address, CurrentValue - 1);
|
||||
|
||||
Memory.ClearExclusiveForStore(0);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
Memory.SetExclusive(0, Address);
|
||||
|
||||
CurrentValue = Memory.ReadInt32(Address);
|
||||
}
|
||||
}
|
||||
|
||||
Memory.ClearExclusive(0);
|
||||
|
||||
if (CurrentValue < Value)
|
||||
{
|
||||
if (Timeout == 0)
|
||||
{
|
||||
System.CriticalSectionLock.Unlock();
|
||||
|
||||
return MakeError(ErrorModule.Kernel, KernelErr.Timeout);
|
||||
}
|
||||
|
||||
CurrentThread.MutexAddress = Address;
|
||||
CurrentThread.WaitingInArbitration = true;
|
||||
|
||||
InsertSortedByPriority(ArbiterThreads, CurrentThread);
|
||||
|
||||
CurrentThread.Reschedule(ThreadSchedState.Paused);
|
||||
|
||||
if (Timeout > 0)
|
||||
{
|
||||
System.TimeManager.ScheduleFutureInvocation(CurrentThread, Timeout);
|
||||
}
|
||||
|
||||
System.CriticalSectionLock.Unlock();
|
||||
|
||||
if (Timeout > 0)
|
||||
{
|
||||
System.TimeManager.UnscheduleFutureInvocation(CurrentThread);
|
||||
}
|
||||
|
||||
System.CriticalSectionLock.Lock();
|
||||
|
||||
if (CurrentThread.WaitingInArbitration)
|
||||
{
|
||||
ArbiterThreads.Remove(CurrentThread);
|
||||
|
||||
CurrentThread.WaitingInArbitration = false;
|
||||
}
|
||||
|
||||
System.CriticalSectionLock.Unlock();
|
||||
|
||||
return CurrentThread.ObjSyncResult;
|
||||
}
|
||||
|
||||
System.CriticalSectionLock.Unlock();
|
||||
|
||||
return MakeError(ErrorModule.Kernel, KernelErr.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 long Signal(long Address, int Count)
|
||||
{
|
||||
System.CriticalSectionLock.Lock();
|
||||
|
||||
WakeArbiterThreads(Address, Count);
|
||||
|
||||
System.CriticalSectionLock.Unlock();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long SignalAndIncrementIfEqual(AMemory Memory, long Address, int Value, int Count)
|
||||
{
|
||||
System.CriticalSectionLock.Lock();
|
||||
|
||||
Memory.SetExclusive(0, Address);
|
||||
|
||||
if (!UserToKernelInt32(Memory, Address, out int CurrentValue))
|
||||
{
|
||||
System.CriticalSectionLock.Unlock();
|
||||
|
||||
return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
|
||||
}
|
||||
|
||||
while (CurrentValue == Value)
|
||||
{
|
||||
if (Memory.TestExclusive(0, Address))
|
||||
{
|
||||
Memory.WriteInt32(Address, CurrentValue + 1);
|
||||
|
||||
Memory.ClearExclusiveForStore(0);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
Memory.SetExclusive(0, Address);
|
||||
|
||||
CurrentValue = Memory.ReadInt32(Address);
|
||||
}
|
||||
|
||||
Memory.ClearExclusive(0);
|
||||
|
||||
if (CurrentValue != Value)
|
||||
{
|
||||
System.CriticalSectionLock.Unlock();
|
||||
|
||||
return MakeError(ErrorModule.Kernel, KernelErr.InvalidState);
|
||||
}
|
||||
|
||||
WakeArbiterThreads(Address, Count);
|
||||
|
||||
System.CriticalSectionLock.Unlock();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long SignalAndModifyIfEqual(AMemory Memory, long Address, int Value, int Count)
|
||||
{
|
||||
System.CriticalSectionLock.Lock();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Memory.SetExclusive(0, Address);
|
||||
|
||||
if (!UserToKernelInt32(Memory, Address, out int CurrentValue))
|
||||
{
|
||||
System.CriticalSectionLock.Unlock();
|
||||
|
||||
return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
|
||||
}
|
||||
|
||||
while (CurrentValue == Value)
|
||||
{
|
||||
if (Memory.TestExclusive(0, Address))
|
||||
{
|
||||
Memory.WriteInt32(Address, CurrentValue + Offset);
|
||||
|
||||
Memory.ClearExclusiveForStore(0);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
Memory.SetExclusive(0, Address);
|
||||
|
||||
CurrentValue = Memory.ReadInt32(Address);
|
||||
}
|
||||
|
||||
Memory.ClearExclusive(0);
|
||||
|
||||
if (CurrentValue != Value)
|
||||
{
|
||||
System.CriticalSectionLock.Unlock();
|
||||
|
||||
return MakeError(ErrorModule.Kernel, KernelErr.InvalidState);
|
||||
}
|
||||
|
||||
WakeArbiterThreads(Address, Count);
|
||||
|
||||
System.CriticalSectionLock.Unlock();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void WakeArbiterThreads(long 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 = 0;
|
||||
|
||||
Thread.ReleaseAndResume();
|
||||
|
||||
Thread.WaitingInArbitration = false;
|
||||
|
||||
ArbiterThreads.Remove(Thread);
|
||||
}
|
||||
}
|
||||
|
||||
private bool UserToKernelInt32(AMemory Memory, long Address, out int Value)
|
||||
{
|
||||
if (Memory.IsMapped(Address))
|
||||
{
|
||||
Value = Memory.ReadInt32(Address);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Value = 0;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool KernelToUserInt32(AMemory Memory, long Address, int Value)
|
||||
{
|
||||
if (Memory.IsMapped(Address))
|
||||
{
|
||||
Memory.WriteInt32ToSharedAddr(Address, Value);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
67
Ryujinx.HLE/HOS/Kernel/KCoreContext.cs
Normal file
67
Ryujinx.HLE/HOS/Kernel/KCoreContext.cs
Normal file
|
@ -0,0 +1,67 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel
|
||||
{
|
||||
class KCoreContext
|
||||
{
|
||||
private KScheduler Scheduler;
|
||||
|
||||
private HleCoreManager CoreManager;
|
||||
|
||||
public bool ContextSwitchNeeded { get; private set; }
|
||||
|
||||
public KThread CurrentThread { get; private set; }
|
||||
public KThread SelectedThread { get; private set; }
|
||||
|
||||
public KCoreContext(KScheduler Scheduler, HleCoreManager CoreManager)
|
||||
{
|
||||
this.Scheduler = Scheduler;
|
||||
this.CoreManager = CoreManager;
|
||||
}
|
||||
|
||||
public void SelectThread(KThread Thread)
|
||||
{
|
||||
SelectedThread = Thread;
|
||||
|
||||
if (Thread != null)
|
||||
{
|
||||
Thread.LastScheduledTicks = (uint)Environment.TickCount;
|
||||
}
|
||||
|
||||
ContextSwitchNeeded = true;
|
||||
}
|
||||
|
||||
public void UpdateCurrentThread()
|
||||
{
|
||||
ContextSwitchNeeded = false;
|
||||
|
||||
CurrentThread = SelectedThread;
|
||||
}
|
||||
|
||||
public void ContextSwitch()
|
||||
{
|
||||
ContextSwitchNeeded = false;
|
||||
|
||||
if (CurrentThread != null)
|
||||
{
|
||||
CoreManager.GetThread(CurrentThread.Context.Work).Reset();
|
||||
}
|
||||
|
||||
CurrentThread = SelectedThread;
|
||||
|
||||
if (CurrentThread != null)
|
||||
{
|
||||
CurrentThread.ClearExclusive();
|
||||
|
||||
CoreManager.GetThread(CurrentThread.Context.Work).Set();
|
||||
|
||||
CurrentThread.Context.Execute();
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveThread(KThread Thread)
|
||||
{
|
||||
//TODO.
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,38 @@
|
|||
namespace Ryujinx.HLE.HOS.Kernel
|
||||
{
|
||||
class KEvent : KSynchronizationObject { }
|
||||
class KEvent : KSynchronizationObject
|
||||
{
|
||||
private bool Signaled;
|
||||
|
||||
public string Name { get; private set; }
|
||||
|
||||
public KEvent(Horizon System, string Name = "") : base(System)
|
||||
{
|
||||
this.Name = Name;
|
||||
}
|
||||
|
||||
public override void Signal()
|
||||
{
|
||||
System.CriticalSectionLock.Lock();
|
||||
|
||||
if (!Signaled)
|
||||
{
|
||||
Signaled = true;
|
||||
|
||||
base.Signal();
|
||||
}
|
||||
|
||||
System.CriticalSectionLock.Unlock();
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
Signaled = false;
|
||||
}
|
||||
|
||||
public override bool IsSignaled()
|
||||
{
|
||||
return Signaled;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,370 +0,0 @@
|
|||
using Ryujinx.HLE.Logging;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel
|
||||
{
|
||||
class KProcessScheduler : IDisposable
|
||||
{
|
||||
private ConcurrentDictionary<KThread, SchedulerThread> AllThreads;
|
||||
|
||||
private ThreadQueue WaitingToRun;
|
||||
|
||||
private KThread[] CoreThreads;
|
||||
|
||||
private bool[] CoreReschedule;
|
||||
|
||||
private object SchedLock;
|
||||
|
||||
private Logger Log;
|
||||
|
||||
public KProcessScheduler(Logger Log)
|
||||
{
|
||||
this.Log = Log;
|
||||
|
||||
AllThreads = new ConcurrentDictionary<KThread, SchedulerThread>();
|
||||
|
||||
WaitingToRun = new ThreadQueue();
|
||||
|
||||
CoreThreads = new KThread[4];
|
||||
|
||||
CoreReschedule = new bool[4];
|
||||
|
||||
SchedLock = new object();
|
||||
}
|
||||
|
||||
public void StartThread(KThread Thread)
|
||||
{
|
||||
lock (SchedLock)
|
||||
{
|
||||
SchedulerThread SchedThread = new SchedulerThread(Thread);
|
||||
|
||||
if (!AllThreads.TryAdd(Thread, SchedThread))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (TryAddToCore(Thread))
|
||||
{
|
||||
Thread.Thread.Execute();
|
||||
|
||||
PrintDbgThreadInfo(Thread, "running.");
|
||||
}
|
||||
else
|
||||
{
|
||||
WaitingToRun.Push(SchedThread);
|
||||
|
||||
PrintDbgThreadInfo(Thread, "waiting to run.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveThread(KThread Thread)
|
||||
{
|
||||
PrintDbgThreadInfo(Thread, "exited.");
|
||||
|
||||
lock (SchedLock)
|
||||
{
|
||||
if (AllThreads.TryRemove(Thread, out SchedulerThread SchedThread))
|
||||
{
|
||||
WaitingToRun.Remove(SchedThread);
|
||||
|
||||
SchedThread.Dispose();
|
||||
}
|
||||
|
||||
int ActualCore = Thread.ActualCore;
|
||||
|
||||
SchedulerThread NewThread = WaitingToRun.Pop(ActualCore);
|
||||
|
||||
if (NewThread == null)
|
||||
{
|
||||
Log.PrintDebug(LogClass.KernelScheduler, $"Nothing to run on core {ActualCore}!");
|
||||
|
||||
CoreThreads[ActualCore] = null;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
NewThread.Thread.ActualCore = ActualCore;
|
||||
|
||||
RunThread(NewThread);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetThreadActivity(KThread Thread, bool Active)
|
||||
{
|
||||
SchedulerThread SchedThread = AllThreads[Thread];
|
||||
|
||||
SchedThread.IsActive = Active;
|
||||
|
||||
if (Active)
|
||||
{
|
||||
SchedThread.WaitActivity.Set();
|
||||
}
|
||||
else
|
||||
{
|
||||
SchedThread.WaitActivity.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
public void EnterWait(KThread Thread, int TimeoutMs = Timeout.Infinite)
|
||||
{
|
||||
SchedulerThread SchedThread = AllThreads[Thread];
|
||||
|
||||
Suspend(Thread);
|
||||
|
||||
SchedThread.WaitSync.WaitOne(TimeoutMs);
|
||||
|
||||
TryResumingExecution(SchedThread);
|
||||
}
|
||||
|
||||
public void WakeUp(KThread Thread)
|
||||
{
|
||||
AllThreads[Thread].WaitSync.Set();
|
||||
}
|
||||
|
||||
public void ForceWakeUp(KThread Thread)
|
||||
{
|
||||
if (AllThreads.TryGetValue(Thread, out SchedulerThread SchedThread))
|
||||
{
|
||||
SchedThread.WaitSync.Set();
|
||||
SchedThread.WaitActivity.Set();
|
||||
SchedThread.WaitSched.Set();
|
||||
}
|
||||
}
|
||||
|
||||
public void ChangeCore(KThread Thread, int IdealCore, int CoreMask)
|
||||
{
|
||||
lock (SchedLock)
|
||||
{
|
||||
if (IdealCore != -3)
|
||||
{
|
||||
Thread.IdealCore = IdealCore;
|
||||
}
|
||||
|
||||
Thread.CoreMask = CoreMask;
|
||||
|
||||
if (AllThreads.ContainsKey(Thread))
|
||||
{
|
||||
SetReschedule(Thread.ActualCore);
|
||||
|
||||
SchedulerThread SchedThread = AllThreads[Thread];
|
||||
|
||||
//Note: Aways if the thread is on the queue first, and try
|
||||
//adding to a new core later, to ensure that a thread that
|
||||
//is already running won't be added to another core.
|
||||
if (WaitingToRun.HasThread(SchedThread) && TryAddToCore(Thread))
|
||||
{
|
||||
WaitingToRun.Remove(SchedThread);
|
||||
|
||||
RunThread(SchedThread);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Suspend(KThread Thread)
|
||||
{
|
||||
lock (SchedLock)
|
||||
{
|
||||
PrintDbgThreadInfo(Thread, "suspended.");
|
||||
|
||||
int ActualCore = Thread.ActualCore;
|
||||
|
||||
CoreReschedule[ActualCore] = false;
|
||||
|
||||
SchedulerThread SchedThread = WaitingToRun.Pop(ActualCore);
|
||||
|
||||
if (SchedThread != null)
|
||||
{
|
||||
SchedThread.Thread.ActualCore = ActualCore;
|
||||
|
||||
CoreThreads[ActualCore] = SchedThread.Thread;
|
||||
|
||||
RunThread(SchedThread);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.PrintDebug(LogClass.KernelScheduler, $"Nothing to run on core {Thread.ActualCore}!");
|
||||
|
||||
CoreThreads[ActualCore] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetReschedule(int Core)
|
||||
{
|
||||
lock (SchedLock)
|
||||
{
|
||||
CoreReschedule[Core] = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Reschedule(KThread Thread)
|
||||
{
|
||||
bool NeedsReschedule;
|
||||
|
||||
lock (SchedLock)
|
||||
{
|
||||
int ActualCore = Thread.ActualCore;
|
||||
|
||||
NeedsReschedule = CoreReschedule[ActualCore];
|
||||
|
||||
CoreReschedule[ActualCore] = false;
|
||||
}
|
||||
|
||||
if (NeedsReschedule)
|
||||
{
|
||||
Yield(Thread, Thread.ActualPriority - 1);
|
||||
}
|
||||
}
|
||||
|
||||
public void Yield(KThread Thread)
|
||||
{
|
||||
Yield(Thread, Thread.ActualPriority);
|
||||
}
|
||||
|
||||
private void Yield(KThread Thread, int MinPriority)
|
||||
{
|
||||
PrintDbgThreadInfo(Thread, "yielded execution.");
|
||||
|
||||
lock (SchedLock)
|
||||
{
|
||||
int ActualCore = Thread.ActualCore;
|
||||
|
||||
SchedulerThread NewThread = WaitingToRun.Pop(ActualCore, MinPriority);
|
||||
|
||||
if (NewThread != null)
|
||||
{
|
||||
NewThread.Thread.ActualCore = ActualCore;
|
||||
|
||||
CoreThreads[ActualCore] = NewThread.Thread;
|
||||
|
||||
RunThread(NewThread);
|
||||
}
|
||||
else
|
||||
{
|
||||
CoreThreads[ActualCore] = null;
|
||||
}
|
||||
}
|
||||
|
||||
Resume(Thread);
|
||||
}
|
||||
|
||||
public void Resume(KThread Thread)
|
||||
{
|
||||
TryResumingExecution(AllThreads[Thread]);
|
||||
}
|
||||
|
||||
private void TryResumingExecution(SchedulerThread SchedThread)
|
||||
{
|
||||
KThread Thread = SchedThread.Thread;
|
||||
|
||||
PrintDbgThreadInfo(Thread, "trying to resume...");
|
||||
|
||||
SchedThread.WaitActivity.WaitOne();
|
||||
|
||||
lock (SchedLock)
|
||||
{
|
||||
if (TryAddToCore(Thread))
|
||||
{
|
||||
PrintDbgThreadInfo(Thread, "resuming execution...");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
WaitingToRun.Push(SchedThread);
|
||||
|
||||
SetReschedule(Thread.ProcessorId);
|
||||
|
||||
PrintDbgThreadInfo(Thread, "entering wait state...");
|
||||
}
|
||||
|
||||
SchedThread.WaitSched.WaitOne();
|
||||
|
||||
PrintDbgThreadInfo(Thread, "resuming execution...");
|
||||
}
|
||||
|
||||
private void RunThread(SchedulerThread SchedThread)
|
||||
{
|
||||
if (!SchedThread.Thread.Thread.Execute())
|
||||
{
|
||||
PrintDbgThreadInfo(SchedThread.Thread, "waked.");
|
||||
|
||||
SchedThread.WaitSched.Set();
|
||||
}
|
||||
else
|
||||
{
|
||||
PrintDbgThreadInfo(SchedThread.Thread, "running.");
|
||||
}
|
||||
}
|
||||
|
||||
public void Resort(KThread Thread)
|
||||
{
|
||||
if (AllThreads.TryGetValue(Thread, out SchedulerThread SchedThread))
|
||||
{
|
||||
WaitingToRun.Resort(SchedThread);
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryAddToCore(KThread Thread)
|
||||
{
|
||||
//First, try running it on Ideal Core.
|
||||
int IdealCore = Thread.IdealCore;
|
||||
|
||||
if (IdealCore != -1 && CoreThreads[IdealCore] == null)
|
||||
{
|
||||
Thread.ActualCore = IdealCore;
|
||||
|
||||
CoreThreads[IdealCore] = Thread;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//If that fails, then try running on any core allowed by Core Mask.
|
||||
int CoreMask = Thread.CoreMask;
|
||||
|
||||
for (int Core = 0; Core < CoreThreads.Length; Core++, CoreMask >>= 1)
|
||||
{
|
||||
if ((CoreMask & 1) != 0 && CoreThreads[Core] == null)
|
||||
{
|
||||
Thread.ActualCore = Core;
|
||||
|
||||
CoreThreads[Core] = Thread;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void PrintDbgThreadInfo(KThread Thread, string Message)
|
||||
{
|
||||
Log.PrintDebug(LogClass.KernelScheduler, "(" +
|
||||
"ThreadId = " + Thread.ThreadId + ", " +
|
||||
"CoreMask = 0x" + Thread.CoreMask.ToString("x1") + ", " +
|
||||
"ActualCore = " + Thread.ActualCore + ", " +
|
||||
"IdealCore = " + Thread.IdealCore + ", " +
|
||||
"ActualPriority = " + Thread.ActualPriority + ", " +
|
||||
"WantedPriority = " + Thread.WantedPriority + ") " + Message);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool Disposing)
|
||||
{
|
||||
if (Disposing)
|
||||
{
|
||||
foreach (SchedulerThread SchedThread in AllThreads.Values)
|
||||
{
|
||||
SchedThread.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
93
Ryujinx.HLE/HOS/Kernel/KRecursiveLock.cs
Normal file
93
Ryujinx.HLE/HOS/Kernel/KRecursiveLock.cs
Normal file
|
@ -0,0 +1,93 @@
|
|||
using ChocolArm64;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel
|
||||
{
|
||||
class KRecursiveLock
|
||||
{
|
||||
private Horizon System;
|
||||
|
||||
public object LockObj { get; private set; }
|
||||
|
||||
private int RecursionCount;
|
||||
|
||||
public KRecursiveLock(Horizon System)
|
||||
{
|
||||
this.System = System;
|
||||
|
||||
LockObj = new object();
|
||||
}
|
||||
|
||||
public void Lock()
|
||||
{
|
||||
Monitor.Enter(LockObj);
|
||||
|
||||
RecursionCount++;
|
||||
}
|
||||
|
||||
public void Unlock()
|
||||
{
|
||||
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)
|
||||
{
|
||||
AThread 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
235
Ryujinx.HLE/HOS/Kernel/KScheduler.cs
Normal file
235
Ryujinx.HLE/HOS/Kernel/KScheduler.cs
Normal file
|
@ -0,0 +1,235 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel
|
||||
{
|
||||
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)
|
||||
{
|
||||
this.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);
|
||||
}
|
||||
|
||||
Thread PreemptionThread = new Thread(PreemptCurrentThread);
|
||||
|
||||
KeepPreempting = true;
|
||||
|
||||
PreemptionThread.Start();
|
||||
}
|
||||
|
||||
private void PreemptThreads()
|
||||
{
|
||||
System.CriticalSectionLock.Lock();
|
||||
|
||||
PreemptThread(PreemptionPriorityCores012, 0);
|
||||
PreemptThread(PreemptionPriorityCores012, 1);
|
||||
PreemptThread(PreemptionPriorityCores012, 2);
|
||||
PreemptThread(PreemptionPriorityCore3, 3);
|
||||
|
||||
System.CriticalSectionLock.Unlock();
|
||||
}
|
||||
|
||||
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.LastScheduledTicks >= Thread.LastScheduledTicks)
|
||||
{
|
||||
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 void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool Disposing)
|
||||
{
|
||||
if (Disposing)
|
||||
{
|
||||
KeepPreempting = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
207
Ryujinx.HLE/HOS/Kernel/KSchedulingData.cs
Normal file
207
Ryujinx.HLE/HOS/Kernel/KSchedulingData.cs
Normal file
|
@ -0,0 +1,207 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel
|
||||
{
|
||||
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];
|
||||
}
|
||||
}
|
||||
}
|
135
Ryujinx.HLE/HOS/Kernel/KSynchronization.cs
Normal file
135
Ryujinx.HLE/HOS/Kernel/KSynchronization.cs
Normal file
|
@ -0,0 +1,135 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
using static Ryujinx.HLE.HOS.ErrorCode;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel
|
||||
{
|
||||
class KSynchronization
|
||||
{
|
||||
private Horizon System;
|
||||
|
||||
public KSynchronization(Horizon System)
|
||||
{
|
||||
this.System = System;
|
||||
}
|
||||
|
||||
public long WaitFor(KSynchronizationObject[] SyncObjs, long Timeout, ref int HndIndex)
|
||||
{
|
||||
long Result = MakeError(ErrorModule.Kernel, KernelErr.Timeout);
|
||||
|
||||
System.CriticalSectionLock.Lock();
|
||||
|
||||
//Check if objects are already signaled before waiting.
|
||||
for (int Index = 0; Index < SyncObjs.Length; Index++)
|
||||
{
|
||||
if (!SyncObjs[Index].IsSignaled())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
HndIndex = Index;
|
||||
|
||||
System.CriticalSectionLock.Unlock();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (Timeout == 0)
|
||||
{
|
||||
System.CriticalSectionLock.Unlock();
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
KThread CurrentThread = System.Scheduler.GetCurrentThread();
|
||||
|
||||
if (CurrentThread.ShallBeTerminated ||
|
||||
CurrentThread.SchedFlags == ThreadSchedState.TerminationPending)
|
||||
{
|
||||
Result = MakeError(ErrorModule.Kernel, KernelErr.ThreadTerminating);
|
||||
}
|
||||
else if (CurrentThread.SyncCancelled)
|
||||
{
|
||||
CurrentThread.SyncCancelled = false;
|
||||
|
||||
Result = MakeError(ErrorModule.Kernel, KernelErr.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 = (int)Result;
|
||||
|
||||
CurrentThread.Reschedule(ThreadSchedState.Paused);
|
||||
|
||||
if (Timeout > 0)
|
||||
{
|
||||
System.TimeManager.ScheduleFutureInvocation(CurrentThread, Timeout);
|
||||
}
|
||||
|
||||
System.CriticalSectionLock.Unlock();
|
||||
|
||||
CurrentThread.WaitingSync = false;
|
||||
|
||||
if (Timeout > 0)
|
||||
{
|
||||
System.TimeManager.UnscheduleFutureInvocation(CurrentThread);
|
||||
}
|
||||
|
||||
System.CriticalSectionLock.Lock();
|
||||
|
||||
Result = (uint)CurrentThread.ObjSyncResult;
|
||||
|
||||
HndIndex = -1;
|
||||
|
||||
for (int Index = 0; Index < SyncObjs.Length; Index++)
|
||||
{
|
||||
SyncObjs[Index].RemoveWaitingThread(SyncNodes[Index]);
|
||||
|
||||
if (SyncObjs[Index] == CurrentThread.SignaledObj)
|
||||
{
|
||||
HndIndex = Index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
System.CriticalSectionLock.Unlock();
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
public void SignalObject(KSynchronizationObject SyncObj)
|
||||
{
|
||||
System.CriticalSectionLock.Lock();
|
||||
|
||||
if (SyncObj.IsSignaled())
|
||||
{
|
||||
LinkedListNode<KThread> Node = SyncObj.WaitingThreads.First;
|
||||
|
||||
while (Node != null)
|
||||
{
|
||||
KThread Thread = Node.Value;
|
||||
|
||||
if ((Thread.SchedFlags & ThreadSchedState.LowNibbleMask) == ThreadSchedState.Paused)
|
||||
{
|
||||
Thread.SignaledObj = SyncObj;
|
||||
Thread.ObjSyncResult = 0;
|
||||
|
||||
Thread.Reschedule(ThreadSchedState.Running);
|
||||
}
|
||||
|
||||
Node = Node.Next;
|
||||
}
|
||||
}
|
||||
|
||||
System.CriticalSectionLock.Unlock();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,28 +1,38 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel
|
||||
{
|
||||
class KSynchronizationObject : IDisposable
|
||||
class KSynchronizationObject
|
||||
{
|
||||
public ManualResetEvent WaitEvent { get; private set; }
|
||||
public LinkedList<KThread> WaitingThreads;
|
||||
|
||||
public KSynchronizationObject()
|
||||
protected Horizon System;
|
||||
|
||||
public KSynchronizationObject(Horizon System)
|
||||
{
|
||||
WaitEvent = new ManualResetEvent(false);
|
||||
this.System = System;
|
||||
|
||||
WaitingThreads = new LinkedList<KThread>();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
public LinkedListNode<KThread> AddWaitingThread(KThread Thread)
|
||||
{
|
||||
Dispose(true);
|
||||
return WaitingThreads.AddLast(Thread);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool Disposing)
|
||||
public void RemoveWaitingThread(LinkedListNode<KThread> Node)
|
||||
{
|
||||
if (Disposing)
|
||||
{
|
||||
WaitEvent.Dispose();
|
||||
}
|
||||
WaitingThreads.Remove(Node);
|
||||
}
|
||||
|
||||
public virtual void Signal()
|
||||
{
|
||||
System.Synchronization.SignalObject(this);
|
||||
}
|
||||
|
||||
public virtual bool IsSignaled()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,98 +1,883 @@
|
|||
using ChocolArm64;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using static Ryujinx.HLE.HOS.ErrorCode;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel
|
||||
{
|
||||
class KThread : KSynchronizationObject
|
||||
class KThread : KSynchronizationObject, IKFutureSchedulerObject
|
||||
{
|
||||
public AThread Thread { get; private set; }
|
||||
public AThread Context { get; private set; }
|
||||
|
||||
public int CoreMask { get; set; }
|
||||
|
||||
public long MutexAddress { get; set; }
|
||||
public long CondVarAddress { get; set; }
|
||||
public long ArbiterWaitAddress { get; set; }
|
||||
|
||||
public bool CondVarSignaled { get; set; }
|
||||
public bool ArbiterSignaled { get; set; }
|
||||
|
||||
private Process Process;
|
||||
|
||||
public List<KThread> MutexWaiters { get; private set; }
|
||||
|
||||
public KThread MutexOwner { get; set; }
|
||||
|
||||
public int ActualPriority { get; private set; }
|
||||
public int WantedPriority { get; private set; }
|
||||
|
||||
public int ActualCore { get; set; }
|
||||
public int ProcessorId { get; set; }
|
||||
public int IdealCore { get; set; }
|
||||
|
||||
public int WaitHandle { get; set; }
|
||||
|
||||
public long LastPc { get; set; }
|
||||
public long AffinityMask { get; set; }
|
||||
|
||||
public int ThreadId { get; private set; }
|
||||
|
||||
public KSynchronizationObject SignaledObj;
|
||||
|
||||
public long CondVarAddress { get; set; }
|
||||
public long MutexAddress { get; set; }
|
||||
|
||||
public Process Owner { get; private set; }
|
||||
|
||||
public long LastScheduledTicks { get; set; }
|
||||
|
||||
public LinkedListNode<KThread>[] SiblingsPerCore { get; private set; }
|
||||
|
||||
private LinkedListNode<KThread> WithholderNode;
|
||||
|
||||
private LinkedList<KThread> MutexWaiters;
|
||||
private LinkedListNode<KThread> MutexWaiterNode;
|
||||
|
||||
public KThread MutexOwner { get; private set; }
|
||||
|
||||
public int ThreadHandleForUserMutex { get; set; }
|
||||
|
||||
private ThreadSchedState ForcePauseFlags;
|
||||
|
||||
public int 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(
|
||||
AThread Thread,
|
||||
Process Process,
|
||||
Horizon System,
|
||||
int ProcessorId,
|
||||
int Priority,
|
||||
int ThreadId)
|
||||
int ThreadId) : base(System)
|
||||
{
|
||||
this.Thread = Thread;
|
||||
this.Process = Process;
|
||||
this.ProcessorId = ProcessorId;
|
||||
this.IdealCore = ProcessorId;
|
||||
this.ThreadId = ThreadId;
|
||||
this.ThreadId = ThreadId;
|
||||
|
||||
MutexWaiters = new List<KThread>();
|
||||
Context = Thread;
|
||||
Owner = Process;
|
||||
PreferredCore = ProcessorId;
|
||||
Scheduler = System.Scheduler;
|
||||
SchedulingData = System.Scheduler.SchedulingData;
|
||||
|
||||
CoreMask = 1 << ProcessorId;
|
||||
SiblingsPerCore = new LinkedListNode<KThread>[KScheduler.CpuCoresCount];
|
||||
|
||||
ActualPriority = WantedPriority = Priority;
|
||||
MutexWaiters = new LinkedList<KThread>();
|
||||
|
||||
AffinityMask = 1 << ProcessorId;
|
||||
|
||||
DynamicPriority = BasePriority = Priority;
|
||||
|
||||
CurrentCore = PreferredCore;
|
||||
}
|
||||
|
||||
public long Start()
|
||||
{
|
||||
long Result = MakeError(ErrorModule.Kernel, KernelErr.ThreadTerminating);
|
||||
|
||||
System.CriticalSectionLock.Lock();
|
||||
|
||||
if (!ShallBeTerminated)
|
||||
{
|
||||
KThread CurrentThread = System.Scheduler.GetCurrentThread();
|
||||
|
||||
while (SchedFlags != ThreadSchedState.TerminationPending &&
|
||||
CurrentThread.SchedFlags != ThreadSchedState.TerminationPending &&
|
||||
!CurrentThread.ShallBeTerminated)
|
||||
{
|
||||
if ((SchedFlags & ThreadSchedState.LowNibbleMask) != ThreadSchedState.None)
|
||||
{
|
||||
Result = MakeError(ErrorModule.Kernel, KernelErr.InvalidState);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (CurrentThread.ForcePauseFlags == ThreadSchedState.None)
|
||||
{
|
||||
if (Owner != null && ForcePauseFlags != ThreadSchedState.None)
|
||||
{
|
||||
CombineForcePauseFlags();
|
||||
}
|
||||
|
||||
SetNewSchedFlags(ThreadSchedState.Running);
|
||||
|
||||
Result = 0;
|
||||
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentThread.CombineForcePauseFlags();
|
||||
|
||||
System.CriticalSectionLock.Unlock();
|
||||
System.CriticalSectionLock.Lock();
|
||||
|
||||
if (CurrentThread.ShallBeTerminated)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
System.CriticalSectionLock.Unlock();
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
public void Exit()
|
||||
{
|
||||
System.CriticalSectionLock.Lock();
|
||||
|
||||
ForcePauseFlags &= ~ThreadSchedState.ExceptionalMask;
|
||||
|
||||
ExitImpl();
|
||||
|
||||
System.CriticalSectionLock.Unlock();
|
||||
}
|
||||
|
||||
private void ExitImpl()
|
||||
{
|
||||
System.CriticalSectionLock.Lock();
|
||||
|
||||
SetNewSchedFlags(ThreadSchedState.TerminationPending);
|
||||
|
||||
HasExited = true;
|
||||
|
||||
Signal();
|
||||
|
||||
System.CriticalSectionLock.Unlock();
|
||||
}
|
||||
|
||||
public long Sleep(long Timeout)
|
||||
{
|
||||
System.CriticalSectionLock.Lock();
|
||||
|
||||
if (ShallBeTerminated || SchedFlags == ThreadSchedState.TerminationPending)
|
||||
{
|
||||
System.CriticalSectionLock.Unlock();
|
||||
|
||||
return MakeError(ErrorModule.Kernel, KernelErr.ThreadTerminating);
|
||||
}
|
||||
|
||||
SetNewSchedFlags(ThreadSchedState.Paused);
|
||||
|
||||
if (Timeout > 0)
|
||||
{
|
||||
System.TimeManager.ScheduleFutureInvocation(this, Timeout);
|
||||
}
|
||||
|
||||
System.CriticalSectionLock.Unlock();
|
||||
|
||||
if (Timeout > 0)
|
||||
{
|
||||
System.TimeManager.UnscheduleFutureInvocation(this);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void Yield()
|
||||
{
|
||||
System.CriticalSectionLock.Lock();
|
||||
|
||||
if (SchedFlags != ThreadSchedState.Running)
|
||||
{
|
||||
System.CriticalSectionLock.Unlock();
|
||||
|
||||
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.CriticalSectionLock.Unlock();
|
||||
|
||||
System.Scheduler.ContextSwitch();
|
||||
}
|
||||
|
||||
public void YieldWithLoadBalancing()
|
||||
{
|
||||
int Prio = DynamicPriority;
|
||||
int Core = CurrentCore;
|
||||
|
||||
System.CriticalSectionLock.Lock();
|
||||
|
||||
if (SchedFlags != ThreadSchedState.Running)
|
||||
{
|
||||
System.CriticalSectionLock.Unlock();
|
||||
|
||||
System.Scheduler.ContextSwitch();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
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.LastScheduledTicks >= Thread.LastScheduledTicks ||
|
||||
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.CriticalSectionLock.Unlock();
|
||||
|
||||
System.Scheduler.ContextSwitch();
|
||||
}
|
||||
|
||||
public void YieldAndWaitForLoadBalancing()
|
||||
{
|
||||
System.CriticalSectionLock.Lock();
|
||||
|
||||
if (SchedFlags != ThreadSchedState.Running)
|
||||
{
|
||||
System.CriticalSectionLock.Unlock();
|
||||
|
||||
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.CriticalSectionLock.Unlock();
|
||||
|
||||
System.Scheduler.ContextSwitch();
|
||||
}
|
||||
|
||||
public void SetPriority(int Priority)
|
||||
{
|
||||
WantedPriority = Priority;
|
||||
System.CriticalSectionLock.Lock();
|
||||
|
||||
UpdatePriority();
|
||||
BasePriority = Priority;
|
||||
|
||||
UpdatePriorityInheritance();
|
||||
|
||||
System.CriticalSectionLock.Unlock();
|
||||
}
|
||||
|
||||
public void UpdatePriority()
|
||||
public long SetActivity(bool Pause)
|
||||
{
|
||||
bool PriorityChanged;
|
||||
long Result = 0;
|
||||
|
||||
lock (Process.ThreadSyncLock)
|
||||
System.CriticalSectionLock.Lock();
|
||||
|
||||
ThreadSchedState LowNibble = SchedFlags & ThreadSchedState.LowNibbleMask;
|
||||
|
||||
if (LowNibble != ThreadSchedState.Paused && LowNibble != ThreadSchedState.Running)
|
||||
{
|
||||
int OldPriority = ActualPriority;
|
||||
System.CriticalSectionLock.Unlock();
|
||||
|
||||
int CurrPriority = WantedPriority;
|
||||
return MakeError(ErrorModule.Kernel, KernelErr.InvalidState);
|
||||
}
|
||||
|
||||
foreach (KThread Thread in MutexWaiters)
|
||||
System.CriticalSectionLock.Lock();
|
||||
|
||||
if (!ShallBeTerminated && SchedFlags != ThreadSchedState.TerminationPending)
|
||||
{
|
||||
if (Pause)
|
||||
{
|
||||
int WantedPriority = Thread.WantedPriority;
|
||||
|
||||
if (CurrPriority > WantedPriority)
|
||||
//Pause, the force pause flag should be clear (thread is NOT paused).
|
||||
if ((ForcePauseFlags & ThreadSchedState.ForcePauseFlag) == 0)
|
||||
{
|
||||
CurrPriority = WantedPriority;
|
||||
ForcePauseFlags |= ThreadSchedState.ForcePauseFlag;
|
||||
|
||||
CombineForcePauseFlags();
|
||||
}
|
||||
else
|
||||
{
|
||||
Result = MakeError(ErrorModule.Kernel, KernelErr.InvalidState);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//Unpause, the force pause flag should be set (thread is paused).
|
||||
if ((ForcePauseFlags & ThreadSchedState.ForcePauseFlag) != 0)
|
||||
{
|
||||
ThreadSchedState OldForcePauseFlags = ForcePauseFlags;
|
||||
|
||||
PriorityChanged = CurrPriority != OldPriority;
|
||||
ForcePauseFlags &= ~ThreadSchedState.ForcePauseFlag;
|
||||
|
||||
ActualPriority = CurrPriority;
|
||||
if ((OldForcePauseFlags & ~ThreadSchedState.ForcePauseFlag) == ThreadSchedState.None)
|
||||
{
|
||||
ThreadSchedState OldSchedFlags = SchedFlags;
|
||||
|
||||
SchedFlags &= ThreadSchedState.LowNibbleMask;
|
||||
|
||||
AdjustScheduling(OldSchedFlags);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Result = MakeError(ErrorModule.Kernel, KernelErr.InvalidState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (PriorityChanged)
|
||||
System.CriticalSectionLock.Unlock();
|
||||
System.CriticalSectionLock.Unlock();
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
public void CancelSynchronization()
|
||||
{
|
||||
System.CriticalSectionLock.Lock();
|
||||
|
||||
if ((SchedFlags & ThreadSchedState.LowNibbleMask) != ThreadSchedState.Paused || !WaitingSync)
|
||||
{
|
||||
Process.Scheduler.Resort(this);
|
||||
|
||||
MutexOwner?.UpdatePriority();
|
||||
SyncCancelled = true;
|
||||
}
|
||||
else if (WithholderNode != null)
|
||||
{
|
||||
System.Withholders.Remove(WithholderNode);
|
||||
|
||||
SetNewSchedFlags(ThreadSchedState.Running);
|
||||
|
||||
WithholderNode = null;
|
||||
|
||||
SyncCancelled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
SignaledObj = null;
|
||||
ObjSyncResult = (int)MakeError(ErrorModule.Kernel, KernelErr.Cancelled);
|
||||
|
||||
SetNewSchedFlags(ThreadSchedState.Running);
|
||||
|
||||
SyncCancelled = false;
|
||||
}
|
||||
|
||||
System.CriticalSectionLock.Unlock();
|
||||
}
|
||||
|
||||
public long SetCoreAndAffinityMask(int NewCore, long NewAffinityMask)
|
||||
{
|
||||
System.CriticalSectionLock.Lock();
|
||||
|
||||
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.CriticalSectionLock.Unlock();
|
||||
|
||||
return MakeError(ErrorModule.Kernel, KernelErr.InvalidMaskValue);
|
||||
}
|
||||
}
|
||||
|
||||
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.CriticalSectionLock.Unlock();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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.LowNibbleMask;
|
||||
|
||||
SchedFlags = LowNibble | ForcePauseFlags;
|
||||
|
||||
AdjustScheduling(OldFlags);
|
||||
}
|
||||
|
||||
private void SetNewSchedFlags(ThreadSchedState NewFlags)
|
||||
{
|
||||
System.CriticalSectionLock.Lock();
|
||||
|
||||
ThreadSchedState OldFlags = SchedFlags;
|
||||
|
||||
SchedFlags = (OldFlags & ThreadSchedState.HighNibbleMask) | NewFlags;
|
||||
|
||||
if ((OldFlags & ThreadSchedState.LowNibbleMask) != NewFlags)
|
||||
{
|
||||
AdjustScheduling(OldFlags);
|
||||
}
|
||||
|
||||
System.CriticalSectionLock.Unlock();
|
||||
}
|
||||
|
||||
public void ReleaseAndResume()
|
||||
{
|
||||
System.CriticalSectionLock.Lock();
|
||||
|
||||
if ((SchedFlags & ThreadSchedState.LowNibbleMask) == ThreadSchedState.Paused)
|
||||
{
|
||||
if (WithholderNode != null)
|
||||
{
|
||||
System.Withholders.Remove(WithholderNode);
|
||||
|
||||
SetNewSchedFlags(ThreadSchedState.Running);
|
||||
|
||||
WithholderNode = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
SetNewSchedFlags(ThreadSchedState.Running);
|
||||
}
|
||||
}
|
||||
|
||||
System.CriticalSectionLock.Unlock();
|
||||
}
|
||||
|
||||
public void Reschedule(ThreadSchedState NewFlags)
|
||||
{
|
||||
System.CriticalSectionLock.Lock();
|
||||
|
||||
ThreadSchedState OldFlags = SchedFlags;
|
||||
|
||||
SchedFlags = (OldFlags & ThreadSchedState.HighNibbleMask) |
|
||||
(NewFlags & ThreadSchedState.LowNibbleMask);
|
||||
|
||||
AdjustScheduling(OldFlags);
|
||||
|
||||
System.CriticalSectionLock.Unlock();
|
||||
}
|
||||
|
||||
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(long 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 ClearExclusive()
|
||||
{
|
||||
Owner.Memory.ClearExclusive(CurrentCore);
|
||||
}
|
||||
|
||||
public void TimeUp()
|
||||
{
|
||||
System.CriticalSectionLock.Lock();
|
||||
|
||||
SetNewSchedFlags(ThreadSchedState.Running);
|
||||
|
||||
System.CriticalSectionLock.Unlock();
|
||||
}
|
||||
}
|
||||
}
|
134
Ryujinx.HLE/HOS/Kernel/KTimeManager.cs
Normal file
134
Ryujinx.HLE/HOS/Kernel/KTimeManager.cs
Normal file
|
@ -0,0 +1,134 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel
|
||||
{
|
||||
class KTimeManager : IDisposable
|
||||
{
|
||||
private class WaitingObject
|
||||
{
|
||||
public IKFutureSchedulerObject Object { get; private set; }
|
||||
|
||||
public long TimePoint { get; private set; }
|
||||
|
||||
public WaitingObject(IKFutureSchedulerObject Object, long TimePoint)
|
||||
{
|
||||
this.Object = Object;
|
||||
this.TimePoint = TimePoint;
|
||||
}
|
||||
}
|
||||
|
||||
private List<WaitingObject> WaitingObjects;
|
||||
|
||||
private AutoResetEvent WaitEvent;
|
||||
|
||||
private Stopwatch Counter;
|
||||
|
||||
private bool KeepRunning;
|
||||
|
||||
public KTimeManager()
|
||||
{
|
||||
WaitingObjects = new List<WaitingObject>();
|
||||
|
||||
Counter = new Stopwatch();
|
||||
|
||||
Counter.Start();
|
||||
|
||||
KeepRunning = true;
|
||||
|
||||
Thread Work = new Thread(WaitAndCheckScheduledObjects);
|
||||
|
||||
Work.Start();
|
||||
}
|
||||
|
||||
public void ScheduleFutureInvocation(IKFutureSchedulerObject Object, long Timeout)
|
||||
{
|
||||
lock (WaitingObjects)
|
||||
{
|
||||
long TimePoint = Counter.ElapsedMilliseconds + ConvertNanosecondsToMilliseconds(Timeout);
|
||||
|
||||
WaitingObjects.Add(new WaitingObject(Object, TimePoint));
|
||||
}
|
||||
|
||||
WaitEvent.Set();
|
||||
}
|
||||
|
||||
private long ConvertNanosecondsToMilliseconds(long Timeout)
|
||||
{
|
||||
Timeout /= 1000000;
|
||||
|
||||
if ((ulong)Timeout > int.MaxValue)
|
||||
{
|
||||
return int.MaxValue;
|
||||
}
|
||||
|
||||
return Timeout;
|
||||
}
|
||||
|
||||
public void UnscheduleFutureInvocation(IKFutureSchedulerObject Object)
|
||||
{
|
||||
lock (WaitingObjects)
|
||||
{
|
||||
WaitingObjects.RemoveAll(x => x.Object == Object);
|
||||
}
|
||||
}
|
||||
|
||||
private void WaitAndCheckScheduledObjects()
|
||||
{
|
||||
using (WaitEvent = new AutoResetEvent(false))
|
||||
{
|
||||
while (KeepRunning)
|
||||
{
|
||||
Monitor.Enter(WaitingObjects);
|
||||
|
||||
WaitingObject Next = WaitingObjects.OrderBy(x => x.TimePoint).FirstOrDefault();
|
||||
|
||||
Monitor.Exit(WaitingObjects);
|
||||
|
||||
if (Next != null)
|
||||
{
|
||||
long TimePoint = Counter.ElapsedMilliseconds;
|
||||
|
||||
if (Next.TimePoint > TimePoint)
|
||||
{
|
||||
WaitEvent.WaitOne((int)(Next.TimePoint - TimePoint));
|
||||
}
|
||||
|
||||
Monitor.Enter(WaitingObjects);
|
||||
|
||||
bool TimeUp = Counter.ElapsedMilliseconds >= Next.TimePoint && WaitingObjects.Remove(Next);
|
||||
|
||||
Monitor.Exit(WaitingObjects);
|
||||
|
||||
if (TimeUp)
|
||||
{
|
||||
Next.Object.TimeUp();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
WaitEvent.WaitOne();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool Disposing)
|
||||
{
|
||||
if (Disposing)
|
||||
{
|
||||
KeepRunning = false;
|
||||
|
||||
WaitEvent?.Set();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ namespace Ryujinx.HLE.HOS.Kernel
|
|||
{
|
||||
static class KernelErr
|
||||
{
|
||||
public const int ThreadTerminating = 59;
|
||||
public const int InvalidSize = 101;
|
||||
public const int InvalidAddress = 102;
|
||||
public const int OutOfMemory = 104;
|
||||
|
@ -13,7 +14,7 @@ namespace Ryujinx.HLE.HOS.Kernel
|
|||
public const int InvalidHandle = 114;
|
||||
public const int InvalidMaskValue = 116;
|
||||
public const int Timeout = 117;
|
||||
public const int Canceled = 118;
|
||||
public const int Cancelled = 118;
|
||||
public const int CountOutOfRange = 119;
|
||||
public const int InvalidEnumValue = 120;
|
||||
public const int InvalidThread = 122;
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
namespace Ryujinx.HLE.HOS.Kernel
|
||||
{
|
||||
static class NsTimeConverter
|
||||
{
|
||||
public static int GetTimeMs(ulong Ns)
|
||||
{
|
||||
ulong Ms = Ns / 1_000_000;
|
||||
|
||||
if (Ms < int.MaxValue)
|
||||
{
|
||||
return (int)Ms;
|
||||
}
|
||||
else
|
||||
{
|
||||
return int.MaxValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
9
Ryujinx.HLE/HOS/Kernel/SignalType.cs
Normal file
9
Ryujinx.HLE/HOS/Kernel/SignalType.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
namespace Ryujinx.HLE.HOS.Kernel
|
||||
{
|
||||
enum SignalType
|
||||
{
|
||||
Signal = 0,
|
||||
SignalAndIncrementIfEqual = 1,
|
||||
SignalAndModifyIfEqual = 2
|
||||
}
|
||||
}
|
|
@ -1,11 +1,10 @@
|
|||
using ChocolArm64.Events;
|
||||
using ChocolArm64.Memory;
|
||||
using ChocolArm64.State;
|
||||
using Ryujinx.HLE.HOS.Ipc;
|
||||
using Ryujinx.HLE.Logging;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel
|
||||
{
|
||||
|
@ -17,9 +16,28 @@ namespace Ryujinx.HLE.HOS.Kernel
|
|||
|
||||
private Switch Device;
|
||||
private Process Process;
|
||||
private Horizon System;
|
||||
private AMemory Memory;
|
||||
|
||||
private ConcurrentDictionary<KThread, AutoResetEvent> SyncWaits;
|
||||
private struct HleIpcMessage
|
||||
{
|
||||
public KThread Thread { get; private set; }
|
||||
public KSession Session { get; private set; }
|
||||
public IpcMessage Message { get; private set; }
|
||||
public long MessagePtr { get; private set; }
|
||||
|
||||
public HleIpcMessage(
|
||||
KThread Thread,
|
||||
KSession Session,
|
||||
IpcMessage Message,
|
||||
long MessagePtr)
|
||||
{
|
||||
this.Thread = Thread;
|
||||
this.Session = Session;
|
||||
this.Message = Message;
|
||||
this.MessagePtr = MessagePtr;
|
||||
}
|
||||
}
|
||||
|
||||
private const uint SelfThreadHandle = 0xffff8000;
|
||||
private const uint SelfProcessHandle = 0xffff8001;
|
||||
|
@ -69,14 +87,14 @@ namespace Ryujinx.HLE.HOS.Kernel
|
|||
{ 0x2d, SvcUnmapPhysicalMemory },
|
||||
{ 0x32, SvcSetThreadActivity },
|
||||
{ 0x33, SvcGetThreadContext3 },
|
||||
{ 0x34, SvcWaitForAddress }
|
||||
{ 0x34, SvcWaitForAddress },
|
||||
{ 0x35, SvcSignalToAddress }
|
||||
};
|
||||
|
||||
this.Device = Device;
|
||||
this.Process = Process;
|
||||
this.System = Process.Device.System;
|
||||
this.Memory = Process.Memory;
|
||||
|
||||
SyncWaits = new ConcurrentDictionary<KThread, AutoResetEvent>();
|
||||
}
|
||||
|
||||
static SvcHandler()
|
||||
|
@ -96,8 +114,6 @@ namespace Ryujinx.HLE.HOS.Kernel
|
|||
|
||||
Func(ThreadState);
|
||||
|
||||
Process.Scheduler.Reschedule(Process.GetThread(ThreadState.Tpidr));
|
||||
|
||||
Device.Log.PrintDebug(LogClass.KernelSvc, $"{Func.Method.Name} ended.");
|
||||
}
|
||||
else
|
||||
|
|
|
@ -68,7 +68,7 @@ namespace Ryujinx.HLE.HOS.Kernel
|
|||
|
||||
if (Event != null)
|
||||
{
|
||||
Event.WaitEvent.Reset();
|
||||
Event.Reset();
|
||||
|
||||
ThreadState.X0 = 0;
|
||||
}
|
||||
|
@ -80,115 +80,6 @@ namespace Ryujinx.HLE.HOS.Kernel
|
|||
}
|
||||
}
|
||||
|
||||
private void SvcWaitSynchronization(AThreadState ThreadState)
|
||||
{
|
||||
long HandlesPtr = (long)ThreadState.X1;
|
||||
int HandlesCount = (int)ThreadState.X2;
|
||||
ulong Timeout = ThreadState.X3;
|
||||
|
||||
Device.Log.PrintDebug(LogClass.KernelSvc,
|
||||
"HandlesPtr = 0x" + HandlesPtr .ToString("x16") + ", " +
|
||||
"HandlesCount = 0x" + HandlesCount.ToString("x8") + ", " +
|
||||
"Timeout = 0x" + Timeout .ToString("x16"));
|
||||
|
||||
if ((uint)HandlesCount > 0x40)
|
||||
{
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.CountOutOfRange);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
|
||||
|
||||
WaitHandle[] Handles = new WaitHandle[HandlesCount + 1];
|
||||
|
||||
for (int Index = 0; Index < HandlesCount; Index++)
|
||||
{
|
||||
int Handle = Memory.ReadInt32(HandlesPtr + Index * 4);
|
||||
|
||||
KSynchronizationObject SyncObj = Process.HandleTable.GetData<KSynchronizationObject>(Handle);
|
||||
|
||||
if (SyncObj == null)
|
||||
{
|
||||
Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid handle 0x{Handle:x8}!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Handles[Index] = SyncObj.WaitEvent;
|
||||
}
|
||||
|
||||
using (AutoResetEvent WaitEvent = new AutoResetEvent(false))
|
||||
{
|
||||
if (!SyncWaits.TryAdd(CurrThread, WaitEvent))
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
Handles[HandlesCount] = WaitEvent;
|
||||
|
||||
Process.Scheduler.Suspend(CurrThread);
|
||||
|
||||
int HandleIndex;
|
||||
|
||||
ulong Result = 0;
|
||||
|
||||
if (Timeout != ulong.MaxValue)
|
||||
{
|
||||
HandleIndex = WaitHandle.WaitAny(Handles, NsTimeConverter.GetTimeMs(Timeout));
|
||||
}
|
||||
else
|
||||
{
|
||||
HandleIndex = WaitHandle.WaitAny(Handles);
|
||||
}
|
||||
|
||||
if (HandleIndex == WaitHandle.WaitTimeout)
|
||||
{
|
||||
Result = MakeError(ErrorModule.Kernel, KernelErr.Timeout);
|
||||
}
|
||||
else if (HandleIndex == HandlesCount)
|
||||
{
|
||||
Result = MakeError(ErrorModule.Kernel, KernelErr.Canceled);
|
||||
}
|
||||
|
||||
SyncWaits.TryRemove(CurrThread, out _);
|
||||
|
||||
Process.Scheduler.Resume(CurrThread);
|
||||
|
||||
ThreadState.X0 = Result;
|
||||
|
||||
if (Result == 0)
|
||||
{
|
||||
ThreadState.X1 = (ulong)HandleIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SvcCancelSynchronization(AThreadState ThreadState)
|
||||
{
|
||||
int ThreadHandle = (int)ThreadState.X0;
|
||||
|
||||
KThread Thread = GetThread(ThreadState.Tpidr, ThreadHandle);
|
||||
|
||||
if (Thread == null)
|
||||
{
|
||||
Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{ThreadHandle:x8}!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (SyncWaits.TryRemove(Thread, out AutoResetEvent WaitEvent))
|
||||
{
|
||||
WaitEvent.Set();
|
||||
}
|
||||
|
||||
ThreadState.X0 = 0;
|
||||
}
|
||||
|
||||
private void SvcGetSystemTick(AThreadState ThreadState)
|
||||
{
|
||||
ThreadState.X0 = ThreadState.CntpctEl0;
|
||||
|
@ -203,7 +94,7 @@ namespace Ryujinx.HLE.HOS.Kernel
|
|||
|
||||
//TODO: Validate that app has perms to access the service, and that the service
|
||||
//actually exists, return error codes otherwise.
|
||||
KSession Session = new KSession(ServiceFactory.MakeService(Name), Name);
|
||||
KSession Session = new KSession(ServiceFactory.MakeService(System, Name), Name);
|
||||
|
||||
ulong Handle = (ulong)Process.HandleTable.OpenHandle(Session);
|
||||
|
||||
|
@ -225,27 +116,38 @@ namespace Ryujinx.HLE.HOS.Kernel
|
|||
(int)ThreadState.X2);
|
||||
}
|
||||
|
||||
private void SendSyncRequest(AThreadState ThreadState, long CmdPtr, long Size, int Handle)
|
||||
private void SendSyncRequest(AThreadState ThreadState, long MessagePtr, long Size, int Handle)
|
||||
{
|
||||
KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
|
||||
|
||||
byte[] CmdData = Memory.ReadBytes(CmdPtr, Size);
|
||||
byte[] MessageData = Memory.ReadBytes(MessagePtr, Size);
|
||||
|
||||
KSession Session = Process.HandleTable.GetData<KSession>(Handle);
|
||||
|
||||
if (Session != null)
|
||||
{
|
||||
Process.Scheduler.Suspend(CurrThread);
|
||||
//Process.Scheduler.Suspend(CurrThread);
|
||||
|
||||
IpcMessage Cmd = new IpcMessage(CmdData, CmdPtr);
|
||||
System.CriticalSectionLock.Lock();
|
||||
|
||||
long Result = IpcHandler.IpcCall(Device, Process, Memory, Session, Cmd, CmdPtr);
|
||||
KThread CurrentThread = System.Scheduler.GetCurrentThread();
|
||||
|
||||
Thread.Yield();
|
||||
CurrentThread.SignaledObj = null;
|
||||
CurrentThread.ObjSyncResult = 0;
|
||||
|
||||
Process.Scheduler.Resume(CurrThread);
|
||||
CurrentThread.Reschedule(ThreadSchedState.Paused);
|
||||
|
||||
ThreadState.X0 = (ulong)Result;
|
||||
IpcMessage Message = new IpcMessage(MessageData, MessagePtr);
|
||||
|
||||
ThreadPool.QueueUserWorkItem(ProcessIpcRequest, new HleIpcMessage(
|
||||
CurrentThread,
|
||||
Session,
|
||||
Message,
|
||||
MessagePtr));
|
||||
|
||||
System.CriticalSectionLock.Unlock();
|
||||
|
||||
ThreadState.X0 = (ulong)CurrentThread.ObjSyncResult;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -255,6 +157,21 @@ namespace Ryujinx.HLE.HOS.Kernel
|
|||
}
|
||||
}
|
||||
|
||||
private void ProcessIpcRequest(object State)
|
||||
{
|
||||
HleIpcMessage IpcMessage = (HleIpcMessage)State;
|
||||
|
||||
IpcMessage.Thread.ObjSyncResult = (int)IpcHandler.IpcCall(
|
||||
Device,
|
||||
Process,
|
||||
Memory,
|
||||
IpcMessage.Session,
|
||||
IpcMessage.Message,
|
||||
IpcMessage.MessagePtr);
|
||||
|
||||
IpcMessage.Thread.Reschedule(ThreadSchedState.Running);
|
||||
}
|
||||
|
||||
private void SvcBreak(AThreadState ThreadState)
|
||||
{
|
||||
long Reason = (long)ThreadState.X0;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
using ChocolArm64.State;
|
||||
using Ryujinx.HLE.Logging;
|
||||
using System.Threading;
|
||||
|
||||
using static Ryujinx.HLE.HOS.ErrorCode;
|
||||
|
||||
|
@ -54,14 +53,18 @@ namespace Ryujinx.HLE.HOS.Kernel
|
|||
{
|
||||
int Handle = (int)ThreadState.X0;
|
||||
|
||||
KThread NewThread = Process.HandleTable.GetData<KThread>(Handle);
|
||||
KThread Thread = Process.HandleTable.GetData<KThread>(Handle);
|
||||
|
||||
if (NewThread != null)
|
||||
if (Thread != null)
|
||||
{
|
||||
Process.Scheduler.StartThread(NewThread);
|
||||
Process.Scheduler.SetReschedule(NewThread.ProcessorId);
|
||||
long Result = Thread.Start();
|
||||
|
||||
ThreadState.X0 = 0;
|
||||
if (Result != 0)
|
||||
{
|
||||
Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!");
|
||||
}
|
||||
|
||||
ThreadState.X0 = (ulong)Result;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -73,30 +76,37 @@ namespace Ryujinx.HLE.HOS.Kernel
|
|||
|
||||
private void SvcExitThread(AThreadState ThreadState)
|
||||
{
|
||||
KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
|
||||
KThread CurrentThread = System.Scheduler.GetCurrentThread();
|
||||
|
||||
CurrThread.Thread.StopExecution();
|
||||
CurrentThread.Exit();
|
||||
|
||||
System.Scheduler.StopThread(CurrentThread);
|
||||
|
||||
System.Scheduler.CoreContexts[CurrentThread.CurrentCore].RemoveThread(CurrentThread);
|
||||
}
|
||||
|
||||
private void SvcSleepThread(AThreadState ThreadState)
|
||||
{
|
||||
ulong TimeoutNs = ThreadState.X0;
|
||||
long Timeout = (long)ThreadState.X0;
|
||||
|
||||
Device.Log.PrintDebug(LogClass.KernelSvc, "Timeout = 0x" + TimeoutNs.ToString("x16"));
|
||||
Device.Log.PrintDebug(LogClass.KernelSvc, "Timeout = 0x" + Timeout.ToString("x16"));
|
||||
|
||||
KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
|
||||
KThread CurrentThread = System.Scheduler.GetCurrentThread();
|
||||
|
||||
if (TimeoutNs == 0 || TimeoutNs == ulong.MaxValue)
|
||||
if (Timeout < 1)
|
||||
{
|
||||
Process.Scheduler.Yield(CurrThread);
|
||||
switch (Timeout)
|
||||
{
|
||||
case 0: CurrentThread.Yield(); break;
|
||||
case -1: CurrentThread.YieldWithLoadBalancing(); break;
|
||||
case -2: CurrentThread.YieldAndWaitForLoadBalancing(); break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Process.Scheduler.Suspend(CurrThread);
|
||||
CurrentThread.Sleep(Timeout);
|
||||
|
||||
Thread.Sleep(NsTimeConverter.GetTimeMs(TimeoutNs));
|
||||
|
||||
Process.Scheduler.Resume(CurrThread);
|
||||
ThreadState.X0 = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -109,7 +119,7 @@ namespace Ryujinx.HLE.HOS.Kernel
|
|||
if (Thread != null)
|
||||
{
|
||||
ThreadState.X0 = 0;
|
||||
ThreadState.X1 = (ulong)Thread.ActualPriority;
|
||||
ThreadState.X1 = (ulong)Thread.DynamicPriority;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -128,20 +138,22 @@ namespace Ryujinx.HLE.HOS.Kernel
|
|||
"Handle = 0x" + Handle .ToString("x8") + ", " +
|
||||
"Priority = 0x" + Priority.ToString("x8"));
|
||||
|
||||
//TODO: NPDM check.
|
||||
|
||||
KThread Thread = GetThread(ThreadState.Tpidr, Handle);
|
||||
|
||||
if (Thread != null)
|
||||
{
|
||||
Thread.SetPriority(Priority);
|
||||
|
||||
ThreadState.X0 = 0;
|
||||
}
|
||||
else
|
||||
if (Thread == null)
|
||||
{
|
||||
Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Thread.SetPriority(Priority);
|
||||
|
||||
ThreadState.X0 = 0;
|
||||
}
|
||||
|
||||
private void SvcGetThreadCoreMask(AThreadState ThreadState)
|
||||
|
@ -155,8 +167,8 @@ namespace Ryujinx.HLE.HOS.Kernel
|
|||
if (Thread != null)
|
||||
{
|
||||
ThreadState.X0 = 0;
|
||||
ThreadState.X1 = (ulong)Thread.IdealCore;
|
||||
ThreadState.X2 = (ulong)Thread.CoreMask;
|
||||
ThreadState.X1 = (ulong)Thread.PreferredCore;
|
||||
ThreadState.X2 = (ulong)Thread.AffinityMask;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -168,40 +180,40 @@ namespace Ryujinx.HLE.HOS.Kernel
|
|||
|
||||
private void SvcSetThreadCoreMask(AThreadState ThreadState)
|
||||
{
|
||||
int Handle = (int)ThreadState.X0;
|
||||
int IdealCore = (int)ThreadState.X1;
|
||||
long CoreMask = (long)ThreadState.X2;
|
||||
int ThreadHandle = (int)ThreadState.X0;
|
||||
int PrefferedCore = (int)ThreadState.X1;
|
||||
long AffinityMask = (long)ThreadState.X2;
|
||||
|
||||
Device.Log.PrintDebug(LogClass.KernelSvc,
|
||||
"Handle = 0x" + Handle .ToString("x8") + ", " +
|
||||
"IdealCore = 0x" + IdealCore.ToString("x8") + ", " +
|
||||
"CoreMask = 0x" + CoreMask .ToString("x16"));
|
||||
"ThreadHandle = 0x" + ThreadHandle .ToString("x8") + ", " +
|
||||
"PrefferedCore = 0x" + PrefferedCore.ToString("x8") + ", " +
|
||||
"AffinityMask = 0x" + AffinityMask .ToString("x16"));
|
||||
|
||||
KThread Thread = GetThread(ThreadState.Tpidr, Handle);
|
||||
|
||||
if (IdealCore == -2)
|
||||
if (PrefferedCore == -2)
|
||||
{
|
||||
//TODO: Get this value from the NPDM file.
|
||||
IdealCore = 0;
|
||||
PrefferedCore = 0;
|
||||
|
||||
CoreMask = 1 << IdealCore;
|
||||
AffinityMask = 1 << PrefferedCore;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((uint)IdealCore > 3)
|
||||
//TODO: Check allowed cores from NPDM file.
|
||||
|
||||
if ((uint)PrefferedCore > 3)
|
||||
{
|
||||
if ((IdealCore | 2) != -1)
|
||||
if ((PrefferedCore | 2) != -1)
|
||||
{
|
||||
Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid core id 0x{IdealCore:x8}!");
|
||||
Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid core id 0x{PrefferedCore:x8}!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidCoreId);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if ((CoreMask & (1 << IdealCore)) == 0)
|
||||
else if ((AffinityMask & (1 << PrefferedCore)) == 0)
|
||||
{
|
||||
Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid core mask 0x{CoreMask:x8}!");
|
||||
Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid core mask 0x{AffinityMask:x8}!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMaskValue);
|
||||
|
||||
|
@ -209,35 +221,30 @@ namespace Ryujinx.HLE.HOS.Kernel
|
|||
}
|
||||
}
|
||||
|
||||
KThread Thread = GetThread(ThreadState.Tpidr, ThreadHandle);
|
||||
|
||||
if (Thread == null)
|
||||
{
|
||||
Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!");
|
||||
Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{ThreadHandle:x8}!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//-1 is used as "don't care", so the IdealCore value is ignored.
|
||||
//-2 is used as "use NPDM default core id" (handled above).
|
||||
//-3 is used as "don't update", the old IdealCore value is kept.
|
||||
if (IdealCore == -3 && (CoreMask & (1 << Thread.IdealCore)) == 0)
|
||||
long Result = Thread.SetCoreAndAffinityMask(PrefferedCore, AffinityMask);
|
||||
|
||||
if (Result != 0)
|
||||
{
|
||||
Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid core mask 0x{CoreMask:x8}!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMaskValue);
|
||||
|
||||
return;
|
||||
Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!");
|
||||
}
|
||||
|
||||
Process.Scheduler.ChangeCore(Thread, IdealCore, (int)CoreMask);
|
||||
|
||||
ThreadState.X0 = 0;
|
||||
ThreadState.X0 = (ulong)Result;
|
||||
}
|
||||
|
||||
private void SvcGetCurrentProcessorNumber(AThreadState ThreadState)
|
||||
{
|
||||
ThreadState.X0 = (ulong)Process.GetThread(ThreadState.Tpidr).ActualCore;
|
||||
ThreadState.X0 = (ulong)Process.GetThread(ThreadState.Tpidr).CurrentCore;
|
||||
}
|
||||
|
||||
private void SvcGetThreadId(AThreadState ThreadState)
|
||||
|
@ -262,22 +269,36 @@ namespace Ryujinx.HLE.HOS.Kernel
|
|||
private void SvcSetThreadActivity(AThreadState ThreadState)
|
||||
{
|
||||
int Handle = (int)ThreadState.X0;
|
||||
bool Active = (int)ThreadState.X1 == 0;
|
||||
bool Pause = (int)ThreadState.X1 == 1;
|
||||
|
||||
KThread Thread = Process.HandleTable.GetData<KThread>(Handle);
|
||||
|
||||
if (Thread != null)
|
||||
{
|
||||
Process.Scheduler.SetThreadActivity(Thread, Active);
|
||||
|
||||
ThreadState.X0 = 0;
|
||||
}
|
||||
else
|
||||
if (Thread == null)
|
||||
{
|
||||
Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (Thread.Owner != Process)
|
||||
{
|
||||
Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread owner process!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
long Result = Thread.SetActivity(Pause);
|
||||
|
||||
if (Result != 0)
|
||||
{
|
||||
Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!");
|
||||
}
|
||||
|
||||
ThreadState.X0 = (ulong)Result;
|
||||
}
|
||||
|
||||
private void SvcGetThreadContext3(AThreadState ThreadState)
|
||||
|
@ -305,79 +326,79 @@ namespace Ryujinx.HLE.HOS.Kernel
|
|||
return;
|
||||
}
|
||||
|
||||
Memory.WriteUInt64(Position + 0x0, ThreadState.X0);
|
||||
Memory.WriteUInt64(Position + 0x8, ThreadState.X1);
|
||||
Memory.WriteUInt64(Position + 0x10, ThreadState.X2);
|
||||
Memory.WriteUInt64(Position + 0x18, ThreadState.X3);
|
||||
Memory.WriteUInt64(Position + 0x20, ThreadState.X4);
|
||||
Memory.WriteUInt64(Position + 0x28, ThreadState.X5);
|
||||
Memory.WriteUInt64(Position + 0x30, ThreadState.X6);
|
||||
Memory.WriteUInt64(Position + 0x38, ThreadState.X7);
|
||||
Memory.WriteUInt64(Position + 0x40, ThreadState.X8);
|
||||
Memory.WriteUInt64(Position + 0x48, ThreadState.X9);
|
||||
Memory.WriteUInt64(Position + 0x50, ThreadState.X10);
|
||||
Memory.WriteUInt64(Position + 0x58, ThreadState.X11);
|
||||
Memory.WriteUInt64(Position + 0x60, ThreadState.X12);
|
||||
Memory.WriteUInt64(Position + 0x68, ThreadState.X13);
|
||||
Memory.WriteUInt64(Position + 0x70, ThreadState.X14);
|
||||
Memory.WriteUInt64(Position + 0x78, ThreadState.X15);
|
||||
Memory.WriteUInt64(Position + 0x80, ThreadState.X16);
|
||||
Memory.WriteUInt64(Position + 0x88, ThreadState.X17);
|
||||
Memory.WriteUInt64(Position + 0x90, ThreadState.X18);
|
||||
Memory.WriteUInt64(Position + 0x98, ThreadState.X19);
|
||||
Memory.WriteUInt64(Position + 0xa0, ThreadState.X20);
|
||||
Memory.WriteUInt64(Position + 0xa8, ThreadState.X21);
|
||||
Memory.WriteUInt64(Position + 0xb0, ThreadState.X22);
|
||||
Memory.WriteUInt64(Position + 0xb8, ThreadState.X23);
|
||||
Memory.WriteUInt64(Position + 0xc0, ThreadState.X24);
|
||||
Memory.WriteUInt64(Position + 0xc8, ThreadState.X25);
|
||||
Memory.WriteUInt64(Position + 0xd0, ThreadState.X26);
|
||||
Memory.WriteUInt64(Position + 0xd8, ThreadState.X27);
|
||||
Memory.WriteUInt64(Position + 0xe0, ThreadState.X28);
|
||||
Memory.WriteUInt64(Position + 0xe8, ThreadState.X29);
|
||||
Memory.WriteUInt64(Position + 0xf0, ThreadState.X30);
|
||||
Memory.WriteUInt64(Position + 0xf8, ThreadState.X31);
|
||||
Memory.WriteUInt64(Position + 0x0, Thread.Context.ThreadState.X0);
|
||||
Memory.WriteUInt64(Position + 0x8, Thread.Context.ThreadState.X1);
|
||||
Memory.WriteUInt64(Position + 0x10, Thread.Context.ThreadState.X2);
|
||||
Memory.WriteUInt64(Position + 0x18, Thread.Context.ThreadState.X3);
|
||||
Memory.WriteUInt64(Position + 0x20, Thread.Context.ThreadState.X4);
|
||||
Memory.WriteUInt64(Position + 0x28, Thread.Context.ThreadState.X5);
|
||||
Memory.WriteUInt64(Position + 0x30, Thread.Context.ThreadState.X6);
|
||||
Memory.WriteUInt64(Position + 0x38, Thread.Context.ThreadState.X7);
|
||||
Memory.WriteUInt64(Position + 0x40, Thread.Context.ThreadState.X8);
|
||||
Memory.WriteUInt64(Position + 0x48, Thread.Context.ThreadState.X9);
|
||||
Memory.WriteUInt64(Position + 0x50, Thread.Context.ThreadState.X10);
|
||||
Memory.WriteUInt64(Position + 0x58, Thread.Context.ThreadState.X11);
|
||||
Memory.WriteUInt64(Position + 0x60, Thread.Context.ThreadState.X12);
|
||||
Memory.WriteUInt64(Position + 0x68, Thread.Context.ThreadState.X13);
|
||||
Memory.WriteUInt64(Position + 0x70, Thread.Context.ThreadState.X14);
|
||||
Memory.WriteUInt64(Position + 0x78, Thread.Context.ThreadState.X15);
|
||||
Memory.WriteUInt64(Position + 0x80, Thread.Context.ThreadState.X16);
|
||||
Memory.WriteUInt64(Position + 0x88, Thread.Context.ThreadState.X17);
|
||||
Memory.WriteUInt64(Position + 0x90, Thread.Context.ThreadState.X18);
|
||||
Memory.WriteUInt64(Position + 0x98, Thread.Context.ThreadState.X19);
|
||||
Memory.WriteUInt64(Position + 0xa0, Thread.Context.ThreadState.X20);
|
||||
Memory.WriteUInt64(Position + 0xa8, Thread.Context.ThreadState.X21);
|
||||
Memory.WriteUInt64(Position + 0xb0, Thread.Context.ThreadState.X22);
|
||||
Memory.WriteUInt64(Position + 0xb8, Thread.Context.ThreadState.X23);
|
||||
Memory.WriteUInt64(Position + 0xc0, Thread.Context.ThreadState.X24);
|
||||
Memory.WriteUInt64(Position + 0xc8, Thread.Context.ThreadState.X25);
|
||||
Memory.WriteUInt64(Position + 0xd0, Thread.Context.ThreadState.X26);
|
||||
Memory.WriteUInt64(Position + 0xd8, Thread.Context.ThreadState.X27);
|
||||
Memory.WriteUInt64(Position + 0xe0, Thread.Context.ThreadState.X28);
|
||||
Memory.WriteUInt64(Position + 0xe8, Thread.Context.ThreadState.X29);
|
||||
Memory.WriteUInt64(Position + 0xf0, Thread.Context.ThreadState.X30);
|
||||
Memory.WriteUInt64(Position + 0xf8, Thread.Context.ThreadState.X31);
|
||||
|
||||
Memory.WriteInt64(Position + 0x100, Thread.LastPc);
|
||||
|
||||
Memory.WriteUInt64(Position + 0x108, (ulong)ThreadState.Psr);
|
||||
Memory.WriteUInt64(Position + 0x108, (ulong)Thread.Context.ThreadState.Psr);
|
||||
|
||||
Memory.WriteVector128(Position + 0x110, ThreadState.V0);
|
||||
Memory.WriteVector128(Position + 0x120, ThreadState.V1);
|
||||
Memory.WriteVector128(Position + 0x130, ThreadState.V2);
|
||||
Memory.WriteVector128(Position + 0x140, ThreadState.V3);
|
||||
Memory.WriteVector128(Position + 0x150, ThreadState.V4);
|
||||
Memory.WriteVector128(Position + 0x160, ThreadState.V5);
|
||||
Memory.WriteVector128(Position + 0x170, ThreadState.V6);
|
||||
Memory.WriteVector128(Position + 0x180, ThreadState.V7);
|
||||
Memory.WriteVector128(Position + 0x190, ThreadState.V8);
|
||||
Memory.WriteVector128(Position + 0x1a0, ThreadState.V9);
|
||||
Memory.WriteVector128(Position + 0x1b0, ThreadState.V10);
|
||||
Memory.WriteVector128(Position + 0x1c0, ThreadState.V11);
|
||||
Memory.WriteVector128(Position + 0x1d0, ThreadState.V12);
|
||||
Memory.WriteVector128(Position + 0x1e0, ThreadState.V13);
|
||||
Memory.WriteVector128(Position + 0x1f0, ThreadState.V14);
|
||||
Memory.WriteVector128(Position + 0x200, ThreadState.V15);
|
||||
Memory.WriteVector128(Position + 0x210, ThreadState.V16);
|
||||
Memory.WriteVector128(Position + 0x220, ThreadState.V17);
|
||||
Memory.WriteVector128(Position + 0x230, ThreadState.V18);
|
||||
Memory.WriteVector128(Position + 0x240, ThreadState.V19);
|
||||
Memory.WriteVector128(Position + 0x250, ThreadState.V20);
|
||||
Memory.WriteVector128(Position + 0x260, ThreadState.V21);
|
||||
Memory.WriteVector128(Position + 0x270, ThreadState.V22);
|
||||
Memory.WriteVector128(Position + 0x280, ThreadState.V23);
|
||||
Memory.WriteVector128(Position + 0x290, ThreadState.V24);
|
||||
Memory.WriteVector128(Position + 0x2a0, ThreadState.V25);
|
||||
Memory.WriteVector128(Position + 0x2b0, ThreadState.V26);
|
||||
Memory.WriteVector128(Position + 0x2c0, ThreadState.V27);
|
||||
Memory.WriteVector128(Position + 0x2d0, ThreadState.V28);
|
||||
Memory.WriteVector128(Position + 0x2e0, ThreadState.V29);
|
||||
Memory.WriteVector128(Position + 0x2f0, ThreadState.V30);
|
||||
Memory.WriteVector128(Position + 0x300, ThreadState.V31);
|
||||
Memory.WriteVector128(Position + 0x110, Thread.Context.ThreadState.V0);
|
||||
Memory.WriteVector128(Position + 0x120, Thread.Context.ThreadState.V1);
|
||||
Memory.WriteVector128(Position + 0x130, Thread.Context.ThreadState.V2);
|
||||
Memory.WriteVector128(Position + 0x140, Thread.Context.ThreadState.V3);
|
||||
Memory.WriteVector128(Position + 0x150, Thread.Context.ThreadState.V4);
|
||||
Memory.WriteVector128(Position + 0x160, Thread.Context.ThreadState.V5);
|
||||
Memory.WriteVector128(Position + 0x170, Thread.Context.ThreadState.V6);
|
||||
Memory.WriteVector128(Position + 0x180, Thread.Context.ThreadState.V7);
|
||||
Memory.WriteVector128(Position + 0x190, Thread.Context.ThreadState.V8);
|
||||
Memory.WriteVector128(Position + 0x1a0, Thread.Context.ThreadState.V9);
|
||||
Memory.WriteVector128(Position + 0x1b0, Thread.Context.ThreadState.V10);
|
||||
Memory.WriteVector128(Position + 0x1c0, Thread.Context.ThreadState.V11);
|
||||
Memory.WriteVector128(Position + 0x1d0, Thread.Context.ThreadState.V12);
|
||||
Memory.WriteVector128(Position + 0x1e0, Thread.Context.ThreadState.V13);
|
||||
Memory.WriteVector128(Position + 0x1f0, Thread.Context.ThreadState.V14);
|
||||
Memory.WriteVector128(Position + 0x200, Thread.Context.ThreadState.V15);
|
||||
Memory.WriteVector128(Position + 0x210, Thread.Context.ThreadState.V16);
|
||||
Memory.WriteVector128(Position + 0x220, Thread.Context.ThreadState.V17);
|
||||
Memory.WriteVector128(Position + 0x230, Thread.Context.ThreadState.V18);
|
||||
Memory.WriteVector128(Position + 0x240, Thread.Context.ThreadState.V19);
|
||||
Memory.WriteVector128(Position + 0x250, Thread.Context.ThreadState.V20);
|
||||
Memory.WriteVector128(Position + 0x260, Thread.Context.ThreadState.V21);
|
||||
Memory.WriteVector128(Position + 0x270, Thread.Context.ThreadState.V22);
|
||||
Memory.WriteVector128(Position + 0x280, Thread.Context.ThreadState.V23);
|
||||
Memory.WriteVector128(Position + 0x290, Thread.Context.ThreadState.V24);
|
||||
Memory.WriteVector128(Position + 0x2a0, Thread.Context.ThreadState.V25);
|
||||
Memory.WriteVector128(Position + 0x2b0, Thread.Context.ThreadState.V26);
|
||||
Memory.WriteVector128(Position + 0x2c0, Thread.Context.ThreadState.V27);
|
||||
Memory.WriteVector128(Position + 0x2d0, Thread.Context.ThreadState.V28);
|
||||
Memory.WriteVector128(Position + 0x2e0, Thread.Context.ThreadState.V29);
|
||||
Memory.WriteVector128(Position + 0x2f0, Thread.Context.ThreadState.V30);
|
||||
Memory.WriteVector128(Position + 0x300, Thread.Context.ThreadState.V31);
|
||||
|
||||
Memory.WriteInt32(Position + 0x310, ThreadState.Fpcr);
|
||||
Memory.WriteInt32(Position + 0x314, ThreadState.Fpsr);
|
||||
Memory.WriteInt64(Position + 0x318, ThreadState.Tpidr);
|
||||
Memory.WriteInt32(Position + 0x310, Thread.Context.ThreadState.Fpcr);
|
||||
Memory.WriteInt32(Position + 0x314, Thread.Context.ThreadState.Fpsr);
|
||||
Memory.WriteInt64(Position + 0x318, Thread.Context.ThreadState.Tpidr);
|
||||
|
||||
ThreadState.X0 = 0;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
using ChocolArm64.State;
|
||||
using Ryujinx.HLE.Logging;
|
||||
using System;
|
||||
|
||||
using static Ryujinx.HLE.HOS.ErrorCode;
|
||||
|
||||
|
@ -8,18 +7,90 @@ namespace Ryujinx.HLE.HOS.Kernel
|
|||
{
|
||||
partial class SvcHandler
|
||||
{
|
||||
private const int MutexHasListenersMask = 0x40000000;
|
||||
private void SvcWaitSynchronization(AThreadState ThreadState)
|
||||
{
|
||||
long HandlesPtr = (long)ThreadState.X1;
|
||||
int HandlesCount = (int)ThreadState.X2;
|
||||
long Timeout = (long)ThreadState.X3;
|
||||
|
||||
Device.Log.PrintDebug(LogClass.KernelSvc,
|
||||
"HandlesPtr = 0x" + HandlesPtr .ToString("x16") + ", " +
|
||||
"HandlesCount = 0x" + HandlesCount.ToString("x8") + ", " +
|
||||
"Timeout = 0x" + Timeout .ToString("x16"));
|
||||
|
||||
if ((uint)HandlesCount > 0x40)
|
||||
{
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.CountOutOfRange);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
KSynchronizationObject[] SyncObjs = new KSynchronizationObject[HandlesCount];
|
||||
|
||||
for (int Index = 0; Index < HandlesCount; Index++)
|
||||
{
|
||||
int Handle = Memory.ReadInt32(HandlesPtr + Index * 4);
|
||||
|
||||
KSynchronizationObject SyncObj = Process.HandleTable.GetData<KSynchronizationObject>(Handle);
|
||||
|
||||
SyncObjs[Index] = SyncObj;
|
||||
}
|
||||
|
||||
int HndIndex = (int)ThreadState.X1;
|
||||
|
||||
ulong High = ThreadState.X1 & (0xffffffffUL << 32);
|
||||
|
||||
long Result = System.Synchronization.WaitFor(SyncObjs, Timeout, ref HndIndex);
|
||||
|
||||
if (Result != 0)
|
||||
{
|
||||
if (Result == MakeError(ErrorModule.Kernel, KernelErr.Timeout) ||
|
||||
Result == MakeError(ErrorModule.Kernel, KernelErr.Cancelled))
|
||||
{
|
||||
Device.Log.PrintDebug(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!");
|
||||
}
|
||||
else
|
||||
{
|
||||
Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!");
|
||||
}
|
||||
}
|
||||
|
||||
ThreadState.X0 = (ulong)Result;
|
||||
ThreadState.X1 = (uint)HndIndex | High;
|
||||
}
|
||||
|
||||
private void SvcCancelSynchronization(AThreadState ThreadState)
|
||||
{
|
||||
int ThreadHandle = (int)ThreadState.X0;
|
||||
|
||||
Device.Log.PrintDebug(LogClass.KernelSvc, "ThreadHandle = 0x" + ThreadHandle.ToString("x8"));
|
||||
|
||||
KThread Thread = Process.HandleTable.GetData<KThread>(ThreadHandle);
|
||||
|
||||
if (Thread == null)
|
||||
{
|
||||
Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{ThreadHandle:x8}!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Thread.CancelSynchronization();
|
||||
|
||||
ThreadState.X0 = 0;
|
||||
}
|
||||
|
||||
private void SvcArbitrateLock(AThreadState ThreadState)
|
||||
{
|
||||
int OwnerThreadHandle = (int)ThreadState.X0;
|
||||
long MutexAddress = (long)ThreadState.X1;
|
||||
int WaitThreadHandle = (int)ThreadState.X2;
|
||||
int OwnerHandle = (int)ThreadState.X0;
|
||||
long MutexAddress = (long)ThreadState.X1;
|
||||
int RequesterHandle = (int)ThreadState.X2;
|
||||
|
||||
Device.Log.PrintDebug(LogClass.KernelSvc,
|
||||
"OwnerThreadHandle = 0x" + OwnerThreadHandle.ToString("x8") + ", " +
|
||||
"MutexAddress = 0x" + MutexAddress .ToString("x16") + ", " +
|
||||
"WaitThreadHandle = 0x" + WaitThreadHandle .ToString("x8"));
|
||||
"OwnerHandle = 0x" + OwnerHandle .ToString("x8") + ", " +
|
||||
"MutexAddress = 0x" + MutexAddress .ToString("x16") + ", " +
|
||||
"RequesterHandle = 0x" + RequesterHandle.ToString("x8"));
|
||||
|
||||
if (IsPointingInsideKernel(MutexAddress))
|
||||
{
|
||||
|
@ -39,33 +110,19 @@ namespace Ryujinx.HLE.HOS.Kernel
|
|||
return;
|
||||
}
|
||||
|
||||
KThread OwnerThread = Process.HandleTable.GetData<KThread>(OwnerThreadHandle);
|
||||
long Result = System.AddressArbiter.ArbitrateLock(
|
||||
Process,
|
||||
Memory,
|
||||
OwnerHandle,
|
||||
MutexAddress,
|
||||
RequesterHandle);
|
||||
|
||||
if (OwnerThread == null)
|
||||
if (Result != 0)
|
||||
{
|
||||
Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid owner thread handle 0x{OwnerThreadHandle:x8}!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
|
||||
|
||||
return;
|
||||
Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!");
|
||||
}
|
||||
|
||||
KThread WaitThread = Process.HandleTable.GetData<KThread>(WaitThreadHandle);
|
||||
|
||||
if (WaitThread == null)
|
||||
{
|
||||
Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid requesting thread handle 0x{WaitThreadHandle:x8}!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
|
||||
|
||||
MutexLock(CurrThread, WaitThread, OwnerThreadHandle, WaitThreadHandle, MutexAddress);
|
||||
|
||||
ThreadState.X0 = 0;
|
||||
ThreadState.X0 = (ulong)Result;
|
||||
}
|
||||
|
||||
private void SvcArbitrateUnlock(AThreadState ThreadState)
|
||||
|
@ -92,9 +149,14 @@ namespace Ryujinx.HLE.HOS.Kernel
|
|||
return;
|
||||
}
|
||||
|
||||
MutexUnlock(Process.GetThread(ThreadState.Tpidr), MutexAddress);
|
||||
long Result = System.AddressArbiter.ArbitrateUnlock(Memory, MutexAddress);
|
||||
|
||||
ThreadState.X0 = 0;
|
||||
if (Result != 0)
|
||||
{
|
||||
Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!");
|
||||
}
|
||||
|
||||
ThreadState.X0 = (ulong)Result;
|
||||
}
|
||||
|
||||
private void SvcWaitProcessWideKeyAtomic(AThreadState ThreadState)
|
||||
|
@ -102,7 +164,7 @@ namespace Ryujinx.HLE.HOS.Kernel
|
|||
long MutexAddress = (long)ThreadState.X0;
|
||||
long CondVarAddress = (long)ThreadState.X1;
|
||||
int ThreadHandle = (int)ThreadState.X2;
|
||||
ulong Timeout = ThreadState.X3;
|
||||
long Timeout = (long)ThreadState.X3;
|
||||
|
||||
Device.Log.PrintDebug(LogClass.KernelSvc,
|
||||
"MutexAddress = 0x" + MutexAddress .ToString("x16") + ", " +
|
||||
|
@ -128,86 +190,54 @@ namespace Ryujinx.HLE.HOS.Kernel
|
|||
return;
|
||||
}
|
||||
|
||||
KThread Thread = Process.HandleTable.GetData<KThread>(ThreadHandle);
|
||||
long Result = System.AddressArbiter.WaitProcessWideKeyAtomic(
|
||||
Memory,
|
||||
MutexAddress,
|
||||
CondVarAddress,
|
||||
ThreadHandle,
|
||||
Timeout);
|
||||
|
||||
if (Thread == null)
|
||||
if (Result != 0)
|
||||
{
|
||||
Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{ThreadHandle:x8}!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
|
||||
|
||||
return;
|
||||
if (Result == MakeError(ErrorModule.Kernel, KernelErr.Timeout))
|
||||
{
|
||||
Device.Log.PrintDebug(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!");
|
||||
}
|
||||
else
|
||||
{
|
||||
Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!");
|
||||
}
|
||||
}
|
||||
|
||||
KThread WaitThread = Process.GetThread(ThreadState.Tpidr);
|
||||
|
||||
if (!CondVarWait(WaitThread, ThreadHandle, MutexAddress, CondVarAddress, Timeout))
|
||||
{
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.Timeout);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadState.X0 = 0;
|
||||
ThreadState.X0 = (ulong)Result;
|
||||
}
|
||||
|
||||
private void SvcSignalProcessWideKey(AThreadState ThreadState)
|
||||
{
|
||||
long CondVarAddress = (long)ThreadState.X0;
|
||||
int Count = (int)ThreadState.X1;
|
||||
long Address = (long)ThreadState.X0;
|
||||
int Count = (int)ThreadState.X1;
|
||||
|
||||
Device.Log.PrintDebug(LogClass.KernelSvc,
|
||||
"CondVarAddress = 0x" + CondVarAddress.ToString("x16") + ", " +
|
||||
"Count = 0x" + Count .ToString("x8"));
|
||||
"Address = 0x" + Address.ToString("x16") + ", " +
|
||||
"Count = 0x" + Count .ToString("x8"));
|
||||
|
||||
KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
|
||||
|
||||
CondVarSignal(ThreadState, CurrThread, CondVarAddress, Count);
|
||||
System.AddressArbiter.SignalProcessWideKey(Process, Memory, Address, Count);
|
||||
|
||||
ThreadState.X0 = 0;
|
||||
}
|
||||
|
||||
private void MutexLock(
|
||||
KThread CurrThread,
|
||||
KThread WaitThread,
|
||||
int OwnerThreadHandle,
|
||||
int WaitThreadHandle,
|
||||
long MutexAddress)
|
||||
{
|
||||
lock (Process.ThreadSyncLock)
|
||||
{
|
||||
int MutexValue = Memory.ReadInt32(MutexAddress);
|
||||
|
||||
Device.Log.PrintDebug(LogClass.KernelSvc, "MutexValue = 0x" + MutexValue.ToString("x8"));
|
||||
|
||||
if (MutexValue != (OwnerThreadHandle | MutexHasListenersMask))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CurrThread.WaitHandle = WaitThreadHandle;
|
||||
CurrThread.MutexAddress = MutexAddress;
|
||||
|
||||
InsertWaitingMutexThreadUnsafe(OwnerThreadHandle, WaitThread);
|
||||
}
|
||||
|
||||
Device.Log.PrintDebug(LogClass.KernelSvc, "Entering wait state...");
|
||||
|
||||
Process.Scheduler.EnterWait(CurrThread);
|
||||
}
|
||||
|
||||
private void SvcWaitForAddress(AThreadState ThreadState)
|
||||
{
|
||||
long Address = (long)ThreadState.X0;
|
||||
long Address = (long)ThreadState.X0;
|
||||
ArbitrationType Type = (ArbitrationType)ThreadState.X1;
|
||||
int Value = (int)ThreadState.X2;
|
||||
ulong Timeout = ThreadState.X3;
|
||||
int Value = (int)ThreadState.X2;
|
||||
long Timeout = (long)ThreadState.X3;
|
||||
|
||||
Device.Log.PrintDebug(LogClass.KernelSvc,
|
||||
"Address = 0x" + Address.ToString("x16") + ", " +
|
||||
"ArbitrationType = 0x" + Type .ToString() + ", " +
|
||||
"Value = 0x" + Value .ToString("x8") + ", " +
|
||||
"Timeout = 0x" + Timeout.ToString("x16"));
|
||||
"Address = 0x" + Address.ToString("x16") + ", " +
|
||||
"Type = " + Type .ToString() + ", " +
|
||||
"Value = 0x" + Value .ToString("x8") + ", " +
|
||||
"Timeout = 0x" + Timeout.ToString("x16"));
|
||||
|
||||
if (IsPointingInsideKernel(Address))
|
||||
{
|
||||
|
@ -227,287 +257,93 @@ namespace Ryujinx.HLE.HOS.Kernel
|
|||
return;
|
||||
}
|
||||
|
||||
long Result;
|
||||
|
||||
switch (Type)
|
||||
{
|
||||
case ArbitrationType.WaitIfLessThan:
|
||||
ThreadState.X0 = AddressArbiter.WaitForAddressIfLessThan(Process, ThreadState, Memory, Address, Value, Timeout, false);
|
||||
Result = System.AddressArbiter.WaitForAddressIfLessThan(Memory, Address, Value, false, Timeout);
|
||||
break;
|
||||
|
||||
case ArbitrationType.DecrementAndWaitIfLessThan:
|
||||
ThreadState.X0 = AddressArbiter.WaitForAddressIfLessThan(Process, ThreadState, Memory, Address, Value, Timeout, true);
|
||||
Result = System.AddressArbiter.WaitForAddressIfLessThan(Memory, Address, Value, true, Timeout);
|
||||
break;
|
||||
|
||||
case ArbitrationType.WaitIfEqual:
|
||||
ThreadState.X0 = AddressArbiter.WaitForAddressIfEqual(Process, ThreadState, Memory, Address, Value, Timeout);
|
||||
Result = System.AddressArbiter.WaitForAddressIfEqual(Memory, Address, Value, Timeout);
|
||||
break;
|
||||
|
||||
default:
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidEnumValue);
|
||||
Result = MakeError(ErrorModule.Kernel, KernelErr.InvalidEnumValue);
|
||||
break;
|
||||
}
|
||||
|
||||
if (Result != 0)
|
||||
{
|
||||
Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!");
|
||||
}
|
||||
|
||||
ThreadState.X0 = (ulong)Result;
|
||||
}
|
||||
|
||||
private void MutexUnlock(KThread CurrThread, long MutexAddress)
|
||||
private void SvcSignalToAddress(AThreadState ThreadState)
|
||||
{
|
||||
lock (Process.ThreadSyncLock)
|
||||
long Address = (long)ThreadState.X0;
|
||||
SignalType Type = (SignalType)ThreadState.X1;
|
||||
int Value = (int)ThreadState.X2;
|
||||
int Count = (int)ThreadState.X3;
|
||||
|
||||
Device.Log.PrintDebug(LogClass.KernelSvc,
|
||||
"Address = 0x" + Address.ToString("x16") + ", " +
|
||||
"Type = " + Type .ToString() + ", " +
|
||||
"Value = 0x" + Value .ToString("x8") + ", " +
|
||||
"Count = 0x" + Count .ToString("x8"));
|
||||
|
||||
if (IsPointingInsideKernel(Address))
|
||||
{
|
||||
//This is the new thread that will now own the mutex.
|
||||
//If no threads are waiting for the lock, then it should be null.
|
||||
(KThread OwnerThread, int Count) = PopMutexThreadUnsafe(CurrThread, MutexAddress);
|
||||
Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid address 0x{Address:x16}!");
|
||||
|
||||
if (OwnerThread == CurrThread)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
if (OwnerThread != null)
|
||||
{
|
||||
//Remove all waiting mutex from the old owner,
|
||||
//and insert then on the new owner.
|
||||
UpdateMutexOwnerUnsafe(CurrThread, OwnerThread, MutexAddress);
|
||||
|
||||
CurrThread.UpdatePriority();
|
||||
|
||||
int HasListeners = Count >= 2 ? MutexHasListenersMask : 0;
|
||||
|
||||
Memory.WriteInt32ToSharedAddr(MutexAddress, HasListeners | OwnerThread.WaitHandle);
|
||||
|
||||
OwnerThread.WaitHandle = 0;
|
||||
OwnerThread.MutexAddress = 0;
|
||||
OwnerThread.CondVarAddress = 0;
|
||||
OwnerThread.MutexOwner = null;
|
||||
|
||||
OwnerThread.UpdatePriority();
|
||||
|
||||
Process.Scheduler.WakeUp(OwnerThread);
|
||||
|
||||
Device.Log.PrintDebug(LogClass.KernelSvc, "Gave mutex to thread id " + OwnerThread.ThreadId + "!");
|
||||
}
|
||||
else
|
||||
{
|
||||
Memory.WriteInt32ToSharedAddr(MutexAddress, 0);
|
||||
|
||||
Device.Log.PrintDebug(LogClass.KernelSvc, "No threads waiting mutex!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool CondVarWait(
|
||||
KThread WaitThread,
|
||||
int WaitThreadHandle,
|
||||
long MutexAddress,
|
||||
long CondVarAddress,
|
||||
ulong Timeout)
|
||||
{
|
||||
WaitThread.WaitHandle = WaitThreadHandle;
|
||||
WaitThread.MutexAddress = MutexAddress;
|
||||
WaitThread.CondVarAddress = CondVarAddress;
|
||||
|
||||
lock (Process.ThreadSyncLock)
|
||||
{
|
||||
MutexUnlock(WaitThread, MutexAddress);
|
||||
|
||||
WaitThread.CondVarSignaled = false;
|
||||
|
||||
Process.ThreadArbiterList.Add(WaitThread);
|
||||
}
|
||||
|
||||
Device.Log.PrintDebug(LogClass.KernelSvc, "Entering wait state...");
|
||||
|
||||
if (Timeout != ulong.MaxValue)
|
||||
{
|
||||
Process.Scheduler.EnterWait(WaitThread, NsTimeConverter.GetTimeMs(Timeout));
|
||||
|
||||
lock (Process.ThreadSyncLock)
|
||||
{
|
||||
if (!WaitThread.CondVarSignaled || WaitThread.MutexOwner != null)
|
||||
{
|
||||
if (WaitThread.MutexOwner != null)
|
||||
{
|
||||
WaitThread.MutexOwner.MutexWaiters.Remove(WaitThread);
|
||||
WaitThread.MutexOwner.UpdatePriority();
|
||||
|
||||
WaitThread.MutexOwner = null;
|
||||
}
|
||||
|
||||
Process.ThreadArbiterList.Remove(WaitThread);
|
||||
|
||||
Device.Log.PrintDebug(LogClass.KernelSvc, "Timed out...");
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Process.Scheduler.EnterWait(WaitThread);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void CondVarSignal(
|
||||
AThreadState ThreadState,
|
||||
KThread CurrThread,
|
||||
long CondVarAddress,
|
||||
int Count)
|
||||
{
|
||||
lock (Process.ThreadSyncLock)
|
||||
{
|
||||
while (Count == -1 || Count-- > 0)
|
||||
{
|
||||
KThread WaitThread = PopCondVarThreadUnsafe(CondVarAddress);
|
||||
|
||||
if (WaitThread == null)
|
||||
{
|
||||
Device.Log.PrintDebug(LogClass.KernelSvc, "No more threads to wake up!");
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
WaitThread.CondVarSignaled = true;
|
||||
|
||||
long MutexAddress = WaitThread.MutexAddress;
|
||||
|
||||
Memory.SetExclusive(ThreadState, MutexAddress);
|
||||
|
||||
int MutexValue = Memory.ReadInt32(MutexAddress);
|
||||
|
||||
while (MutexValue != 0)
|
||||
{
|
||||
if (Memory.TestExclusive(ThreadState, MutexAddress))
|
||||
{
|
||||
//Wait until the lock is released.
|
||||
InsertWaitingMutexThreadUnsafe(MutexValue & ~MutexHasListenersMask, WaitThread);
|
||||
|
||||
Memory.WriteInt32(MutexAddress, MutexValue | MutexHasListenersMask);
|
||||
|
||||
Memory.ClearExclusiveForStore(ThreadState);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
Memory.SetExclusive(ThreadState, MutexAddress);
|
||||
|
||||
MutexValue = Memory.ReadInt32(MutexAddress);
|
||||
}
|
||||
|
||||
Device.Log.PrintDebug(LogClass.KernelSvc, "MutexValue = 0x" + MutexValue.ToString("x8"));
|
||||
|
||||
if (MutexValue == 0)
|
||||
{
|
||||
//Give the lock to this thread.
|
||||
Memory.WriteInt32ToSharedAddr(MutexAddress, WaitThread.WaitHandle);
|
||||
|
||||
WaitThread.WaitHandle = 0;
|
||||
WaitThread.MutexAddress = 0;
|
||||
WaitThread.CondVarAddress = 0;
|
||||
|
||||
WaitThread.MutexOwner?.UpdatePriority();
|
||||
|
||||
WaitThread.MutexOwner = null;
|
||||
|
||||
Process.Scheduler.WakeUp(WaitThread);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateMutexOwnerUnsafe(KThread CurrThread, KThread NewOwner, long MutexAddress)
|
||||
{
|
||||
//Go through all threads waiting for the mutex,
|
||||
//and update the MutexOwner field to point to the new owner.
|
||||
for (int Index = 0; Index < CurrThread.MutexWaiters.Count; Index++)
|
||||
{
|
||||
KThread Thread = CurrThread.MutexWaiters[Index];
|
||||
|
||||
if (Thread.MutexAddress == MutexAddress)
|
||||
{
|
||||
CurrThread.MutexWaiters.RemoveAt(Index--);
|
||||
|
||||
InsertWaitingMutexThreadUnsafe(NewOwner, Thread);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void InsertWaitingMutexThreadUnsafe(int OwnerThreadHandle, KThread WaitThread)
|
||||
{
|
||||
KThread OwnerThread = Process.HandleTable.GetData<KThread>(OwnerThreadHandle);
|
||||
|
||||
if (OwnerThread == null)
|
||||
{
|
||||
Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{OwnerThreadHandle:x8}!");
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
InsertWaitingMutexThreadUnsafe(OwnerThread, WaitThread);
|
||||
}
|
||||
|
||||
private void InsertWaitingMutexThreadUnsafe(KThread OwnerThread, KThread WaitThread)
|
||||
{
|
||||
WaitThread.MutexOwner = OwnerThread;
|
||||
|
||||
if (!OwnerThread.MutexWaiters.Contains(WaitThread))
|
||||
if (IsAddressNotWordAligned(Address))
|
||||
{
|
||||
OwnerThread.MutexWaiters.Add(WaitThread);
|
||||
Device.Log.PrintWarning(LogClass.KernelSvc, $"Unaligned address 0x{Address:x16}!");
|
||||
|
||||
OwnerThread.UpdatePriority();
|
||||
}
|
||||
}
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
|
||||
|
||||
private (KThread, int) PopMutexThreadUnsafe(KThread OwnerThread, long MutexAddress)
|
||||
{
|
||||
int Count = 0;
|
||||
|
||||
KThread WakeThread = null;
|
||||
|
||||
foreach (KThread Thread in OwnerThread.MutexWaiters)
|
||||
{
|
||||
if (Thread.MutexAddress != MutexAddress)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (WakeThread == null || Thread.ActualPriority < WakeThread.ActualPriority)
|
||||
{
|
||||
WakeThread = Thread;
|
||||
}
|
||||
|
||||
Count++;
|
||||
return;
|
||||
}
|
||||
|
||||
if (WakeThread != null)
|
||||
long Result;
|
||||
|
||||
switch (Type)
|
||||
{
|
||||
OwnerThread.MutexWaiters.Remove(WakeThread);
|
||||
case SignalType.Signal:
|
||||
Result = System.AddressArbiter.Signal(Address, Count);
|
||||
break;
|
||||
|
||||
case SignalType.SignalAndIncrementIfEqual:
|
||||
Result = System.AddressArbiter.SignalAndIncrementIfEqual(Memory, Address, Value, Count);
|
||||
break;
|
||||
|
||||
case SignalType.SignalAndModifyIfEqual:
|
||||
Result = System.AddressArbiter.SignalAndModifyIfEqual(Memory, Address, Value, Count);
|
||||
break;
|
||||
|
||||
default:
|
||||
Result = MakeError(ErrorModule.Kernel, KernelErr.InvalidEnumValue);
|
||||
break;
|
||||
}
|
||||
|
||||
return (WakeThread, Count);
|
||||
}
|
||||
|
||||
private KThread PopCondVarThreadUnsafe(long CondVarAddress)
|
||||
{
|
||||
KThread WakeThread = null;
|
||||
|
||||
foreach (KThread Thread in Process.ThreadArbiterList)
|
||||
if (Result != 0)
|
||||
{
|
||||
if (Thread.CondVarAddress != CondVarAddress)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (WakeThread == null || Thread.ActualPriority < WakeThread.ActualPriority)
|
||||
{
|
||||
WakeThread = Thread;
|
||||
}
|
||||
Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!");
|
||||
}
|
||||
|
||||
if (WakeThread != null)
|
||||
{
|
||||
Process.ThreadArbiterList.Remove(WakeThread);
|
||||
}
|
||||
|
||||
return WakeThread;
|
||||
ThreadState.X0 = (ulong)Result;
|
||||
}
|
||||
|
||||
private bool IsPointingInsideKernel(long Address)
|
||||
|
|
|
@ -1,158 +0,0 @@
|
|||
namespace Ryujinx.HLE.HOS.Kernel
|
||||
{
|
||||
class ThreadQueue
|
||||
{
|
||||
private const int LowestPriority = 0x3f;
|
||||
|
||||
private SchedulerThread Head;
|
||||
|
||||
private object ListLock;
|
||||
|
||||
public ThreadQueue()
|
||||
{
|
||||
ListLock = new object();
|
||||
}
|
||||
|
||||
public void Push(SchedulerThread Wait)
|
||||
{
|
||||
lock (ListLock)
|
||||
{
|
||||
//Ensure that we're not creating circular references
|
||||
//by adding a thread that is already on the list.
|
||||
if (HasThread(Wait))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Head == null || Head.Thread.ActualPriority >= Wait.Thread.ActualPriority)
|
||||
{
|
||||
Wait.Next = Head;
|
||||
|
||||
Head = Wait;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
SchedulerThread Curr = Head;
|
||||
|
||||
while (Curr.Next != null)
|
||||
{
|
||||
if (Curr.Next.Thread.ActualPriority >= Wait.Thread.ActualPriority)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
Curr = Curr.Next;
|
||||
}
|
||||
|
||||
Wait.Next = Curr.Next;
|
||||
Curr.Next = Wait;
|
||||
}
|
||||
}
|
||||
|
||||
public SchedulerThread Pop(int Core, int MinPriority = LowestPriority)
|
||||
{
|
||||
lock (ListLock)
|
||||
{
|
||||
int CoreMask = 1 << Core;
|
||||
|
||||
SchedulerThread Prev = null;
|
||||
SchedulerThread Curr = Head;
|
||||
|
||||
while (Curr != null)
|
||||
{
|
||||
KThread Thread = Curr.Thread;
|
||||
|
||||
if (Thread.ActualPriority <= MinPriority && (Thread.CoreMask & CoreMask) != 0)
|
||||
{
|
||||
if (Prev != null)
|
||||
{
|
||||
Prev.Next = Curr.Next;
|
||||
}
|
||||
else
|
||||
{
|
||||
Head = Head.Next;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
Prev = Curr;
|
||||
Curr = Curr.Next;
|
||||
}
|
||||
|
||||
return Curr;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Remove(SchedulerThread Thread)
|
||||
{
|
||||
lock (ListLock)
|
||||
{
|
||||
if (Head == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (Head == Thread)
|
||||
{
|
||||
Head = Head.Next;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
SchedulerThread Prev = Head;
|
||||
SchedulerThread Curr = Head.Next;
|
||||
|
||||
while (Curr != null)
|
||||
{
|
||||
if (Curr == Thread)
|
||||
{
|
||||
Prev.Next = Curr.Next;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Prev = Curr;
|
||||
Curr = Curr.Next;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Resort(SchedulerThread Thread)
|
||||
{
|
||||
lock (ListLock)
|
||||
{
|
||||
if (Remove(Thread))
|
||||
{
|
||||
Push(Thread);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasThread(SchedulerThread Thread)
|
||||
{
|
||||
lock (ListLock)
|
||||
{
|
||||
SchedulerThread Curr = Head;
|
||||
|
||||
while (Curr != null)
|
||||
{
|
||||
if (Curr == Thread)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Curr = Curr.Next;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
15
Ryujinx.HLE/HOS/Kernel/ThreadSchedState.cs
Normal file
15
Ryujinx.HLE/HOS/Kernel/ThreadSchedState.cs
Normal file
|
@ -0,0 +1,15 @@
|
|||
namespace Ryujinx.HLE.HOS.Kernel
|
||||
{
|
||||
enum ThreadSchedState : byte
|
||||
{
|
||||
LowNibbleMask = 0xf,
|
||||
HighNibbleMask = 0xf0,
|
||||
ExceptionalMask = 0x70,
|
||||
ForcePauseFlag = 0x20,
|
||||
|
||||
None = 0,
|
||||
Paused = 1,
|
||||
Running = 2,
|
||||
TerminationPending = 3
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue