diff options
| author | gdkchan <gab.dark.100@gmail.com> | 2018-04-18 23:52:23 -0300 |
|---|---|---|
| committer | gdkchan <gab.dark.100@gmail.com> | 2018-04-18 23:52:36 -0300 |
| commit | b9af34f3dd1e7f5e38b038f182c9fd4a791fdfea (patch) | |
| tree | e86e7941f5d2871ed2d9bd702b38997c86fb2af8 /Ryujinx.Core/OsHle/Kernel | |
| parent | e9a96e3522ee7620b525d210915a0e45510ea528 (diff) | |
[HLE/Kernel] Somewhat improved sync primitives
Diffstat (limited to 'Ryujinx.Core/OsHle/Kernel')
| -rw-r--r-- | Ryujinx.Core/OsHle/Kernel/ConditionVariable.cs | 139 | ||||
| -rw-r--r-- | Ryujinx.Core/OsHle/Kernel/KernelErr.cs | 10 | ||||
| -rw-r--r-- | Ryujinx.Core/OsHle/Kernel/MutualExclusion.cs | 91 | ||||
| -rw-r--r-- | Ryujinx.Core/OsHle/Kernel/SvcHandler.cs | 123 | ||||
| -rw-r--r-- | Ryujinx.Core/OsHle/Kernel/SvcMemory.cs | 264 | ||||
| -rw-r--r-- | Ryujinx.Core/OsHle/Kernel/SvcSystem.cs | 306 | ||||
| -rw-r--r-- | Ryujinx.Core/OsHle/Kernel/SvcThread.cs | 156 | ||||
| -rw-r--r-- | Ryujinx.Core/OsHle/Kernel/SvcThreadSync.cs | 120 |
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 |
