KAddressArbiter.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577
  1. using Ryujinx.HLE.HOS.Kernel.Common;
  2. using Ryujinx.HLE.HOS.Kernel.Process;
  3. using Ryujinx.Horizon.Common;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Linq;
  7. using System.Threading;
  8. namespace Ryujinx.HLE.HOS.Kernel.Threading
  9. {
  10. class KAddressArbiter
  11. {
  12. private const int HasListenersMask = 0x40000000;
  13. private readonly KernelContext _context;
  14. private readonly List<KThread> _condVarThreads;
  15. private readonly List<KThread> _arbiterThreads;
  16. public KAddressArbiter(KernelContext context)
  17. {
  18. _context = context;
  19. _condVarThreads = new List<KThread>();
  20. _arbiterThreads = new List<KThread>();
  21. }
  22. public Result ArbitrateLock(int ownerHandle, ulong mutexAddress, int requesterHandle)
  23. {
  24. KThread currentThread = KernelStatic.GetCurrentThread();
  25. _context.CriticalSection.Enter();
  26. currentThread.SignaledObj = null;
  27. currentThread.ObjSyncResult = Result.Success;
  28. KProcess currentProcess = KernelStatic.GetCurrentProcess();
  29. if (!KernelTransfer.UserToKernel(out int mutexValue, mutexAddress))
  30. {
  31. _context.CriticalSection.Leave();
  32. return KernelResult.InvalidMemState;
  33. }
  34. if (mutexValue != (ownerHandle | HasListenersMask))
  35. {
  36. _context.CriticalSection.Leave();
  37. return Result.Success;
  38. }
  39. KThread mutexOwner = currentProcess.HandleTable.GetObject<KThread>(ownerHandle);
  40. if (mutexOwner == null)
  41. {
  42. _context.CriticalSection.Leave();
  43. return KernelResult.InvalidHandle;
  44. }
  45. currentThread.MutexAddress = mutexAddress;
  46. currentThread.ThreadHandleForUserMutex = requesterHandle;
  47. mutexOwner.AddMutexWaiter(currentThread);
  48. currentThread.Reschedule(ThreadSchedState.Paused);
  49. _context.CriticalSection.Leave();
  50. _context.CriticalSection.Enter();
  51. if (currentThread.MutexOwner != null)
  52. {
  53. currentThread.MutexOwner.RemoveMutexWaiter(currentThread);
  54. }
  55. _context.CriticalSection.Leave();
  56. return currentThread.ObjSyncResult;
  57. }
  58. public Result ArbitrateUnlock(ulong mutexAddress)
  59. {
  60. _context.CriticalSection.Enter();
  61. KThread currentThread = KernelStatic.GetCurrentThread();
  62. (int mutexValue, KThread newOwnerThread) = MutexUnlock(currentThread, mutexAddress);
  63. Result result = Result.Success;
  64. if (!KernelTransfer.KernelToUser(mutexAddress, mutexValue))
  65. {
  66. result = KernelResult.InvalidMemState;
  67. }
  68. if (result != Result.Success && newOwnerThread != null)
  69. {
  70. newOwnerThread.SignaledObj = null;
  71. newOwnerThread.ObjSyncResult = result;
  72. }
  73. _context.CriticalSection.Leave();
  74. return result;
  75. }
  76. public Result WaitProcessWideKeyAtomic(ulong mutexAddress, ulong condVarAddress, int threadHandle, long timeout)
  77. {
  78. _context.CriticalSection.Enter();
  79. KThread currentThread = KernelStatic.GetCurrentThread();
  80. currentThread.SignaledObj = null;
  81. currentThread.ObjSyncResult = KernelResult.TimedOut;
  82. if (currentThread.ShallBeTerminated ||
  83. currentThread.SchedFlags == ThreadSchedState.TerminationPending)
  84. {
  85. _context.CriticalSection.Leave();
  86. return KernelResult.ThreadTerminating;
  87. }
  88. (int mutexValue, _) = MutexUnlock(currentThread, mutexAddress);
  89. KernelTransfer.KernelToUser(condVarAddress, 1);
  90. if (!KernelTransfer.KernelToUser(mutexAddress, mutexValue))
  91. {
  92. _context.CriticalSection.Leave();
  93. return KernelResult.InvalidMemState;
  94. }
  95. currentThread.MutexAddress = mutexAddress;
  96. currentThread.ThreadHandleForUserMutex = threadHandle;
  97. currentThread.CondVarAddress = condVarAddress;
  98. _condVarThreads.Add(currentThread);
  99. if (timeout != 0)
  100. {
  101. currentThread.Reschedule(ThreadSchedState.Paused);
  102. if (timeout > 0)
  103. {
  104. _context.TimeManager.ScheduleFutureInvocation(currentThread, timeout);
  105. }
  106. }
  107. _context.CriticalSection.Leave();
  108. if (timeout > 0)
  109. {
  110. _context.TimeManager.UnscheduleFutureInvocation(currentThread);
  111. }
  112. _context.CriticalSection.Enter();
  113. if (currentThread.MutexOwner != null)
  114. {
  115. currentThread.MutexOwner.RemoveMutexWaiter(currentThread);
  116. }
  117. _condVarThreads.Remove(currentThread);
  118. _context.CriticalSection.Leave();
  119. return currentThread.ObjSyncResult;
  120. }
  121. private (int, KThread) MutexUnlock(KThread currentThread, ulong mutexAddress)
  122. {
  123. KThread newOwnerThread = currentThread.RelinquishMutex(mutexAddress, out int count);
  124. int mutexValue = 0;
  125. if (newOwnerThread != null)
  126. {
  127. mutexValue = newOwnerThread.ThreadHandleForUserMutex;
  128. if (count >= 2)
  129. {
  130. mutexValue |= HasListenersMask;
  131. }
  132. newOwnerThread.SignaledObj = null;
  133. newOwnerThread.ObjSyncResult = Result.Success;
  134. newOwnerThread.ReleaseAndResume();
  135. }
  136. return (mutexValue, newOwnerThread);
  137. }
  138. public void SignalProcessWideKey(ulong address, int count)
  139. {
  140. _context.CriticalSection.Enter();
  141. WakeThreads(_condVarThreads, count, TryAcquireMutex, x => x.CondVarAddress == address);
  142. if (!_condVarThreads.Any(x => x.CondVarAddress == address))
  143. {
  144. KernelTransfer.KernelToUser(address, 0);
  145. }
  146. _context.CriticalSection.Leave();
  147. }
  148. private static void TryAcquireMutex(KThread requester)
  149. {
  150. ulong address = requester.MutexAddress;
  151. KProcess currentProcess = KernelStatic.GetCurrentProcess();
  152. if (!currentProcess.CpuMemory.IsMapped(address))
  153. {
  154. // Invalid address.
  155. requester.SignaledObj = null;
  156. requester.ObjSyncResult = KernelResult.InvalidMemState;
  157. return;
  158. }
  159. ref int mutexRef = ref currentProcess.CpuMemory.GetRef<int>(address);
  160. int mutexValue, newMutexValue;
  161. do
  162. {
  163. mutexValue = mutexRef;
  164. if (mutexValue != 0)
  165. {
  166. // Update value to indicate there is a mutex waiter now.
  167. newMutexValue = mutexValue | HasListenersMask;
  168. }
  169. else
  170. {
  171. // No thread owning the mutex, assign to requesting thread.
  172. newMutexValue = requester.ThreadHandleForUserMutex;
  173. }
  174. }
  175. while (Interlocked.CompareExchange(ref mutexRef, newMutexValue, mutexValue) != mutexValue);
  176. if (mutexValue == 0)
  177. {
  178. // We now own the mutex.
  179. requester.SignaledObj = null;
  180. requester.ObjSyncResult = Result.Success;
  181. requester.ReleaseAndResume();
  182. return;
  183. }
  184. mutexValue &= ~HasListenersMask;
  185. KThread mutexOwner = currentProcess.HandleTable.GetObject<KThread>(mutexValue);
  186. if (mutexOwner != null)
  187. {
  188. // Mutex already belongs to another thread, wait for it.
  189. mutexOwner.AddMutexWaiter(requester);
  190. }
  191. else
  192. {
  193. // Invalid mutex owner.
  194. requester.SignaledObj = null;
  195. requester.ObjSyncResult = KernelResult.InvalidHandle;
  196. requester.ReleaseAndResume();
  197. }
  198. }
  199. public Result WaitForAddressIfEqual(ulong address, int value, long timeout)
  200. {
  201. KThread currentThread = KernelStatic.GetCurrentThread();
  202. _context.CriticalSection.Enter();
  203. if (currentThread.ShallBeTerminated ||
  204. currentThread.SchedFlags == ThreadSchedState.TerminationPending)
  205. {
  206. _context.CriticalSection.Leave();
  207. return KernelResult.ThreadTerminating;
  208. }
  209. currentThread.SignaledObj = null;
  210. currentThread.ObjSyncResult = KernelResult.TimedOut;
  211. if (!KernelTransfer.UserToKernel(out int currentValue, address))
  212. {
  213. _context.CriticalSection.Leave();
  214. return KernelResult.InvalidMemState;
  215. }
  216. if (currentValue == value)
  217. {
  218. if (timeout == 0)
  219. {
  220. _context.CriticalSection.Leave();
  221. return KernelResult.TimedOut;
  222. }
  223. currentThread.MutexAddress = address;
  224. currentThread.WaitingInArbitration = true;
  225. _arbiterThreads.Add(currentThread);
  226. currentThread.Reschedule(ThreadSchedState.Paused);
  227. if (timeout > 0)
  228. {
  229. _context.TimeManager.ScheduleFutureInvocation(currentThread, timeout);
  230. }
  231. _context.CriticalSection.Leave();
  232. if (timeout > 0)
  233. {
  234. _context.TimeManager.UnscheduleFutureInvocation(currentThread);
  235. }
  236. _context.CriticalSection.Enter();
  237. if (currentThread.WaitingInArbitration)
  238. {
  239. _arbiterThreads.Remove(currentThread);
  240. currentThread.WaitingInArbitration = false;
  241. }
  242. _context.CriticalSection.Leave();
  243. return currentThread.ObjSyncResult;
  244. }
  245. _context.CriticalSection.Leave();
  246. return KernelResult.InvalidState;
  247. }
  248. public Result WaitForAddressIfLessThan(ulong address, int value, bool shouldDecrement, long timeout)
  249. {
  250. KThread currentThread = KernelStatic.GetCurrentThread();
  251. _context.CriticalSection.Enter();
  252. if (currentThread.ShallBeTerminated ||
  253. currentThread.SchedFlags == ThreadSchedState.TerminationPending)
  254. {
  255. _context.CriticalSection.Leave();
  256. return KernelResult.ThreadTerminating;
  257. }
  258. currentThread.SignaledObj = null;
  259. currentThread.ObjSyncResult = KernelResult.TimedOut;
  260. KProcess currentProcess = KernelStatic.GetCurrentProcess();
  261. if (!KernelTransfer.UserToKernel(out int currentValue, address))
  262. {
  263. _context.CriticalSection.Leave();
  264. return KernelResult.InvalidMemState;
  265. }
  266. if (shouldDecrement)
  267. {
  268. currentValue = Interlocked.Decrement(ref currentProcess.CpuMemory.GetRef<int>(address)) + 1;
  269. }
  270. if (currentValue < value)
  271. {
  272. if (timeout == 0)
  273. {
  274. _context.CriticalSection.Leave();
  275. return KernelResult.TimedOut;
  276. }
  277. currentThread.MutexAddress = address;
  278. currentThread.WaitingInArbitration = true;
  279. _arbiterThreads.Add(currentThread);
  280. currentThread.Reschedule(ThreadSchedState.Paused);
  281. if (timeout > 0)
  282. {
  283. _context.TimeManager.ScheduleFutureInvocation(currentThread, timeout);
  284. }
  285. _context.CriticalSection.Leave();
  286. if (timeout > 0)
  287. {
  288. _context.TimeManager.UnscheduleFutureInvocation(currentThread);
  289. }
  290. _context.CriticalSection.Enter();
  291. if (currentThread.WaitingInArbitration)
  292. {
  293. _arbiterThreads.Remove(currentThread);
  294. currentThread.WaitingInArbitration = false;
  295. }
  296. _context.CriticalSection.Leave();
  297. return currentThread.ObjSyncResult;
  298. }
  299. _context.CriticalSection.Leave();
  300. return KernelResult.InvalidState;
  301. }
  302. public Result Signal(ulong address, int count)
  303. {
  304. _context.CriticalSection.Enter();
  305. WakeArbiterThreads(address, count);
  306. _context.CriticalSection.Leave();
  307. return Result.Success;
  308. }
  309. public Result SignalAndIncrementIfEqual(ulong address, int value, int count)
  310. {
  311. _context.CriticalSection.Enter();
  312. KProcess currentProcess = KernelStatic.GetCurrentProcess();
  313. if (!currentProcess.CpuMemory.IsMapped(address))
  314. {
  315. _context.CriticalSection.Leave();
  316. return KernelResult.InvalidMemState;
  317. }
  318. ref int valueRef = ref currentProcess.CpuMemory.GetRef<int>(address);
  319. int currentValue;
  320. do
  321. {
  322. currentValue = valueRef;
  323. if (currentValue != value)
  324. {
  325. _context.CriticalSection.Leave();
  326. return KernelResult.InvalidState;
  327. }
  328. }
  329. while (Interlocked.CompareExchange(ref valueRef, currentValue + 1, currentValue) != currentValue);
  330. WakeArbiterThreads(address, count);
  331. _context.CriticalSection.Leave();
  332. return Result.Success;
  333. }
  334. public Result SignalAndModifyIfEqual(ulong address, int value, int count)
  335. {
  336. _context.CriticalSection.Enter();
  337. int addend;
  338. // The value is decremented if the number of threads waiting is less
  339. // or equal to the Count of threads to be signaled, or Count is zero
  340. // or negative. It is incremented if there are no threads waiting.
  341. int waitingCount = 0;
  342. foreach (KThread thread in _arbiterThreads.Where(x => x.MutexAddress == address))
  343. {
  344. if (++waitingCount >= count)
  345. {
  346. break;
  347. }
  348. }
  349. if (waitingCount > 0)
  350. {
  351. if (count <= 0)
  352. {
  353. addend = -2;
  354. }
  355. else if (waitingCount < count)
  356. {
  357. addend = -1;
  358. }
  359. else
  360. {
  361. addend = 0;
  362. }
  363. }
  364. else
  365. {
  366. addend = 1;
  367. }
  368. KProcess currentProcess = KernelStatic.GetCurrentProcess();
  369. if (!currentProcess.CpuMemory.IsMapped(address))
  370. {
  371. _context.CriticalSection.Leave();
  372. return KernelResult.InvalidMemState;
  373. }
  374. ref int valueRef = ref currentProcess.CpuMemory.GetRef<int>(address);
  375. int currentValue;
  376. do
  377. {
  378. currentValue = valueRef;
  379. if (currentValue != value)
  380. {
  381. _context.CriticalSection.Leave();
  382. return KernelResult.InvalidState;
  383. }
  384. }
  385. while (Interlocked.CompareExchange(ref valueRef, currentValue + addend, currentValue) != currentValue);
  386. WakeArbiterThreads(address, count);
  387. _context.CriticalSection.Leave();
  388. return Result.Success;
  389. }
  390. private void WakeArbiterThreads(ulong address, int count)
  391. {
  392. static void RemoveArbiterThread(KThread thread)
  393. {
  394. thread.SignaledObj = null;
  395. thread.ObjSyncResult = Result.Success;
  396. thread.ReleaseAndResume();
  397. thread.WaitingInArbitration = false;
  398. }
  399. WakeThreads(_arbiterThreads, count, RemoveArbiterThread, x => x.MutexAddress == address);
  400. }
  401. private static void WakeThreads(
  402. List<KThread> threads,
  403. int count,
  404. Action<KThread> removeCallback,
  405. Func<KThread, bool> predicate)
  406. {
  407. var candidates = threads.Where(predicate).OrderBy(x => x.DynamicPriority);
  408. var toSignal = (count > 0 ? candidates.Take(count) : candidates).ToArray();
  409. foreach (KThread thread in toSignal)
  410. {
  411. removeCallback(thread);
  412. threads.Remove(thread);
  413. }
  414. }
  415. }
  416. }