aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.HLE/OsHle/Kernel
diff options
context:
space:
mode:
authorgdkchan <gab.dark.100@gmail.com>2018-06-10 21:46:42 -0300
committergdkchan <gab.dark.100@gmail.com>2018-06-10 21:46:42 -0300
commit76f3b1b3a4637ec72abfbb8cbc0679f2e0ca838f (patch)
tree0411b709de31c1c0517763512df8eeb9f7491bc9 /Ryujinx.HLE/OsHle/Kernel
parent518fe799da6dd4f12c58c9e6e174767effb0b868 (diff)
Rename Ryujinx.Core to Ryujinx.HLE and add a separate project for a future LLE implementation
Diffstat (limited to 'Ryujinx.HLE/OsHle/Kernel')
-rw-r--r--Ryujinx.HLE/OsHle/Kernel/KernelErr.cs17
-rw-r--r--Ryujinx.HLE/OsHle/Kernel/NsTimeConverter.cs19
-rw-r--r--Ryujinx.HLE/OsHle/Kernel/SvcHandler.cs147
-rw-r--r--Ryujinx.HLE/OsHle/Kernel/SvcMemory.cs285
-rw-r--r--Ryujinx.HLE/OsHle/Kernel/SvcSystem.cs369
-rw-r--r--Ryujinx.HLE/OsHle/Kernel/SvcThread.cs291
-rw-r--r--Ryujinx.HLE/OsHle/Kernel/SvcThreadSync.cs435
7 files changed, 1563 insertions, 0 deletions
diff --git a/Ryujinx.HLE/OsHle/Kernel/KernelErr.cs b/Ryujinx.HLE/OsHle/Kernel/KernelErr.cs
new file mode 100644
index 00000000..643e5f68
--- /dev/null
+++ b/Ryujinx.HLE/OsHle/Kernel/KernelErr.cs
@@ -0,0 +1,17 @@
+namespace Ryujinx.HLE.OsHle.Kernel
+{
+ static class KernelErr
+ {
+ public const int InvalidAlignment = 102;
+ public const int InvalidAddress = 106;
+ public const int InvalidMemRange = 110;
+ public const int InvalidPriority = 112;
+ public const int InvalidCoreId = 113;
+ public const int InvalidHandle = 114;
+ public const int InvalidCoreMask = 116;
+ public const int Timeout = 117;
+ public const int Canceled = 118;
+ public const int CountOutOfRange = 119;
+ public const int InvalidInfo = 120;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/OsHle/Kernel/NsTimeConverter.cs b/Ryujinx.HLE/OsHle/Kernel/NsTimeConverter.cs
new file mode 100644
index 00000000..966fdaca
--- /dev/null
+++ b/Ryujinx.HLE/OsHle/Kernel/NsTimeConverter.cs
@@ -0,0 +1,19 @@
+namespace Ryujinx.HLE.OsHle.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;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/OsHle/Kernel/SvcHandler.cs b/Ryujinx.HLE/OsHle/Kernel/SvcHandler.cs
new file mode 100644
index 00000000..f3a5fae0
--- /dev/null
+++ b/Ryujinx.HLE/OsHle/Kernel/SvcHandler.cs
@@ -0,0 +1,147 @@
+using ChocolArm64.Events;
+using ChocolArm64.Memory;
+using ChocolArm64.State;
+using Ryujinx.HLE.Logging;
+using Ryujinx.HLE.OsHle.Handles;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Threading;
+
+namespace Ryujinx.HLE.OsHle.Kernel
+{
+ partial class SvcHandler : IDisposable
+ {
+ private delegate void SvcFunc(AThreadState ThreadState);
+
+ private Dictionary<int, SvcFunc> SvcFuncs;
+
+ private Switch Ns;
+ private Process Process;
+ private AMemory Memory;
+
+ private ConcurrentDictionary<KThread, AutoResetEvent> SyncWaits;
+
+ private HashSet<(HSharedMem, long)> MappedSharedMems;
+
+ private ulong CurrentHeapSize;
+
+ private const uint SelfThreadHandle = 0xffff8000;
+ private const uint SelfProcessHandle = 0xffff8001;
+
+ private static Random Rng;
+
+ public SvcHandler(Switch Ns, Process Process)
+ {
+ SvcFuncs = new Dictionary<int, SvcFunc>()
+ {
+ { 0x01, SvcSetHeapSize },
+ { 0x03, SvcSetMemoryAttribute },
+ { 0x04, SvcMapMemory },
+ { 0x05, SvcUnmapMemory },
+ { 0x06, SvcQueryMemory },
+ { 0x07, SvcExitProcess },
+ { 0x08, SvcCreateThread },
+ { 0x09, SvcStartThread },
+ { 0x0a, SvcExitThread },
+ { 0x0b, SvcSleepThread },
+ { 0x0c, SvcGetThreadPriority },
+ { 0x0d, SvcSetThreadPriority },
+ { 0x0e, SvcGetThreadCoreMask },
+ { 0x0f, SvcSetThreadCoreMask },
+ { 0x10, SvcGetCurrentProcessorNumber },
+ { 0x12, SvcClearEvent },
+ { 0x13, SvcMapSharedMemory },
+ { 0x14, SvcUnmapSharedMemory },
+ { 0x15, SvcCreateTransferMemory },
+ { 0x16, SvcCloseHandle },
+ { 0x17, SvcResetSignal },
+ { 0x18, SvcWaitSynchronization },
+ { 0x19, SvcCancelSynchronization },
+ { 0x1a, SvcArbitrateLock },
+ { 0x1b, SvcArbitrateUnlock },
+ { 0x1c, SvcWaitProcessWideKeyAtomic },
+ { 0x1d, SvcSignalProcessWideKey },
+ { 0x1e, SvcGetSystemTick },
+ { 0x1f, SvcConnectToNamedPort },
+ { 0x21, SvcSendSyncRequest },
+ { 0x22, SvcSendSyncRequestWithUserBuffer },
+ { 0x25, SvcGetThreadId },
+ { 0x26, SvcBreak },
+ { 0x27, SvcOutputDebugString },
+ { 0x29, SvcGetInfo },
+ { 0x2c, SvcMapPhysicalMemory },
+ { 0x2d, SvcUnmapPhysicalMemory },
+ { 0x32, SvcSetThreadActivity }
+ };
+
+ this.Ns = Ns;
+ this.Process = Process;
+ this.Memory = Process.Memory;
+
+ SyncWaits = new ConcurrentDictionary<KThread, AutoResetEvent>();
+
+ MappedSharedMems = new HashSet<(HSharedMem, long)>();
+ }
+
+ static SvcHandler()
+ {
+ Rng = new Random();
+ }
+
+ public void SvcCall(object sender, AInstExceptionEventArgs e)
+ {
+ AThreadState ThreadState = (AThreadState)sender;
+
+ if (SvcFuncs.TryGetValue(e.Id, out SvcFunc Func))
+ {
+ Ns.Log.PrintDebug(LogClass.KernelSvc, $"{Func.Method.Name} called.");
+
+ Func(ThreadState);
+
+ Process.Scheduler.Reschedule(Process.GetThread(ThreadState.Tpidr));
+
+ Ns.Log.PrintDebug(LogClass.KernelSvc, $"{Func.Method.Name} ended.");
+ }
+ else
+ {
+ Process.PrintStackTrace(ThreadState);
+
+ throw new NotImplementedException(e.Id.ToString("x4"));
+ }
+ }
+
+ private KThread GetThread(long Tpidr, int Handle)
+ {
+ if ((uint)Handle == SelfThreadHandle)
+ {
+ return Process.GetThread(Tpidr);
+ }
+ else
+ {
+ return Process.HandleTable.GetData<KThread>(Handle);
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool Disposing)
+ {
+ if (Disposing)
+ {
+ lock (MappedSharedMems)
+ {
+ foreach ((HSharedMem SharedMem, long Position) in MappedSharedMems)
+ {
+ SharedMem.RemoveVirtualPosition(Memory, Position);
+ }
+
+ MappedSharedMems.Clear();
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/OsHle/Kernel/SvcMemory.cs b/Ryujinx.HLE/OsHle/Kernel/SvcMemory.cs
new file mode 100644
index 00000000..bb73f1ea
--- /dev/null
+++ b/Ryujinx.HLE/OsHle/Kernel/SvcMemory.cs
@@ -0,0 +1,285 @@
+using ChocolArm64.Memory;
+using ChocolArm64.State;
+using Ryujinx.HLE.Logging;
+using Ryujinx.HLE.OsHle.Handles;
+
+using static Ryujinx.HLE.OsHle.ErrorCode;
+
+namespace Ryujinx.HLE.OsHle.Kernel
+{
+ partial class SvcHandler
+ {
+ private void SvcSetHeapSize(AThreadState ThreadState)
+ {
+ uint Size = (uint)ThreadState.X1;
+
+ long Position = MemoryRegions.HeapRegionAddress;
+
+ if (Size > CurrentHeapSize)
+ {
+ Memory.Manager.Map(Position, Size, (int)MemoryType.Heap, AMemoryPerm.RW);
+ }
+ else
+ {
+ Memory.Manager.Unmap(Position + Size, (long)CurrentHeapSize - Size);
+ }
+
+ CurrentHeapSize = Size;
+
+ ThreadState.X0 = 0;
+ ThreadState.X1 = (ulong)Position;
+ }
+
+ private void SvcSetMemoryAttribute(AThreadState ThreadState)
+ {
+ long Position = (long)ThreadState.X0;
+ long Size = (long)ThreadState.X1;
+ int State0 = (int)ThreadState.X2;
+ int State1 = (int)ThreadState.X3;
+
+ if ((State0 == 0 && State1 == 0) ||
+ (State0 == 8 && State1 == 0))
+ {
+ Memory.Manager.ClearAttrBit(Position, Size, 3);
+ }
+ else if (State0 == 8 && State1 == 8)
+ {
+ Memory.Manager.SetAttrBit(Position, Size, 3);
+ }
+
+ ThreadState.X0 = 0;
+ }
+
+ private void SvcMapMemory(AThreadState ThreadState)
+ {
+ long Dst = (long)ThreadState.X0;
+ long Src = (long)ThreadState.X1;
+ long Size = (long)ThreadState.X2;
+
+ if (!IsValidPosition(Src))
+ {
+ Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid src address {Src:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange);
+
+ return;
+ }
+
+ if (!IsValidMapPosition(Dst))
+ {
+ Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid dst address {Dst:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange);
+
+ return;
+ }
+
+ AMemoryMapInfo SrcInfo = Memory.Manager.GetMapInfo(Src);
+
+ Memory.Manager.Map(Dst, Size, (int)MemoryType.MappedMemory, SrcInfo.Perm);
+
+ Memory.Manager.Reprotect(Src, Size, AMemoryPerm.None);
+
+ Memory.Manager.SetAttrBit(Src, Size, 0);
+
+ ThreadState.X0 = 0;
+ }
+
+ private void SvcUnmapMemory(AThreadState ThreadState)
+ {
+ long Dst = (long)ThreadState.X0;
+ long Src = (long)ThreadState.X1;
+ long Size = (long)ThreadState.X2;
+
+ if (!IsValidPosition(Src))
+ {
+ Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid src address {Src:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange);
+
+ return;
+ }
+
+ if (!IsValidMapPosition(Dst))
+ {
+ Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid dst address {Dst:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange);
+
+ return;
+ }
+
+ AMemoryMapInfo DstInfo = Memory.Manager.GetMapInfo(Dst);
+
+ Memory.Manager.Unmap(Dst, Size, (int)MemoryType.MappedMemory);
+
+ Memory.Manager.Reprotect(Src, Size, DstInfo.Perm);
+
+ Memory.Manager.ClearAttrBit(Src, Size, 0);
+
+ ThreadState.X0 = 0;
+ }
+
+ private void SvcQueryMemory(AThreadState ThreadState)
+ {
+ long InfoPtr = (long)ThreadState.X0;
+ long Position = (long)ThreadState.X2;
+
+ AMemoryMapInfo MapInfo = Memory.Manager.GetMapInfo(Position);
+
+ if (MapInfo == null)
+ {
+ long AddrSpaceEnd = MemoryRegions.AddrSpaceStart + MemoryRegions.AddrSpaceSize;
+
+ long ReservedSize = (long)(ulong.MaxValue - (ulong)AddrSpaceEnd) + 1;
+
+ MapInfo = new AMemoryMapInfo(AddrSpaceEnd, ReservedSize, (int)MemoryType.Reserved, 0, AMemoryPerm.None);
+ }
+
+ Memory.WriteInt64(InfoPtr + 0x00, MapInfo.Position);
+ Memory.WriteInt64(InfoPtr + 0x08, MapInfo.Size);
+ Memory.WriteInt32(InfoPtr + 0x10, MapInfo.Type);
+ Memory.WriteInt32(InfoPtr + 0x14, MapInfo.Attr);
+ Memory.WriteInt32(InfoPtr + 0x18, (int)MapInfo.Perm);
+ Memory.WriteInt32(InfoPtr + 0x1c, 0);
+ Memory.WriteInt32(InfoPtr + 0x20, 0);
+ Memory.WriteInt32(InfoPtr + 0x24, 0);
+ //TODO: X1.
+
+ ThreadState.X0 = 0;
+ ThreadState.X1 = 0;
+ }
+
+ private void SvcMapSharedMemory(AThreadState ThreadState)
+ {
+ int Handle = (int)ThreadState.X0;
+ long Src = (long)ThreadState.X1;
+ long Size = (long)ThreadState.X2;
+ int Perm = (int)ThreadState.X3;
+
+ if (!IsValidPosition(Src))
+ {
+ Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid address {Src:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange);
+
+ return;
+ }
+
+ HSharedMem SharedMem = Process.HandleTable.GetData<HSharedMem>(Handle);
+
+ if (SharedMem != null)
+ {
+ Memory.Manager.Map(Src, Size, (int)MemoryType.SharedMemory, AMemoryPerm.Write);
+
+ AMemoryHelper.FillWithZeros(Memory, Src, (int)Size);
+
+ Memory.Manager.Reprotect(Src, Size, (AMemoryPerm)Perm);
+
+ lock (MappedSharedMems)
+ {
+ MappedSharedMems.Add((SharedMem, Src));
+ }
+
+ SharedMem.AddVirtualPosition(Memory, Src);
+
+ ThreadState.X0 = 0;
+ }
+
+ //TODO: Error codes.
+ }
+
+ private void SvcUnmapSharedMemory(AThreadState ThreadState)
+ {
+ int Handle = (int)ThreadState.X0;
+ long Src = (long)ThreadState.X1;
+ long Size = (long)ThreadState.X2;
+
+ if (!IsValidPosition(Src))
+ {
+ Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid address {Src:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange);
+
+ return;
+ }
+
+ HSharedMem SharedMem = Process.HandleTable.GetData<HSharedMem>(Handle);
+
+ if (SharedMem != null)
+ {
+ Memory.Manager.Unmap(Src, Size, (int)MemoryType.SharedMemory);
+
+ SharedMem.RemoveVirtualPosition(Memory, Src);
+
+ lock (MappedSharedMems)
+ {
+ MappedSharedMems.Remove((SharedMem, Src));
+ }
+
+ ThreadState.X0 = 0;
+ }
+
+ //TODO: Error codes.
+ }
+
+ private void SvcCreateTransferMemory(AThreadState ThreadState)
+ {
+ long Src = (long)ThreadState.X1;
+ long Size = (long)ThreadState.X2;
+ int Perm = (int)ThreadState.X3;
+
+ if (!IsValidPosition(Src))
+ {
+ Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid address {Src:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange);
+
+ return;
+ }
+
+ AMemoryMapInfo MapInfo = Memory.Manager.GetMapInfo(Src);
+
+ Memory.Manager.Reprotect(Src, Size, (AMemoryPerm)Perm);
+
+ HTransferMem TMem = new HTransferMem(Memory, MapInfo.Perm, Src, Size);
+
+ ulong Handle = (ulong)Process.HandleTable.OpenHandle(TMem);
+
+ ThreadState.X0 = 0;
+ ThreadState.X1 = Handle;
+ }
+
+ private void SvcMapPhysicalMemory(AThreadState ThreadState)
+ {
+ long Position = (long)ThreadState.X0;
+ uint Size = (uint)ThreadState.X1;
+
+ Memory.Manager.Map(Position, Size, (int)MemoryType.Heap, AMemoryPerm.RW);
+
+ ThreadState.X0 = 0;
+ }
+
+ private void SvcUnmapPhysicalMemory(AThreadState ThreadState)
+ {
+ long Position = (long)ThreadState.X0;
+ uint Size = (uint)ThreadState.X1;
+
+ Memory.Manager.Unmap(Position, Size);
+
+ ThreadState.X0 = 0;
+ }
+
+ private static bool IsValidPosition(long Position)
+ {
+ return Position >= MemoryRegions.AddrSpaceStart &&
+ Position < MemoryRegions.AddrSpaceStart + MemoryRegions.AddrSpaceSize;
+ }
+
+ private static bool IsValidMapPosition(long Position)
+ {
+ return Position >= MemoryRegions.MapRegionAddress &&
+ Position < MemoryRegions.MapRegionAddress + MemoryRegions.MapRegionSize;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/OsHle/Kernel/SvcSystem.cs b/Ryujinx.HLE/OsHle/Kernel/SvcSystem.cs
new file mode 100644
index 00000000..a32b2d86
--- /dev/null
+++ b/Ryujinx.HLE/OsHle/Kernel/SvcSystem.cs
@@ -0,0 +1,369 @@
+using ChocolArm64.Memory;
+using ChocolArm64.State;
+using Ryujinx.HLE.Logging;
+using Ryujinx.HLE.OsHle.Exceptions;
+using Ryujinx.HLE.OsHle.Handles;
+using Ryujinx.HLE.OsHle.Ipc;
+using Ryujinx.HLE.OsHle.Services;
+using System;
+using System.Threading;
+
+using static Ryujinx.HLE.OsHle.ErrorCode;
+
+namespace Ryujinx.HLE.OsHle.Kernel
+{
+ partial class SvcHandler
+ {
+ private const int AllowedCpuIdBitmask = 0b1111;
+
+ private const bool EnableProcessDebugging = false;
+
+ private const bool IsVirtualMemoryEnabled = true; //This is always true(?)
+
+ private void SvcExitProcess(AThreadState ThreadState)
+ {
+ Ns.Os.ExitProcess(ThreadState.ProcessId);
+ }
+
+ private void SvcClearEvent(AThreadState ThreadState)
+ {
+ int Handle = (int)ThreadState.X0;
+
+ //TODO: Implement events.
+
+ ThreadState.X0 = 0;
+ }
+
+ private void SvcCloseHandle(AThreadState ThreadState)
+ {
+ int Handle = (int)ThreadState.X0;
+
+ object Obj = Process.HandleTable.CloseHandle(Handle);
+
+ if (Obj == null)
+ {
+ Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid handle 0x{Handle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+
+ return;
+ }
+
+ if (Obj is KSession Session)
+ {
+ Session.Dispose();
+ }
+ else if (Obj is HTransferMem TMem)
+ {
+ TMem.Memory.Manager.Reprotect(
+ TMem.Position,
+ TMem.Size,
+ TMem.Perm);
+ }
+
+ ThreadState.X0 = 0;
+ }
+
+ private void SvcResetSignal(AThreadState ThreadState)
+ {
+ int Handle = (int)ThreadState.X0;
+
+ KEvent Event = Process.HandleTable.GetData<KEvent>(Handle);
+
+ if (Event != null)
+ {
+ Event.WaitEvent.Reset();
+
+ ThreadState.X0 = 0;
+ }
+ else
+ {
+ Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid event handle 0x{Handle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+ }
+ }
+
+ private void SvcWaitSynchronization(AThreadState ThreadState)
+ {
+ long HandlesPtr = (long)ThreadState.X1;
+ int HandlesCount = (int)ThreadState.X2;
+ ulong Timeout = ThreadState.X3;
+
+ Ns.Log.PrintDebug(LogClass.KernelSvc,
+ "HandlesPtr = " + HandlesPtr .ToString("x16") + ", " +
+ "HandlesCount = " + HandlesCount.ToString("x8") + ", " +
+ "Timeout = " + 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)
+ {
+ Ns.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)
+ {
+ Ns.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;
+ }
+
+ private void SvcConnectToNamedPort(AThreadState ThreadState)
+ {
+ long StackPtr = (long)ThreadState.X0;
+ long NamePtr = (long)ThreadState.X1;
+
+ string Name = AMemoryHelper.ReadAsciiString(Memory, NamePtr, 8);
+
+ //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);
+
+ ulong Handle = (ulong)Process.HandleTable.OpenHandle(Session);
+
+ ThreadState.X0 = 0;
+ ThreadState.X1 = Handle;
+ }
+
+ private void SvcSendSyncRequest(AThreadState ThreadState)
+ {
+ SendSyncRequest(ThreadState, ThreadState.Tpidr, 0x100, (int)ThreadState.X0);
+ }
+
+ private void SvcSendSyncRequestWithUserBuffer(AThreadState ThreadState)
+ {
+ SendSyncRequest(
+ ThreadState,
+ (long)ThreadState.X0,
+ (long)ThreadState.X1,
+ (int)ThreadState.X2);
+ }
+
+ private void SendSyncRequest(AThreadState ThreadState, long CmdPtr, long Size, int Handle)
+ {
+ KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
+
+ byte[] CmdData = Memory.ReadBytes(CmdPtr, Size);
+
+ KSession Session = Process.HandleTable.GetData<KSession>(Handle);
+
+ if (Session != null)
+ {
+ Process.Scheduler.Suspend(CurrThread);
+
+ IpcMessage Cmd = new IpcMessage(CmdData, CmdPtr);
+
+ long Result = IpcHandler.IpcCall(Ns, Process, Memory, Session, Cmd, CmdPtr);
+
+ Thread.Yield();
+
+ Process.Scheduler.Resume(CurrThread);
+
+ ThreadState.X0 = (ulong)Result;
+ }
+ else
+ {
+ Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid session handle 0x{Handle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+ }
+ }
+
+ private void SvcBreak(AThreadState ThreadState)
+ {
+ long Reason = (long)ThreadState.X0;
+ long Unknown = (long)ThreadState.X1;
+ long Info = (long)ThreadState.X2;
+
+ Process.PrintStackTrace(ThreadState);
+
+ throw new GuestBrokeExecutionException();
+ }
+
+ private void SvcOutputDebugString(AThreadState ThreadState)
+ {
+ long Position = (long)ThreadState.X0;
+ long Size = (long)ThreadState.X1;
+
+ string Str = AMemoryHelper.ReadAsciiString(Memory, Position, Size);
+
+ Ns.Log.PrintWarning(LogClass.KernelSvc, Str);
+
+ ThreadState.X0 = 0;
+ }
+
+ private void SvcGetInfo(AThreadState ThreadState)
+ {
+ long StackPtr = (long)ThreadState.X0;
+ int InfoType = (int)ThreadState.X1;
+ long Handle = (long)ThreadState.X2;
+ int InfoId = (int)ThreadState.X3;
+
+ //Fail for info not available on older Kernel versions.
+ if (InfoType == 18 ||
+ InfoType == 19 ||
+ InfoType == 20)
+ {
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidInfo);
+
+ return;
+ }
+
+ switch (InfoType)
+ {
+ case 0:
+ ThreadState.X1 = AllowedCpuIdBitmask;
+ break;
+
+ case 2:
+ ThreadState.X1 = MemoryRegions.MapRegionAddress;
+ break;
+
+ case 3:
+ ThreadState.X1 = MemoryRegions.MapRegionSize;
+ break;
+
+ case 4:
+ ThreadState.X1 = MemoryRegions.HeapRegionAddress;
+ break;
+
+ case 5:
+ ThreadState.X1 = MemoryRegions.HeapRegionSize;
+ break;
+
+ case 6:
+ ThreadState.X1 = MemoryRegions.TotalMemoryAvailable;
+ break;
+
+ case 7:
+ ThreadState.X1 = MemoryRegions.TotalMemoryUsed + CurrentHeapSize;
+ break;
+
+ case 8:
+ ThreadState.X1 = EnableProcessDebugging ? 1 : 0;
+ break;
+
+ case 11:
+ ThreadState.X1 = (ulong)Rng.Next() + ((ulong)Rng.Next() << 32);
+ break;
+
+ case 12:
+ ThreadState.X1 = MemoryRegions.AddrSpaceStart;
+ break;
+
+ case 13:
+ ThreadState.X1 = MemoryRegions.AddrSpaceSize;
+ break;
+
+ case 14:
+ ThreadState.X1 = MemoryRegions.MapRegionAddress;
+ break;
+
+ case 15:
+ ThreadState.X1 = MemoryRegions.MapRegionSize;
+ break;
+
+ case 16:
+ ThreadState.X1 = IsVirtualMemoryEnabled ? 1 : 0;
+ break;
+
+ default:
+ Process.PrintStackTrace(ThreadState);
+
+ throw new NotImplementedException($"SvcGetInfo: {InfoType} {Handle:x8} {InfoId}");
+ }
+
+ ThreadState.X0 = 0;
+ }
+ }
+}
diff --git a/Ryujinx.HLE/OsHle/Kernel/SvcThread.cs b/Ryujinx.HLE/OsHle/Kernel/SvcThread.cs
new file mode 100644
index 00000000..9dcfaaee
--- /dev/null
+++ b/Ryujinx.HLE/OsHle/Kernel/SvcThread.cs
@@ -0,0 +1,291 @@
+using ChocolArm64.State;
+using Ryujinx.HLE.Logging;
+using Ryujinx.HLE.OsHle.Handles;
+using System.Threading;
+
+using static Ryujinx.HLE.OsHle.ErrorCode;
+
+namespace Ryujinx.HLE.OsHle.Kernel
+{
+ partial class SvcHandler
+ {
+ private void SvcCreateThread(AThreadState ThreadState)
+ {
+ long EntryPoint = (long)ThreadState.X1;
+ long ArgsPtr = (long)ThreadState.X2;
+ long StackTop = (long)ThreadState.X3;
+ int Priority = (int)ThreadState.X4;
+ int ProcessorId = (int)ThreadState.X5;
+
+ if ((uint)Priority > 0x3f)
+ {
+ Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid priority 0x{Priority:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidPriority);
+
+ return;
+ }
+
+ if (ProcessorId == -2)
+ {
+ //TODO: Get this value from the NPDM file.
+ ProcessorId = 0;
+ }
+ else if ((uint)ProcessorId > 3)
+ {
+ Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid core id 0x{ProcessorId:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidCoreId);
+
+ return;
+ }
+
+ int Handle = Process.MakeThread(
+ EntryPoint,
+ StackTop,
+ ArgsPtr,
+ Priority,
+ ProcessorId);
+
+ ThreadState.X0 = 0;
+ ThreadState.X1 = (ulong)Handle;
+ }
+
+ private void SvcStartThread(AThreadState ThreadState)
+ {
+ int Handle = (int)ThreadState.X0;
+
+ KThread NewThread = Process.HandleTable.GetData<KThread>(Handle);
+
+ if (NewThread != null)
+ {
+ Process.Scheduler.StartThread(NewThread);
+ Process.Scheduler.SetReschedule(NewThread.ProcessorId);
+
+ ThreadState.X0 = 0;
+ }
+ else
+ {
+ Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+ }
+ }
+
+ private void SvcExitThread(AThreadState ThreadState)
+ {
+ KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
+
+ CurrThread.Thread.StopExecution();
+ }
+
+ private void SvcSleepThread(AThreadState ThreadState)
+ {
+ ulong TimeoutNs = ThreadState.X0;
+
+ KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
+
+ if (TimeoutNs == 0)
+ {
+ Process.Scheduler.Yield(CurrThread);
+ }
+ else
+ {
+ Process.Scheduler.Suspend(CurrThread);
+
+ Thread.Sleep(NsTimeConverter.GetTimeMs(TimeoutNs));
+
+ Process.Scheduler.Resume(CurrThread);
+ }
+ }
+
+ private void SvcGetThreadPriority(AThreadState ThreadState)
+ {
+ int Handle = (int)ThreadState.X1;
+
+ KThread Thread = GetThread(ThreadState.Tpidr, Handle);
+
+ if (Thread != null)
+ {
+ ThreadState.X0 = 0;
+ ThreadState.X1 = (ulong)Thread.ActualPriority;
+ }
+ else
+ {
+ Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+ }
+ }
+
+ private void SvcSetThreadPriority(AThreadState ThreadState)
+ {
+ int Handle = (int)ThreadState.X0;
+ int Priority = (int)ThreadState.X1;
+
+ KThread Thread = GetThread(ThreadState.Tpidr, Handle);
+
+ if (Thread != null)
+ {
+ Thread.SetPriority(Priority);
+
+ ThreadState.X0 = 0;
+ }
+ else
+ {
+ Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+ }
+ }
+
+ private void SvcGetThreadCoreMask(AThreadState ThreadState)
+ {
+ int Handle = (int)ThreadState.X2;
+
+ Ns.Log.PrintDebug(LogClass.KernelSvc, "Handle = " + Handle.ToString("x8"));
+
+ KThread Thread = GetThread(ThreadState.Tpidr, Handle);
+
+ if (Thread != null)
+ {
+ ThreadState.X0 = 0;
+ ThreadState.X1 = (ulong)Thread.IdealCore;
+ ThreadState.X2 = (ulong)Thread.CoreMask;
+ }
+ else
+ {
+ Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+ }
+ }
+
+ private void SvcSetThreadCoreMask(AThreadState ThreadState)
+ {
+ //FIXME: This is wrong, but the "correct" way to handle
+ //this svc causes deadlocks when more often.
+ //There is probably something wrong with it still.
+ ThreadState.X0 = 0;
+
+ return;
+
+ int Handle = (int)ThreadState.X0;
+ int IdealCore = (int)ThreadState.X1;
+ long CoreMask = (long)ThreadState.X2;
+
+ Ns.Log.PrintDebug(LogClass.KernelSvc,
+ "Handle = " + Handle .ToString("x8") + ", " +
+ "IdealCore = " + IdealCore.ToString("x8") + ", " +
+ "CoreMask = " + CoreMask .ToString("x16"));
+
+ KThread Thread = GetThread(ThreadState.Tpidr, Handle);
+
+ if (IdealCore == -2)
+ {
+ //TODO: Get this value from the NPDM file.
+ IdealCore = 0;
+
+ CoreMask = 1 << IdealCore;
+ }
+ else if (IdealCore != -3)
+ {
+ if ((uint)IdealCore > 3)
+ {
+ if ((IdealCore | 2) != -1)
+ {
+ Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid core id 0x{IdealCore:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidCoreId);
+
+ return;
+ }
+ }
+ else if ((CoreMask & (1 << IdealCore)) == 0)
+ {
+ Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid core mask 0x{CoreMask:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidCoreMask);
+
+ return;
+ }
+ }
+
+ if (Thread == null)
+ {
+ Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle: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)
+ {
+ Thread.IdealCore = IdealCore;
+ }
+ else if ((CoreMask & (1 << Thread.IdealCore)) == 0)
+ {
+ Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid core mask 0x{CoreMask:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidCoreMask);
+
+ return;
+ }
+
+ Thread.CoreMask = (int)CoreMask;
+
+ Process.Scheduler.TryToRun(Thread);
+
+ ThreadState.X0 = 0;
+ }
+
+ private void SvcGetCurrentProcessorNumber(AThreadState ThreadState)
+ {
+ ThreadState.X0 = (ulong)Process.GetThread(ThreadState.Tpidr).ActualCore;
+ }
+
+ private void SvcGetThreadId(AThreadState ThreadState)
+ {
+ int Handle = (int)ThreadState.X1;
+
+ KThread Thread = GetThread(ThreadState.Tpidr, Handle);
+
+ if (Thread != null)
+ {
+ ThreadState.X0 = 0;
+ ThreadState.X1 = (ulong)Thread.ThreadId;
+ }
+ else
+ {
+ Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+ }
+ }
+
+ private void SvcSetThreadActivity(AThreadState ThreadState)
+ {
+ int Handle = (int)ThreadState.X0;
+ bool Active = (int)ThreadState.X1 == 0;
+
+ KThread Thread = Process.HandleTable.GetData<KThread>(Handle);
+
+ if (Thread != null)
+ {
+ Process.Scheduler.SetThreadActivity(Thread, Active);
+
+ ThreadState.X0 = 0;
+ }
+ else
+ {
+ Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/OsHle/Kernel/SvcThreadSync.cs b/Ryujinx.HLE/OsHle/Kernel/SvcThreadSync.cs
new file mode 100644
index 00000000..030e6e3d
--- /dev/null
+++ b/Ryujinx.HLE/OsHle/Kernel/SvcThreadSync.cs
@@ -0,0 +1,435 @@
+using ChocolArm64.State;
+using Ryujinx.HLE.Logging;
+using Ryujinx.HLE.OsHle.Handles;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+
+using static Ryujinx.HLE.OsHle.ErrorCode;
+
+namespace Ryujinx.HLE.OsHle.Kernel
+{
+ partial class SvcHandler
+ {
+ private const int MutexHasListenersMask = 0x40000000;
+
+ private void SvcArbitrateLock(AThreadState ThreadState)
+ {
+ int OwnerThreadHandle = (int)ThreadState.X0;
+ long MutexAddress = (long)ThreadState.X1;
+ int WaitThreadHandle = (int)ThreadState.X2;
+
+ Ns.Log.PrintDebug(LogClass.KernelSvc,
+ "OwnerThreadHandle = " + OwnerThreadHandle.ToString("x8") + ", " +
+ "MutexAddress = " + MutexAddress .ToString("x16") + ", " +
+ "WaitThreadHandle = " + WaitThreadHandle .ToString("x8"));
+
+ if (IsPointingInsideKernel(MutexAddress))
+ {
+ Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid mutex address 0x{MutexAddress:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
+
+ return;
+ }
+
+ if (IsWordAddressUnaligned(MutexAddress))
+ {
+ Ns.Log.PrintWarning(LogClass.KernelSvc, $"Unaligned mutex address 0x{MutexAddress:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAlignment);
+
+ return;
+ }
+
+ KThread OwnerThread = Process.HandleTable.GetData<KThread>(OwnerThreadHandle);
+
+ if (OwnerThread == null)
+ {
+ Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid owner thread handle 0x{OwnerThreadHandle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+
+ return;
+ }
+
+ KThread WaitThread = Process.HandleTable.GetData<KThread>(WaitThreadHandle);
+
+ if (WaitThread == null)
+ {
+ Ns.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;
+ }
+
+ private void SvcArbitrateUnlock(AThreadState ThreadState)
+ {
+ long MutexAddress = (long)ThreadState.X0;
+
+ Ns.Log.PrintDebug(LogClass.KernelSvc, "MutexAddress = " + MutexAddress.ToString("x16"));
+
+ if (IsPointingInsideKernel(MutexAddress))
+ {
+ Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid mutex address 0x{MutexAddress:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
+
+ return;
+ }
+
+ if (IsWordAddressUnaligned(MutexAddress))
+ {
+ Ns.Log.PrintWarning(LogClass.KernelSvc, $"Unaligned mutex address 0x{MutexAddress:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAlignment);
+
+ return;
+ }
+
+ MutexUnlock(Process.GetThread(ThreadState.Tpidr), MutexAddress);
+
+ ThreadState.X0 = 0;
+ }
+
+ private void SvcWaitProcessWideKeyAtomic(AThreadState ThreadState)
+ {
+ long MutexAddress = (long)ThreadState.X0;
+ long CondVarAddress = (long)ThreadState.X1;
+ int ThreadHandle = (int)ThreadState.X2;
+ ulong Timeout = ThreadState.X3;
+
+ Ns.Log.PrintDebug(LogClass.KernelSvc,
+ "MutexAddress = " + MutexAddress .ToString("x16") + ", " +
+ "CondVarAddress = " + CondVarAddress.ToString("x16") + ", " +
+ "ThreadHandle = " + ThreadHandle .ToString("x8") + ", " +
+ "Timeout = " + Timeout .ToString("x16"));
+
+ if (IsPointingInsideKernel(MutexAddress))
+ {
+ Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid mutex address 0x{MutexAddress:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
+
+ return;
+ }
+
+ if (IsWordAddressUnaligned(MutexAddress))
+ {
+ Ns.Log.PrintWarning(LogClass.KernelSvc, $"Unaligned mutex address 0x{MutexAddress:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAlignment);
+
+ return;
+ }
+
+ KThread Thread = Process.HandleTable.GetData<KThread>(ThreadHandle);
+
+ if (Thread == null)
+ {
+ Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{ThreadHandle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+
+ return;
+ }
+
+ KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
+
+ MutexUnlock(CurrThread, MutexAddress);
+
+ if (!CondVarWait(CurrThread, ThreadHandle, MutexAddress, CondVarAddress, Timeout))
+ {
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.Timeout);
+
+ return;
+ }
+
+ ThreadState.X0 = 0;
+ }
+
+ private void SvcSignalProcessWideKey(AThreadState ThreadState)
+ {
+ long CondVarAddress = (long)ThreadState.X0;
+ int Count = (int)ThreadState.X1;
+
+ Ns.Log.PrintDebug(LogClass.KernelSvc,
+ "CondVarAddress = " + CondVarAddress.ToString("x16") + ", " +
+ "Count = " + Count .ToString("x8"));
+
+ KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
+
+ CondVarSignal(CurrThread, CondVarAddress, Count);
+
+ ThreadState.X0 = 0;
+ }
+
+ private void MutexLock(
+ KThread CurrThread,
+ KThread WaitThread,
+ int OwnerThreadHandle,
+ int WaitThreadHandle,
+ long MutexAddress)
+ {
+ lock (Process.ThreadSyncLock)
+ {
+ int MutexValue = Process.Memory.ReadInt32(MutexAddress);
+
+ Ns.Log.PrintDebug(LogClass.KernelSvc, "MutexValue = " + MutexValue.ToString("x8"));
+
+ if (MutexValue != (OwnerThreadHandle | MutexHasListenersMask))
+ {
+ return;
+ }
+
+ CurrThread.WaitHandle = WaitThreadHandle;
+ CurrThread.MutexAddress = MutexAddress;
+
+ InsertWaitingMutexThread(OwnerThreadHandle, WaitThread);
+ }
+
+ Ns.Log.PrintDebug(LogClass.KernelSvc, "Entering wait state...");
+
+ Process.Scheduler.EnterWait(CurrThread);
+ }
+
+ private void MutexUnlock(KThread CurrThread, long MutexAddress)
+ {
+ lock (Process.ThreadSyncLock)
+ {
+ //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 = PopThread(CurrThread.MutexWaiters, x => x.MutexAddress == MutexAddress);
+
+ if (OwnerThread != null)
+ {
+ //Remove all waiting mutex from the old owner,
+ //and insert then on the new owner.
+ UpdateMutexOwner(CurrThread, OwnerThread, MutexAddress);
+
+ CurrThread.UpdatePriority();
+
+ int HasListeners = OwnerThread.MutexWaiters.Count > 0 ? MutexHasListenersMask : 0;
+
+ Process.Memory.WriteInt32(MutexAddress, HasListeners | OwnerThread.WaitHandle);
+
+ OwnerThread.WaitHandle = 0;
+ OwnerThread.MutexAddress = 0;
+ OwnerThread.CondVarAddress = 0;
+
+ OwnerThread.MutexOwner = null;
+
+ OwnerThread.UpdatePriority();
+
+ Process.Scheduler.WakeUp(OwnerThread);
+
+ Ns.Log.PrintDebug(LogClass.KernelSvc, "Gave mutex to thread id " + OwnerThread.ThreadId + "!");
+ }
+ else
+ {
+ Process.Memory.WriteInt32(MutexAddress, 0);
+
+ Ns.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)
+ {
+ WaitThread.CondVarSignaled = false;
+
+ Process.ThreadArbiterList.Add(WaitThread);
+ }
+
+ Ns.Log.PrintDebug(LogClass.KernelSvc, "Entering wait state...");
+
+ if (Timeout != ulong.MaxValue)
+ {
+ Process.Scheduler.EnterWait(WaitThread, NsTimeConverter.GetTimeMs(Timeout));
+
+ lock (Process.ThreadSyncLock)
+ {
+ WaitThread.MutexOwner?.MutexWaiters.Remove(WaitThread);
+
+ if (!WaitThread.CondVarSignaled || WaitThread.MutexOwner != null)
+ {
+ WaitThread.MutexOwner = null;
+
+ Process.ThreadArbiterList.Remove(WaitThread);
+
+ Ns.Log.PrintDebug(LogClass.KernelSvc, "Timed out...");
+
+ return false;
+ }
+ }
+ }
+ else
+ {
+ Process.Scheduler.EnterWait(WaitThread);
+ }
+
+ return true;
+ }
+
+ private void CondVarSignal(KThread CurrThread, long CondVarAddress, int Count)
+ {
+ lock (Process.ThreadSyncLock)
+ {
+ while (Count == -1 || Count-- > 0)
+ {
+ KThread WaitThread = PopThread(Process.ThreadArbiterList, x => x.CondVarAddress == CondVarAddress);
+
+ if (WaitThread == null)
+ {
+ Ns.Log.PrintDebug(LogClass.KernelSvc, "No more threads to wake up!");
+
+ break;
+ }
+
+ WaitThread.CondVarSignaled = true;
+
+ AcquireMutexValue(WaitThread.MutexAddress);
+
+ int MutexValue = Process.Memory.ReadInt32(WaitThread.MutexAddress);
+
+ Ns.Log.PrintDebug(LogClass.KernelSvc, "MutexValue = " + MutexValue.ToString("x8"));
+
+ if (MutexValue == 0)
+ {
+ //Give the lock to this thread.
+ Process.Memory.WriteInt32(WaitThread.MutexAddress, WaitThread.WaitHandle);
+
+ WaitThread.WaitHandle = 0;
+ WaitThread.MutexAddress = 0;
+ WaitThread.CondVarAddress = 0;
+
+ WaitThread.MutexOwner?.UpdatePriority();
+
+ WaitThread.MutexOwner = null;
+
+ Process.Scheduler.WakeUp(WaitThread);
+ }
+ else
+ {
+ //Wait until the lock is released.
+ MutexValue &= ~MutexHasListenersMask;
+
+ InsertWaitingMutexThread(MutexValue, WaitThread);
+
+ MutexValue |= MutexHasListenersMask;
+
+ Process.Memory.WriteInt32(WaitThread.MutexAddress, MutexValue);
+ }
+
+ ReleaseMutexValue(WaitThread.MutexAddress);
+ }
+ }
+ }
+
+ private void UpdateMutexOwner(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.
+ lock (Process.ThreadSyncLock)
+ {
+ for (int Index = 0; Index < CurrThread.MutexWaiters.Count; Index++)
+ {
+ KThread Thread = CurrThread.MutexWaiters[Index];
+
+ if (Thread.MutexAddress == MutexAddress)
+ {
+ CurrThread.MutexWaiters.RemoveAt(Index--);
+
+ Thread.MutexOwner = NewOwner;
+
+ InsertWaitingMutexThread(NewOwner, Thread);
+ }
+ }
+ }
+ }
+
+ private void InsertWaitingMutexThread(int OwnerThreadHandle, KThread WaitThread)
+ {
+ KThread OwnerThread = Process.HandleTable.GetData<KThread>(OwnerThreadHandle);
+
+ if (OwnerThread == null)
+ {
+ Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{OwnerThreadHandle:x8}!");
+
+ return;
+ }
+
+ InsertWaitingMutexThread(OwnerThread, WaitThread);
+ }
+
+ private void InsertWaitingMutexThread(KThread OwnerThread, KThread WaitThread)
+ {
+ lock (Process.ThreadSyncLock)
+ {
+ WaitThread.MutexOwner = OwnerThread;
+
+ if (!OwnerThread.MutexWaiters.Contains(WaitThread))
+ {
+ OwnerThread.MutexWaiters.Add(WaitThread);
+
+ OwnerThread.UpdatePriority();
+ }
+ }
+ }
+
+ private KThread PopThread(List<KThread> Threads, Func<KThread, bool> Predicate)
+ {
+ KThread Thread = Threads.OrderBy(x => x.ActualPriority).FirstOrDefault(Predicate);
+
+ if (Thread != null)
+ {
+ Threads.Remove(Thread);
+ }
+
+ return Thread;
+ }
+
+ private void AcquireMutexValue(long MutexAddress)
+ {
+ while (!Process.Memory.AcquireAddress(MutexAddress))
+ {
+ Thread.Yield();
+ }
+ }
+
+ private void ReleaseMutexValue(long MutexAddress)
+ {
+ Process.Memory.ReleaseAddress(MutexAddress);
+ }
+
+ private bool IsPointingInsideKernel(long Address)
+ {
+ return ((ulong)Address + 0x1000000000) < 0xffffff000;
+ }
+
+ private bool IsWordAddressUnaligned(long Address)
+ {
+ return (Address & 3) != 0;
+ }
+ }
+} \ No newline at end of file