KAddressArbiter.cs 17 KB

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