aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Core/OsHle/Kernel
diff options
context:
space:
mode:
authorgdkchan <gab.dark.100@gmail.com>2018-04-18 23:52:23 -0300
committergdkchan <gab.dark.100@gmail.com>2018-04-18 23:52:36 -0300
commitb9af34f3dd1e7f5e38b038f182c9fd4a791fdfea (patch)
treee86e7941f5d2871ed2d9bd702b38997c86fb2af8 /Ryujinx.Core/OsHle/Kernel
parente9a96e3522ee7620b525d210915a0e45510ea528 (diff)
[HLE/Kernel] Somewhat improved sync primitives
Diffstat (limited to 'Ryujinx.Core/OsHle/Kernel')
-rw-r--r--Ryujinx.Core/OsHle/Kernel/ConditionVariable.cs139
-rw-r--r--Ryujinx.Core/OsHle/Kernel/KernelErr.cs10
-rw-r--r--Ryujinx.Core/OsHle/Kernel/MutualExclusion.cs91
-rw-r--r--Ryujinx.Core/OsHle/Kernel/SvcHandler.cs123
-rw-r--r--Ryujinx.Core/OsHle/Kernel/SvcMemory.cs264
-rw-r--r--Ryujinx.Core/OsHle/Kernel/SvcSystem.cs306
-rw-r--r--Ryujinx.Core/OsHle/Kernel/SvcThread.cs156
-rw-r--r--Ryujinx.Core/OsHle/Kernel/SvcThreadSync.cs120
8 files changed, 1209 insertions, 0 deletions
diff --git a/Ryujinx.Core/OsHle/Kernel/ConditionVariable.cs b/Ryujinx.Core/OsHle/Kernel/ConditionVariable.cs
new file mode 100644
index 00000000..34d5820b
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Kernel/ConditionVariable.cs
@@ -0,0 +1,139 @@
+using Ryujinx.Core.OsHle.Handles;
+using System.Collections.Generic;
+using System.Threading;
+
+namespace Ryujinx.Core.OsHle.Kernel
+{
+ class ConditionVariable
+ {
+ private Process Process;
+
+ private long CondVarAddress;
+
+ private bool OwnsCondVarValue;
+
+ private List<(KThread Thread, AutoResetEvent WaitEvent)> WaitingThreads;
+
+ public ConditionVariable(Process Process, long CondVarAddress)
+ {
+ this.Process = Process;
+ this.CondVarAddress = CondVarAddress;
+
+ WaitingThreads = new List<(KThread, AutoResetEvent)>();
+ }
+
+ public bool WaitForSignal(KThread Thread, long Timeout)
+ {
+ bool Result = true;
+
+ int Count = Process.Memory.ReadInt32(CondVarAddress);
+
+ if (Count <= 0)
+ {
+ using (AutoResetEvent WaitEvent = new AutoResetEvent(false))
+ {
+ lock (WaitingThreads)
+ {
+ WaitingThreads.Add((Thread, WaitEvent));
+ }
+
+ Process.Scheduler.Suspend(Thread.ProcessorId);
+
+ if (Timeout < 0)
+ {
+ Result = WaitEvent.WaitOne();
+ }
+ else
+ {
+ Result = WaitEvent.WaitOne((int)(Timeout / 1000000));
+
+ lock (WaitingThreads)
+ {
+ WaitingThreads.Remove((Thread, WaitEvent));
+ }
+ }
+
+ Process.Scheduler.Resume(Thread);
+ }
+ }
+
+ AcquireCondVarValue();
+
+ Count = Process.Memory.ReadInt32(CondVarAddress);
+
+ if (Count > 0)
+ {
+ Process.Memory.WriteInt32(CondVarAddress, Count - 1);
+ }
+
+ ReleaseCondVarValue();
+
+ return Result;
+ }
+
+ public void SetSignal(KThread Thread, int Count)
+ {
+ lock (WaitingThreads)
+ {
+ if (Count < 0)
+ {
+ Process.Memory.WriteInt32(CondVarAddress, WaitingThreads.Count);
+
+ foreach ((_, AutoResetEvent WaitEvent) in WaitingThreads)
+ {
+ WaitEvent.Set();
+ }
+
+ WaitingThreads.Clear();
+ }
+ else
+ {
+ Process.Memory.WriteInt32(CondVarAddress, Count);
+
+ while (WaitingThreads.Count > 0 && Count-- > 0)
+ {
+ int HighestPriority = WaitingThreads[0].Thread.Priority;
+ int HighestPrioIndex = 0;
+
+ for (int Index = 1; Index < WaitingThreads.Count; Index++)
+ {
+ if (HighestPriority > WaitingThreads[Index].Thread.Priority)
+ {
+ HighestPriority = WaitingThreads[Index].Thread.Priority;
+
+ HighestPrioIndex = Index;
+ }
+ }
+
+ WaitingThreads[HighestPrioIndex].WaitEvent.Set();
+
+ WaitingThreads.RemoveAt(HighestPrioIndex);
+ }
+ }
+ }
+ }
+
+ private void AcquireCondVarValue()
+ {
+ if (!OwnsCondVarValue)
+ {
+ while (!Process.Memory.AcquireAddress(CondVarAddress))
+ {
+ Thread.Yield();
+ }
+
+ OwnsCondVarValue = true;
+ }
+ }
+
+ private void ReleaseCondVarValue()
+ {
+ if (OwnsCondVarValue)
+ {
+ OwnsCondVarValue = false;
+
+ Process.Memory.ReleaseAddress(CondVarAddress);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Kernel/KernelErr.cs b/Ryujinx.Core/OsHle/Kernel/KernelErr.cs
new file mode 100644
index 00000000..e7cd72dc
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Kernel/KernelErr.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.Core.OsHle.Kernel
+{
+ static class KernelErr
+ {
+ public const int InvalidMemRange = 110;
+ public const int InvalidHandle = 114;
+ public const int Timeout = 117;
+ public const int InvalidInfo = 120;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Kernel/MutualExclusion.cs b/Ryujinx.Core/OsHle/Kernel/MutualExclusion.cs
new file mode 100644
index 00000000..aeaaf70f
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Kernel/MutualExclusion.cs
@@ -0,0 +1,91 @@
+using Ryujinx.Core.OsHle.Handles;
+using System.Collections.Generic;
+using System.Threading;
+
+namespace Ryujinx.Core.OsHle.Kernel
+{
+ class MutualExclusion
+ {
+ private const int MutexHasListenersMask = 0x40000000;
+
+ private Process Process;
+
+ private long MutexAddress;
+
+ private List<(KThread Thread, AutoResetEvent WaitEvent)> WaitingThreads;
+
+ public MutualExclusion(Process Process, long MutexAddress)
+ {
+ this.Process = Process;
+ this.MutexAddress = MutexAddress;
+
+ WaitingThreads = new List<(KThread, AutoResetEvent)>();
+ }
+
+ public void WaitForLock(KThread RequestingThread)
+ {
+ int OwnerThreadHandle = Process.Memory.ReadInt32(MutexAddress) & ~MutexHasListenersMask;
+
+ WaitForLock(RequestingThread, OwnerThreadHandle);
+ }
+
+ public void WaitForLock(KThread RequestingThread, int OwnerThreadHandle)
+ {
+ if (OwnerThreadHandle == RequestingThread.Handle ||
+ OwnerThreadHandle == 0)
+ {
+ return;
+ }
+
+ using (AutoResetEvent WaitEvent = new AutoResetEvent(false))
+ {
+ lock (WaitingThreads)
+ {
+ WaitingThreads.Add((RequestingThread, WaitEvent));
+ }
+
+ Process.Scheduler.Suspend(RequestingThread.ProcessorId);
+
+ WaitEvent.WaitOne();
+
+ Process.Scheduler.Resume(RequestingThread);
+ }
+ }
+
+ public void Unlock()
+ {
+ lock (WaitingThreads)
+ {
+ int HasListeners = WaitingThreads.Count > 1 ? MutexHasListenersMask : 0;
+
+ if (WaitingThreads.Count > 0)
+ {
+ int HighestPriority = WaitingThreads[0].Thread.Priority;
+ int HighestPrioIndex = 0;
+
+ for (int Index = 1; Index < WaitingThreads.Count; Index++)
+ {
+ if (HighestPriority > WaitingThreads[Index].Thread.Priority)
+ {
+ HighestPriority = WaitingThreads[Index].Thread.Priority;
+
+ HighestPrioIndex = Index;
+ }
+ }
+
+ int Handle = WaitingThreads[HighestPrioIndex].Thread.Handle;
+
+ WaitingThreads[HighestPrioIndex].WaitEvent.Set();
+
+ WaitingThreads.RemoveAt(HighestPrioIndex);
+
+ Process.Memory.WriteInt32(MutexAddress, HasListeners | Handle);
+ }
+ else
+ {
+ Process.Memory.WriteInt32(MutexAddress, 0);
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Kernel/SvcHandler.cs b/Ryujinx.Core/OsHle/Kernel/SvcHandler.cs
new file mode 100644
index 00000000..4d93ef29
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Kernel/SvcHandler.cs
@@ -0,0 +1,123 @@
+using ChocolArm64.Events;
+using ChocolArm64.Memory;
+using ChocolArm64.State;
+using Ryujinx.Core.OsHle.Handles;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+
+namespace Ryujinx.Core.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<long, MutualExclusion> Mutexes;
+ private ConcurrentDictionary<long, ConditionVariable> CondVars;
+
+ private HashSet<(HSharedMem, long)> MappedSharedMems;
+
+ private ulong CurrentHeapSize;
+
+ 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 },
+ { 0x0f, SvcSetThreadCoreMask },
+ { 0x10, SvcGetCurrentProcessorNumber },
+ { 0x12, SvcClearEvent },
+ { 0x13, SvcMapSharedMemory },
+ { 0x14, SvcUnmapSharedMemory },
+ { 0x15, SvcCreateTransferMemory },
+ { 0x16, SvcCloseHandle },
+ { 0x17, SvcResetSignal },
+ { 0x18, SvcWaitSynchronization },
+ { 0x1a, SvcArbitrateLock },
+ { 0x1b, SvcArbitrateUnlock },
+ { 0x1c, SvcWaitProcessWideKeyAtomic },
+ { 0x1d, SvcSignalProcessWideKey },
+ { 0x1e, SvcGetSystemTick },
+ { 0x1f, SvcConnectToNamedPort },
+ { 0x21, SvcSendSyncRequest },
+ { 0x22, SvcSendSyncRequestWithUserBuffer },
+ { 0x25, SvcGetThreadId },
+ { 0x26, SvcBreak },
+ { 0x27, SvcOutputDebugString },
+ { 0x29, SvcGetInfo }
+ };
+
+ this.Ns = Ns;
+ this.Process = Process;
+ this.Memory = Process.Memory;
+
+ Mutexes = new ConcurrentDictionary<long, MutualExclusion>();
+ CondVars = new ConcurrentDictionary<long, ConditionVariable>();
+
+ 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))
+ {
+ Logging.Trace(LogClass.KernelSvc, $"(Thread {ThreadState.ThreadId}) {Func.Method.Name} called.");
+
+ Func(ThreadState);
+
+ Logging.Trace(LogClass.KernelSvc, $"(Thread {ThreadState.ThreadId}) {Func.Method.Name} ended.");
+ }
+ else
+ {
+ throw new NotImplementedException(e.Id.ToString("x4"));
+ }
+ }
+
+ 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.Core/OsHle/Kernel/SvcMemory.cs b/Ryujinx.Core/OsHle/Kernel/SvcMemory.cs
new file mode 100644
index 00000000..c8aedcff
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Kernel/SvcMemory.cs
@@ -0,0 +1,264 @@
+using ChocolArm64.Memory;
+using ChocolArm64.State;
+using Ryujinx.Core.OsHle.Handles;
+
+using static Ryujinx.Core.OsHle.ErrorCode;
+
+namespace Ryujinx.Core.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))
+ {
+ Logging.Warn(LogClass.KernelSvc, $"Tried to map Memory at invalid src address {Src:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange);
+
+ return;
+ }
+
+ if (!IsValidMapPosition(Dst))
+ {
+ Logging.Warn(LogClass.KernelSvc, $"Tried to map Memory at 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))
+ {
+ Logging.Warn(LogClass.KernelSvc, $"Tried to unmap Memory at invalid src address {Src:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange);
+
+ return;
+ }
+
+ if (!IsValidMapPosition(Dst))
+ {
+ Logging.Warn(LogClass.KernelSvc, $"Tried to unmap Memory at 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))
+ {
+ Logging.Warn(LogClass.KernelSvc, $"Tried to map SharedMemory at 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))
+ {
+ Logging.Warn(LogClass.KernelSvc, $"Tried to unmap SharedMemory at 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))
+ {
+ Logging.Warn(LogClass.KernelSvc, $"Tried to create TransferMemory at 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 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.Core/OsHle/Kernel/SvcSystem.cs b/Ryujinx.Core/OsHle/Kernel/SvcSystem.cs
new file mode 100644
index 00000000..ebbbef4a
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Kernel/SvcSystem.cs
@@ -0,0 +1,306 @@
+using ChocolArm64.Memory;
+using ChocolArm64.State;
+using Ryujinx.Core.OsHle.Exceptions;
+using Ryujinx.Core.OsHle.Handles;
+using Ryujinx.Core.OsHle.Ipc;
+using Ryujinx.Core.OsHle.Services;
+using System;
+using System.Threading;
+
+using static Ryujinx.Core.OsHle.ErrorCode;
+
+namespace Ryujinx.Core.OsHle.Kernel
+{
+ partial class SvcHandler
+ {
+ private const int AllowedCpuIdBitmask = 0b1111;
+
+ private const bool EnableProcessDebugging = false;
+
+ 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)
+ {
+ Logging.Warn(LogClass.KernelSvc, $"Tried to CloseHandle on 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
+ {
+ Logging.Warn(LogClass.KernelSvc, $"Tried to ResetSignal on 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;
+ long Timeout = (long)ThreadState.X3;
+
+ KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
+
+ WaitHandle[] Handles = new WaitHandle[HandlesCount];
+
+ for (int Index = 0; Index < HandlesCount; Index++)
+ {
+ int Handle = Memory.ReadInt32(HandlesPtr + Index * 4);
+
+ KSynchronizationObject SyncObj = Process.HandleTable.GetData<KSynchronizationObject>(Handle);
+
+ if (SyncObj == null)
+ {
+ Logging.Warn(LogClass.KernelSvc, $"Tried to WaitSynchronization on invalid handle 0x{Handle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+
+ return;
+ }
+
+ Handles[Index] = SyncObj.WaitEvent;
+ }
+
+ Process.Scheduler.Suspend(CurrThread.ProcessorId);
+
+ int HandleIndex;
+
+ ulong Result = 0;
+
+ if (Timeout != -1)
+ {
+ HandleIndex = WaitHandle.WaitAny(Handles, (int)(Timeout / 1000000));
+
+ if (HandleIndex == WaitHandle.WaitTimeout)
+ {
+ Result = MakeError(ErrorModule.Kernel, KernelErr.Timeout);
+ }
+ }
+ else
+ {
+ HandleIndex = WaitHandle.WaitAny(Handles);
+ }
+
+ Process.Scheduler.Resume(CurrThread);
+
+ ThreadState.X0 = Result;
+
+ if (Result == 0)
+ {
+ ThreadState.X1 = (ulong)HandleIndex;
+ }
+ }
+
+ 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 = AMemoryHelper.ReadBytes(Memory, CmdPtr, Size);
+
+ KSession Session = Process.HandleTable.GetData<KSession>(Handle);
+
+ if (Session != null)
+ {
+ Process.Scheduler.Suspend(CurrThread.ProcessorId);
+
+ IpcMessage Cmd = new IpcMessage(CmdData, CmdPtr);
+
+ IpcHandler.IpcCall(Ns, Process, Memory, Session, Cmd, CmdPtr);
+
+ Thread.Yield();
+
+ Process.Scheduler.Resume(CurrThread);
+
+ ThreadState.X0 = 0;
+ }
+ else
+ {
+ Logging.Warn(LogClass.KernelSvc, $"Tried to SendSyncRequest on 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;
+
+ 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);
+
+ Logging.Info(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;
+
+ default: throw new NotImplementedException($"SvcGetInfo: {InfoType} {Handle} {InfoId}");
+ }
+
+ ThreadState.X0 = 0;
+ }
+ }
+}
diff --git a/Ryujinx.Core/OsHle/Kernel/SvcThread.cs b/Ryujinx.Core/OsHle/Kernel/SvcThread.cs
new file mode 100644
index 00000000..7418732f
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Kernel/SvcThread.cs
@@ -0,0 +1,156 @@
+using ChocolArm64.State;
+using Ryujinx.Core.OsHle.Handles;
+using System.Threading;
+
+using static Ryujinx.Core.OsHle.ErrorCode;
+
+namespace Ryujinx.Core.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 (ProcessorId == -2)
+ {
+ //TODO: Get this value from the NPDM file.
+ ProcessorId = 0;
+ }
+
+ 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 CurrThread = Process.HandleTable.GetData<KThread>(Handle);
+
+ if (CurrThread != null)
+ {
+ Process.Scheduler.StartThread(CurrThread);
+
+ Process.Scheduler.Yield(Process.GetThread(ThreadState.Tpidr));
+
+ ThreadState.X0 = 0;
+ }
+ else
+ {
+ Logging.Warn(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 NanoSecs = ThreadState.X0;
+
+ KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
+
+ if (NanoSecs == 0)
+ {
+ Process.Scheduler.Yield(CurrThread);
+ }
+ else
+ {
+ Process.Scheduler.Suspend(CurrThread.ProcessorId);
+
+ Thread.Sleep((int)(NanoSecs / 1000000));
+
+ Process.Scheduler.Resume(CurrThread);
+ }
+ }
+
+ private void SvcGetThreadPriority(AThreadState ThreadState)
+ {
+ int Handle = (int)ThreadState.X1;
+
+ KThread CurrThread = Process.HandleTable.GetData<KThread>(Handle);
+
+ if (CurrThread != null)
+ {
+ ThreadState.X0 = 0;
+ ThreadState.X1 = (ulong)CurrThread.Priority;
+ }
+ else
+ {
+ Logging.Warn(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+ }
+ }
+
+ private void SvcSetThreadPriority(AThreadState ThreadState)
+ {
+ int Prio = (int)ThreadState.X0;
+ int Handle = (int)ThreadState.X1;
+
+ KThread CurrThread = Process.HandleTable.GetData<KThread>(Handle);
+
+ if (CurrThread != null)
+ {
+ CurrThread.Priority = Prio;
+
+ ThreadState.X0 = 0;
+ }
+ else
+ {
+ Logging.Warn(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+ }
+ }
+
+ private void SvcSetThreadCoreMask(AThreadState ThreadState)
+ {
+ ThreadState.X0 = 0;
+
+ //TODO: Error codes.
+ }
+
+ private void SvcGetCurrentProcessorNumber(AThreadState ThreadState)
+ {
+ ThreadState.X0 = (ulong)Process.GetThread(ThreadState.Tpidr).ProcessorId;
+ }
+
+ private void SvcGetThreadId(AThreadState ThreadState)
+ {
+ int Handle = (int)ThreadState.X1;
+
+ KThread CurrThread = Process.HandleTable.GetData<KThread>(Handle);
+
+ if (CurrThread != null)
+ {
+ ThreadState.X0 = 0;
+ ThreadState.X1 = (ulong)CurrThread.ThreadId;
+ }
+ else
+ {
+ Logging.Warn(LogClass.KernelSvc, $"Tried to GetThreadId on invalid thread handle 0x{Handle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Kernel/SvcThreadSync.cs b/Ryujinx.Core/OsHle/Kernel/SvcThreadSync.cs
new file mode 100644
index 00000000..e9d801b4
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Kernel/SvcThreadSync.cs
@@ -0,0 +1,120 @@
+using ChocolArm64.State;
+using Ryujinx.Core.OsHle.Handles;
+
+using static Ryujinx.Core.OsHle.ErrorCode;
+
+namespace Ryujinx.Core.OsHle.Kernel
+{
+ partial class SvcHandler
+ {
+ private void SvcArbitrateLock(AThreadState ThreadState)
+ {
+ int OwnerThreadHandle = (int)ThreadState.X0;
+ long MutexAddress = (long)ThreadState.X1;
+ int RequestingThreadHandle = (int)ThreadState.X2;
+
+ KThread OwnerThread = Process.HandleTable.GetData<KThread>(OwnerThreadHandle);
+
+ if (OwnerThread == null)
+ {
+ Logging.Warn(LogClass.KernelSvc, $"Invalid owner thread handle 0x{OwnerThreadHandle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+
+ return;
+ }
+
+ KThread RequestingThread = Process.HandleTable.GetData<KThread>(RequestingThreadHandle);
+
+ if (RequestingThread == null)
+ {
+ Logging.Warn(LogClass.KernelSvc, $"Invalid requesting thread handle 0x{RequestingThreadHandle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+
+ return;
+ }
+
+ MutualExclusion Mutex = GetMutex(MutexAddress);
+
+ Mutex.WaitForLock(RequestingThread, OwnerThreadHandle);
+
+ ThreadState.X0 = 0;
+ }
+
+ private void SvcArbitrateUnlock(AThreadState ThreadState)
+ {
+ long MutexAddress = (long)ThreadState.X0;
+
+ GetMutex(MutexAddress).Unlock();
+
+ Process.Scheduler.Yield(Process.GetThread(ThreadState.Tpidr));
+
+ ThreadState.X0 = 0;
+ }
+
+ private void SvcWaitProcessWideKeyAtomic(AThreadState ThreadState)
+ {
+ long MutexAddress = (long)ThreadState.X0;
+ long CondVarAddress = (long)ThreadState.X1;
+ int ThreadHandle = (int)ThreadState.X2;
+ long Timeout = (long)ThreadState.X3;
+
+ KThread Thread = Process.HandleTable.GetData<KThread>(ThreadHandle);
+
+ if (Thread == null)
+ {
+ Logging.Warn(LogClass.KernelSvc, $"Invalid thread handle 0x{ThreadHandle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+ }
+
+ MutualExclusion Mutex = GetMutex(MutexAddress);
+
+ Mutex.Unlock();
+
+ if (!GetCondVar(CondVarAddress).WaitForSignal(Thread, Timeout))
+ {
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.Timeout);
+
+ return;
+ }
+
+ Mutex.WaitForLock(Thread);
+
+ ThreadState.X0 = 0;
+ }
+
+ private void SvcSignalProcessWideKey(AThreadState ThreadState)
+ {
+ long CondVarAddress = (long)ThreadState.X0;
+ int Count = (int)ThreadState.X1;
+
+ KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
+
+ GetCondVar(CondVarAddress).SetSignal(CurrThread, Count);
+
+ ThreadState.X0 = 0;
+ }
+
+ private MutualExclusion GetMutex(long MutexAddress)
+ {
+ MutualExclusion MutexFactory(long Key)
+ {
+ return new MutualExclusion(Process, MutexAddress);
+ }
+
+ return Mutexes.GetOrAdd(MutexAddress, MutexFactory);
+ }
+
+ private ConditionVariable GetCondVar(long CondVarAddress)
+ {
+ ConditionVariable CondVarFactory(long Key)
+ {
+ return new ConditionVariable(Process, CondVarAddress);
+ }
+
+ return CondVars.GetOrAdd(CondVarAddress, CondVarFactory);
+ }
+ }
+} \ No newline at end of file