| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678 |
- 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,
- MemoryManager 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.GetObject<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(MemoryManager 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(
- MemoryManager 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(MemoryManager 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, MemoryManager 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, MemoryManager 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.GetObject<KThread>(MutexValue);
- if (MutexOwner != null)
- {
- //Mutex already belongs to another thread, wait for it.
- MutexOwner.AddMutexWaiter(Requester);
- }
- else
- {
- //Invalid mutex owner.
- Requester.SignaledObj = null;
- Requester.ObjSyncResult = (int)MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
- Requester.ReleaseAndResume();
- }
- return MutexOwner;
- }
- public long WaitForAddressIfEqual(MemoryManager 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(
- MemoryManager 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(MemoryManager 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(MemoryManager 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(MemoryManager Memory, long Address, out int Value)
- {
- if (Memory.IsMapped(Address))
- {
- Value = Memory.ReadInt32(Address);
- return true;
- }
- Value = 0;
- return false;
- }
- private bool KernelToUserInt32(MemoryManager Memory, long Address, int Value)
- {
- if (Memory.IsMapped(Address))
- {
- Memory.WriteInt32ToSharedAddr(Address, Value);
- return true;
- }
- return false;
- }
- }
- }
|