mirror of
https://git.743378673.xyz/MeloNX/MeloNX.git
synced 2025-07-24 15:37:10 +02:00
[HLE/Kernel] Somewhat improved sync primitives
This commit is contained in:
parent
e9a96e3522
commit
b9af34f3dd
20 changed files with 408 additions and 443 deletions
139
Ryujinx.Core/OsHle/Kernel/ConditionVariable.cs
Normal file
139
Ryujinx.Core/OsHle/Kernel/ConditionVariable.cs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
10
Ryujinx.Core/OsHle/Kernel/KernelErr.cs
Normal file
10
Ryujinx.Core/OsHle/Kernel/KernelErr.cs
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
91
Ryujinx.Core/OsHle/Kernel/MutualExclusion.cs
Normal file
91
Ryujinx.Core/OsHle/Kernel/MutualExclusion.cs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
123
Ryujinx.Core/OsHle/Kernel/SvcHandler.cs
Normal file
123
Ryujinx.Core/OsHle/Kernel/SvcHandler.cs
Normal file
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
264
Ryujinx.Core/OsHle/Kernel/SvcMemory.cs
Normal file
264
Ryujinx.Core/OsHle/Kernel/SvcMemory.cs
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
306
Ryujinx.Core/OsHle/Kernel/SvcSystem.cs
Normal file
306
Ryujinx.Core/OsHle/Kernel/SvcSystem.cs
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
156
Ryujinx.Core/OsHle/Kernel/SvcThread.cs
Normal file
156
Ryujinx.Core/OsHle/Kernel/SvcThread.cs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
120
Ryujinx.Core/OsHle/Kernel/SvcThreadSync.cs
Normal file
120
Ryujinx.Core/OsHle/Kernel/SvcThreadSync.cs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue