aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.HLE/HOS/Kernel
diff options
context:
space:
mode:
authorgdkchan <gab.dark.100@gmail.com>2018-08-16 20:47:36 -0300
committerGitHub <noreply@github.com>2018-08-16 20:47:36 -0300
commit521751795a1c97c0d97f6f8904a3be69b13d3a9d (patch)
tree942a05899c40e2de6d92a38b93a494bd96ee64b8 /Ryujinx.HLE/HOS/Kernel
parent182d716867ae477c2b15a5332430dc2641fa1cc3 (diff)
Code style fixes and nits on the HLE project (#355)
* Some style fixes and nits on ITimeZoneService * Remove some unneeded usings * Remove the Ryujinx.HLE.OsHle.Handles namespace * Remove hbmenu automatic load on process exit * Rename Ns to Device, rename Os to System, rename SystemState to State * Move Exceptions and Utilities out of OsHle * Rename OsHle to HOS * Rename OsHle folder to HOS * IManagerDisplayService and ISystemDisplayService style fixes * BsdError shouldn't be public * Add a empty new line before using static * Remove unused file * Some style fixes on NPDM * Exit gracefully when the application is closed * Code style fixes on IGeneralService * Add 0x prefix on values printed as hex * Small improvements on finalization code * Move ProcessId and ThreadId out of AThreadState * Rename VFs to FileSystem * FsAccessHeader shouldn't be public. Also fix file names casing * More case changes on NPDM * Remove unused files * Move using to the correct place on NPDM * Use properties on KernelAccessControlMmio * Address PR feedback
Diffstat (limited to 'Ryujinx.HLE/HOS/Kernel')
-rw-r--r--Ryujinx.HLE/HOS/Kernel/AddressArbiter.cs111
-rw-r--r--Ryujinx.HLE/HOS/Kernel/AddressSpaceType.cs10
-rw-r--r--Ryujinx.HLE/HOS/Kernel/KEvent.cs4
-rw-r--r--Ryujinx.HLE/HOS/Kernel/KMemoryBlock.cs43
-rw-r--r--Ryujinx.HLE/HOS/Kernel/KMemoryInfo.cs33
-rw-r--r--Ryujinx.HLE/HOS/Kernel/KMemoryManager.cs1081
-rw-r--r--Ryujinx.HLE/HOS/Kernel/KProcessHandleTable.cs34
-rw-r--r--Ryujinx.HLE/HOS/Kernel/KProcessScheduler.cs370
-rw-r--r--Ryujinx.HLE/HOS/Kernel/KSession.cs31
-rw-r--r--Ryujinx.HLE/HOS/Kernel/KSharedMemory.cs14
-rw-r--r--Ryujinx.HLE/HOS/Kernel/KSynchronizationObject.cs28
-rw-r--r--Ryujinx.HLE/HOS/Kernel/KThread.cs98
-rw-r--r--Ryujinx.HLE/HOS/Kernel/KTlsPageManager.cs60
-rw-r--r--Ryujinx.HLE/HOS/Kernel/KTransferMemory.cs14
-rw-r--r--Ryujinx.HLE/HOS/Kernel/KernelErr.cs22
-rw-r--r--Ryujinx.HLE/HOS/Kernel/MemoryAttribute.cs22
-rw-r--r--Ryujinx.HLE/HOS/Kernel/MemoryPermission.cs18
-rw-r--r--Ryujinx.HLE/HOS/Kernel/MemoryState.cs49
-rw-r--r--Ryujinx.HLE/HOS/Kernel/NsTimeConverter.cs19
-rw-r--r--Ryujinx.HLE/HOS/Kernel/SchedulerThread.cs48
-rw-r--r--Ryujinx.HLE/HOS/Kernel/SvcHandler.cs123
-rw-r--r--Ryujinx.HLE/HOS/Kernel/SvcMemory.cs577
-rw-r--r--Ryujinx.HLE/HOS/Kernel/SvcSystem.cs373
-rw-r--r--Ryujinx.HLE/HOS/Kernel/SvcThread.cs385
-rw-r--r--Ryujinx.HLE/HOS/Kernel/SvcThreadSync.cs523
-rw-r--r--Ryujinx.HLE/HOS/Kernel/ThreadQueue.cs158
26 files changed, 4248 insertions, 0 deletions
diff --git a/Ryujinx.HLE/HOS/Kernel/AddressArbiter.cs b/Ryujinx.HLE/HOS/Kernel/AddressArbiter.cs
new file mode 100644
index 00000000..d7df0a72
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/AddressArbiter.cs
@@ -0,0 +1,111 @@
+using ChocolArm64.Memory;
+using ChocolArm64.State;
+
+using static Ryujinx.HLE.HOS.ErrorCode;
+
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ static class AddressArbiter
+ {
+ static ulong WaitForAddress(Process Process, AThreadState ThreadState, long Address, ulong Timeout)
+ {
+ KThread CurrentThread = Process.GetThread(ThreadState.Tpidr);
+
+ Process.Scheduler.SetReschedule(CurrentThread.ProcessorId);
+
+ CurrentThread.ArbiterWaitAddress = Address;
+ CurrentThread.ArbiterSignaled = false;
+
+ Process.Scheduler.EnterWait(CurrentThread, NsTimeConverter.GetTimeMs(Timeout));
+
+ if (!CurrentThread.ArbiterSignaled)
+ {
+ return MakeError(ErrorModule.Kernel, KernelErr.Timeout);
+ }
+
+ return 0;
+ }
+
+ public static ulong WaitForAddressIfLessThan(Process Process,
+ AThreadState ThreadState,
+ AMemory Memory,
+ long Address,
+ int Value,
+ ulong Timeout,
+ bool ShouldDecrement)
+ {
+ Memory.SetExclusive(ThreadState, Address);
+
+ int CurrentValue = Memory.ReadInt32(Address);
+
+ while (true)
+ {
+ if (Memory.TestExclusive(ThreadState, Address))
+ {
+ if (CurrentValue < Value)
+ {
+ if (ShouldDecrement)
+ {
+ Memory.WriteInt32(Address, CurrentValue - 1);
+ }
+
+ Memory.ClearExclusiveForStore(ThreadState);
+ }
+ else
+ {
+ Memory.ClearExclusiveForStore(ThreadState);
+
+ return MakeError(ErrorModule.Kernel, KernelErr.InvalidState);
+ }
+
+ break;
+ }
+
+ Memory.SetExclusive(ThreadState, Address);
+
+ CurrentValue = Memory.ReadInt32(Address);
+ }
+
+ if (Timeout == 0)
+ {
+ return MakeError(ErrorModule.Kernel, KernelErr.Timeout);
+ }
+
+ return WaitForAddress(Process, ThreadState, Address, Timeout);
+ }
+
+ public static ulong WaitForAddressIfEqual(Process Process,
+ AThreadState ThreadState,
+ AMemory Memory,
+ long Address,
+ int Value,
+ ulong Timeout)
+ {
+ if (Memory.ReadInt32(Address) != Value)
+ {
+ return MakeError(ErrorModule.Kernel, KernelErr.InvalidState);
+ }
+
+ if (Timeout == 0)
+ {
+ return MakeError(ErrorModule.Kernel, KernelErr.Timeout);
+ }
+
+ return WaitForAddress(Process, ThreadState, Address, Timeout);
+ }
+ }
+
+ enum ArbitrationType : int
+ {
+ WaitIfLessThan,
+ DecrementAndWaitIfLessThan,
+ WaitIfEqual
+ }
+
+ enum SignalType : int
+ {
+ Signal,
+ IncrementAndSignalIfEqual,
+ ModifyByWaitingCountAndSignalIfEqual
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Kernel/AddressSpaceType.cs b/Ryujinx.HLE/HOS/Kernel/AddressSpaceType.cs
new file mode 100644
index 00000000..c97caf42
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/AddressSpaceType.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ enum AddressSpaceType
+ {
+ Addr32Bits = 0,
+ Addr36Bits = 1,
+ Addr36BitsNoMap = 2,
+ Addr39Bits = 3
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/KEvent.cs b/Ryujinx.HLE/HOS/Kernel/KEvent.cs
new file mode 100644
index 00000000..eaaafaba
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/KEvent.cs
@@ -0,0 +1,4 @@
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ class KEvent : KSynchronizationObject { }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/KMemoryBlock.cs b/Ryujinx.HLE/HOS/Kernel/KMemoryBlock.cs
new file mode 100644
index 00000000..6100741b
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/KMemoryBlock.cs
@@ -0,0 +1,43 @@
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ class KMemoryBlock
+ {
+ public long BasePosition { get; set; }
+ public long PagesCount { get; set; }
+
+ public MemoryState State { get; set; }
+ public MemoryPermission Permission { get; set; }
+ public MemoryAttribute Attribute { get; set; }
+
+ public int IpcRefCount { get; set; }
+ public int DeviceRefCount { get; set; }
+
+ public KMemoryBlock(
+ long BasePosition,
+ long PagesCount,
+ MemoryState State,
+ MemoryPermission Permission,
+ MemoryAttribute Attribute)
+ {
+ this.BasePosition = BasePosition;
+ this.PagesCount = PagesCount;
+ this.State = State;
+ this.Attribute = Attribute;
+ this.Permission = Permission;
+ }
+
+ public KMemoryInfo GetInfo()
+ {
+ long Size = PagesCount * KMemoryManager.PageSize;
+
+ return new KMemoryInfo(
+ BasePosition,
+ Size,
+ State,
+ Permission,
+ Attribute,
+ IpcRefCount,
+ DeviceRefCount);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/KMemoryInfo.cs b/Ryujinx.HLE/HOS/Kernel/KMemoryInfo.cs
new file mode 100644
index 00000000..9b73b32b
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/KMemoryInfo.cs
@@ -0,0 +1,33 @@
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ class KMemoryInfo
+ {
+ public long Position { get; private set; }
+ public long Size { get; private set; }
+
+ public MemoryState State { get; private set; }
+ public MemoryPermission Permission { get; private set; }
+ public MemoryAttribute Attribute { get; private set; }
+
+ public int IpcRefCount { get; private set; }
+ public int DeviceRefCount { get; private set; }
+
+ public KMemoryInfo(
+ long Position,
+ long Size,
+ MemoryState State,
+ MemoryPermission Permission,
+ MemoryAttribute Attribute,
+ int IpcRefCount,
+ int DeviceRefCount)
+ {
+ this.Position = Position;
+ this.Size = Size;
+ this.State = State;
+ this.Attribute = Attribute;
+ this.Permission = Permission;
+ this.IpcRefCount = IpcRefCount;
+ this.DeviceRefCount = DeviceRefCount;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/KMemoryManager.cs b/Ryujinx.HLE/HOS/Kernel/KMemoryManager.cs
new file mode 100644
index 00000000..0432aa88
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/KMemoryManager.cs
@@ -0,0 +1,1081 @@
+using ChocolArm64.Memory;
+using Ryujinx.HLE.Memory;
+using System;
+using System.Collections.Generic;
+
+using static Ryujinx.HLE.HOS.ErrorCode;
+
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ class KMemoryManager
+ {
+ public const int PageSize = 0x1000;
+
+ private LinkedList<KMemoryBlock> Blocks;
+
+ private AMemory CpuMemory;
+
+ private ArenaAllocator Allocator;
+
+ public long AddrSpaceStart { get; private set; }
+ public long AddrSpaceEnd { get; private set; }
+
+ public long CodeRegionStart { get; private set; }
+ public long CodeRegionEnd { get; private set; }
+
+ public long MapRegionStart { get; private set; }
+ public long MapRegionEnd { get; private set; }
+
+ public long HeapRegionStart { get; private set; }
+ public long HeapRegionEnd { get; private set; }
+
+ public long NewMapRegionStart { get; private set; }
+ public long NewMapRegionEnd { get; private set; }
+
+ public long TlsIoRegionStart { get; private set; }
+ public long TlsIoRegionEnd { get; private set; }
+
+ public long PersonalMmHeapUsage { get; private set; }
+
+ private long CurrentHeapAddr;
+
+ public KMemoryManager(Process Process)
+ {
+ CpuMemory = Process.Memory;
+ Allocator = Process.Device.Memory.Allocator;
+
+ long CodeRegionSize;
+ long MapRegionSize;
+ long HeapRegionSize;
+ long NewMapRegionSize;
+ long TlsIoRegionSize;
+ int AddrSpaceWidth;
+
+ AddressSpaceType AddrType = AddressSpaceType.Addr39Bits;
+
+ if (Process.MetaData != null)
+ {
+ AddrType = (AddressSpaceType)Process.MetaData.AddressSpaceWidth;
+ }
+
+ switch (AddrType)
+ {
+ case AddressSpaceType.Addr32Bits:
+ CodeRegionStart = 0x200000;
+ CodeRegionSize = 0x3fe00000;
+ MapRegionSize = 0x40000000;
+ HeapRegionSize = 0x40000000;
+ NewMapRegionSize = 0;
+ TlsIoRegionSize = 0;
+ AddrSpaceWidth = 32;
+ break;
+
+ case AddressSpaceType.Addr36Bits:
+ CodeRegionStart = 0x8000000;
+ CodeRegionSize = 0x78000000;
+ MapRegionSize = 0x180000000;
+ HeapRegionSize = 0x180000000;
+ NewMapRegionSize = 0;
+ TlsIoRegionSize = 0;
+ AddrSpaceWidth = 36;
+ break;
+
+ case AddressSpaceType.Addr36BitsNoMap:
+ CodeRegionStart = 0x200000;
+ CodeRegionSize = 0x3fe00000;
+ MapRegionSize = 0;
+ HeapRegionSize = 0x80000000;
+ NewMapRegionSize = 0;
+ TlsIoRegionSize = 0;
+ AddrSpaceWidth = 36;
+ break;
+
+ case AddressSpaceType.Addr39Bits:
+ CodeRegionStart = 0;
+ CodeRegionSize = 0x80000000;
+ MapRegionSize = 0x1000000000;
+ HeapRegionSize = 0x180000000;
+ NewMapRegionSize = 0x80000000;
+ TlsIoRegionSize = 0x1000000000;
+ AddrSpaceWidth = 39;
+ break;
+
+ default: throw new InvalidOperationException();
+ }
+
+ AddrSpaceStart = 0;
+ AddrSpaceEnd = 1L << AddrSpaceWidth;
+
+ CodeRegionEnd = CodeRegionStart + CodeRegionSize;
+ MapRegionStart = CodeRegionEnd;
+ MapRegionEnd = CodeRegionEnd + MapRegionSize;
+ HeapRegionStart = MapRegionEnd;
+ HeapRegionEnd = MapRegionEnd + HeapRegionSize;
+ NewMapRegionStart = HeapRegionEnd;
+ NewMapRegionEnd = HeapRegionEnd + NewMapRegionSize;
+ TlsIoRegionStart = NewMapRegionEnd;
+ TlsIoRegionEnd = NewMapRegionEnd + TlsIoRegionSize;
+
+ CurrentHeapAddr = HeapRegionStart;
+
+ if (NewMapRegionSize == 0)
+ {
+ NewMapRegionStart = AddrSpaceStart;
+ NewMapRegionEnd = AddrSpaceEnd;
+ }
+
+ Blocks = new LinkedList<KMemoryBlock>();
+
+ long AddrSpacePagesCount = (AddrSpaceEnd - AddrSpaceStart) / PageSize;
+
+ InsertBlock(AddrSpaceStart, AddrSpacePagesCount, MemoryState.Unmapped);
+ }
+
+ public void HleMapProcessCode(long Position, long Size)
+ {
+ long PagesCount = Size / PageSize;
+
+ if (!Allocator.TryAllocate(Size, out long PA))
+ {
+ throw new InvalidOperationException();
+ }
+
+ lock (Blocks)
+ {
+ InsertBlock(Position, PagesCount, MemoryState.CodeStatic, MemoryPermission.ReadAndExecute);
+
+ CpuMemory.Map(Position, PA, Size);
+ }
+ }
+
+ public void HleMapCustom(long Position, long Size, MemoryState State, MemoryPermission Permission)
+ {
+ long PagesCount = Size / PageSize;
+
+ if (!Allocator.TryAllocate(Size, out long PA))
+ {
+ throw new InvalidOperationException();
+ }
+
+ lock (Blocks)
+ {
+ InsertBlock(Position, PagesCount, State, Permission);
+
+ CpuMemory.Map(Position, PA, Size);
+ }
+ }
+
+ public long HleMapTlsPage()
+ {
+ bool HasTlsIoRegion = TlsIoRegionStart != TlsIoRegionEnd;
+
+ long Position = HasTlsIoRegion ? TlsIoRegionStart : CodeRegionStart;
+
+ lock (Blocks)
+ {
+ while (Position < (HasTlsIoRegion ? TlsIoRegionEnd : CodeRegionEnd))
+ {
+ if (FindBlock(Position).State == MemoryState.Unmapped)
+ {
+ InsertBlock(Position, 1, MemoryState.ThreadLocal, MemoryPermission.ReadAndWrite);
+
+ if (!Allocator.TryAllocate(PageSize, out long PA))
+ {
+ throw new InvalidOperationException();
+ }
+
+ CpuMemory.Map(Position, PA, PageSize);
+
+ return Position;
+ }
+
+ Position += PageSize;
+ }
+
+ throw new InvalidOperationException();
+ }
+ }
+
+ public long TrySetHeapSize(long Size, out long Position)
+ {
+ Position = 0;
+
+ if ((ulong)Size > (ulong)(HeapRegionEnd - HeapRegionStart))
+ {
+ return MakeError(ErrorModule.Kernel, KernelErr.OutOfMemory);
+ }
+
+ bool Success = false;
+
+ long CurrentHeapSize = GetHeapSize();
+
+ if ((ulong)CurrentHeapSize <= (ulong)Size)
+ {
+ //Expand.
+ long DiffSize = Size - CurrentHeapSize;
+
+ lock (Blocks)
+ {
+ if (Success = IsUnmapped(CurrentHeapAddr, DiffSize))
+ {
+ if (!Allocator.TryAllocate(DiffSize, out long PA))
+ {
+ return MakeError(ErrorModule.Kernel, KernelErr.OutOfMemory);
+ }
+
+ long PagesCount = DiffSize / PageSize;
+
+ InsertBlock(CurrentHeapAddr, PagesCount, MemoryState.Heap, MemoryPermission.ReadAndWrite);
+
+ CpuMemory.Map(CurrentHeapAddr, PA, DiffSize);
+ }
+ }
+ }
+ else
+ {
+ //Shrink.
+ long FreeAddr = HeapRegionStart + Size;
+ long DiffSize = CurrentHeapSize - Size;
+
+ lock (Blocks)
+ {
+ Success = CheckRange(
+ FreeAddr,
+ DiffSize,
+ MemoryState.Mask,
+ MemoryState.Heap,
+ MemoryPermission.Mask,
+ MemoryPermission.ReadAndWrite,
+ MemoryAttribute.Mask,
+ MemoryAttribute.None,
+ MemoryAttribute.IpcAndDeviceMapped,
+ out _,
+ out _,
+ out _);
+
+ if (Success)
+ {
+ long PagesCount = DiffSize / PageSize;
+
+ InsertBlock(FreeAddr, PagesCount, MemoryState.Unmapped);
+
+ CpuMemory.Unmap(FreeAddr, DiffSize);
+
+ FreePages(FreeAddr, PagesCount);
+ }
+ }
+ }
+
+ CurrentHeapAddr = HeapRegionStart + Size;
+
+ if (Success)
+ {
+ Position = HeapRegionStart;
+
+ return 0;
+ }
+
+ return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+ }
+
+ public long GetHeapSize()
+ {
+ return CurrentHeapAddr - HeapRegionStart;
+ }
+
+ public long SetMemoryAttribute(
+ long Position,
+ long Size,
+ MemoryAttribute AttributeMask,
+ MemoryAttribute AttributeValue)
+ {
+ lock (Blocks)
+ {
+ if (CheckRange(
+ Position,
+ Size,
+ MemoryState.AttributeChangeAllowed,
+ MemoryState.AttributeChangeAllowed,
+ MemoryPermission.None,
+ MemoryPermission.None,
+ MemoryAttribute.BorrowedAndIpcMapped,
+ MemoryAttribute.None,
+ MemoryAttribute.DeviceMappedAndUncached,
+ out MemoryState State,
+ out MemoryPermission Permission,
+ out MemoryAttribute Attribute))
+ {
+ long PagesCount = Size / PageSize;
+
+ Attribute &= ~AttributeMask;
+ Attribute |= AttributeMask & AttributeValue;
+
+ InsertBlock(Position, PagesCount, State, Permission, Attribute);
+
+ return 0;
+ }
+ }
+
+ return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+ }
+
+ public KMemoryInfo QueryMemory(long Position)
+ {
+ if ((ulong)Position >= (ulong)AddrSpaceStart &&
+ (ulong)Position < (ulong)AddrSpaceEnd)
+ {
+ lock (Blocks)
+ {
+ return FindBlock(Position).GetInfo();
+ }
+ }
+ else
+ {
+ return new KMemoryInfo(
+ AddrSpaceEnd,
+ -AddrSpaceEnd,
+ MemoryState.Reserved,
+ MemoryPermission.None,
+ MemoryAttribute.None,
+ 0,
+ 0);
+ }
+ }
+
+ public long Map(long Src, long Dst, long Size)
+ {
+ bool Success;
+
+ lock (Blocks)
+ {
+ Success = CheckRange(
+ Src,
+ Size,
+ MemoryState.MapAllowed,
+ MemoryState.MapAllowed,
+ MemoryPermission.Mask,
+ MemoryPermission.ReadAndWrite,
+ MemoryAttribute.Mask,
+ MemoryAttribute.None,
+ MemoryAttribute.IpcAndDeviceMapped,
+ out MemoryState SrcState,
+ out _,
+ out _);
+
+ Success &= IsUnmapped(Dst, Size);
+
+ if (Success)
+ {
+ long PagesCount = Size / PageSize;
+
+ InsertBlock(Src, PagesCount, SrcState, MemoryPermission.None, MemoryAttribute.Borrowed);
+
+ InsertBlock(Dst, PagesCount, MemoryState.MappedMemory, MemoryPermission.ReadAndWrite);
+
+ long PA = CpuMemory.GetPhysicalAddress(Src);
+
+ CpuMemory.Map(Dst, PA, Size);
+ }
+ }
+
+ return Success ? 0 : MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+ }
+
+ public long Unmap(long Src, long Dst, long Size)
+ {
+ bool Success;
+
+ lock (Blocks)
+ {
+ Success = CheckRange(
+ Src,
+ Size,
+ MemoryState.MapAllowed,
+ MemoryState.MapAllowed,
+ MemoryPermission.Mask,
+ MemoryPermission.None,
+ MemoryAttribute.Mask,
+ MemoryAttribute.Borrowed,
+ MemoryAttribute.IpcAndDeviceMapped,
+ out MemoryState SrcState,
+ out _,
+ out _);
+
+ Success &= CheckRange(
+ Dst,
+ Size,
+ MemoryState.Mask,
+ MemoryState.MappedMemory,
+ MemoryPermission.None,
+ MemoryPermission.None,
+ MemoryAttribute.Mask,
+ MemoryAttribute.None,
+ MemoryAttribute.IpcAndDeviceMapped,
+ out _,
+ out _,
+ out _);
+
+ if (Success)
+ {
+ long PagesCount = Size / PageSize;
+
+ InsertBlock(Src, PagesCount, SrcState, MemoryPermission.ReadAndWrite);
+
+ InsertBlock(Dst, PagesCount, MemoryState.Unmapped);
+
+ CpuMemory.Unmap(Dst, Size);
+ }
+ }
+
+ return Success ? 0 : MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+ }
+
+ public long MapSharedMemory(KSharedMemory SharedMemory, MemoryPermission Permission, long Position)
+ {
+ lock (Blocks)
+ {
+ if (IsUnmapped(Position, SharedMemory.Size))
+ {
+ long PagesCount = SharedMemory.Size / PageSize;
+
+ InsertBlock(Position, PagesCount, MemoryState.SharedMemory, Permission);
+
+ CpuMemory.Map(Position, SharedMemory.PA, SharedMemory.Size);
+
+ return 0;
+ }
+ }
+
+ return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+ }
+
+ public long UnmapSharedMemory(long Position, long Size)
+ {
+ lock (Blocks)
+ {
+ if (CheckRange(
+ Position,
+ Size,
+ MemoryState.Mask,
+ MemoryState.SharedMemory,
+ MemoryPermission.None,
+ MemoryPermission.None,
+ MemoryAttribute.Mask,
+ MemoryAttribute.None,
+ MemoryAttribute.IpcAndDeviceMapped,
+ out MemoryState State,
+ out _,
+ out _))
+ {
+ long PagesCount = Size / PageSize;
+
+ InsertBlock(Position, PagesCount, MemoryState.Unmapped);
+
+ CpuMemory.Unmap(Position, Size);
+
+ return 0;
+ }
+ }
+
+ return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+ }
+
+ public long ReserveTransferMemory(long Position, long Size, MemoryPermission Permission)
+ {
+ lock (Blocks)
+ {
+ if (CheckRange(
+ Position,
+ Size,
+ MemoryState.TransferMemoryAllowed | MemoryState.IsPoolAllocated,
+ MemoryState.TransferMemoryAllowed | MemoryState.IsPoolAllocated,
+ MemoryPermission.Mask,
+ MemoryPermission.ReadAndWrite,
+ MemoryAttribute.Mask,
+ MemoryAttribute.None,
+ MemoryAttribute.IpcAndDeviceMapped,
+ out MemoryState State,
+ out _,
+ out MemoryAttribute Attribute))
+ {
+ long PagesCount = Size / PageSize;
+
+ Attribute |= MemoryAttribute.Borrowed;
+
+ InsertBlock(Position, PagesCount, State, Permission, Attribute);
+
+ return 0;
+ }
+ }
+
+ return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+ }
+
+ public long ResetTransferMemory(long Position, long Size)
+ {
+ lock (Blocks)
+ {
+ if (CheckRange(
+ Position,
+ Size,
+ MemoryState.TransferMemoryAllowed | MemoryState.IsPoolAllocated,
+ MemoryState.TransferMemoryAllowed | MemoryState.IsPoolAllocated,
+ MemoryPermission.None,
+ MemoryPermission.None,
+ MemoryAttribute.Mask,
+ MemoryAttribute.Borrowed,
+ MemoryAttribute.IpcAndDeviceMapped,
+ out MemoryState State,
+ out _,
+ out _))
+ {
+ long PagesCount = Size / PageSize;
+
+ InsertBlock(Position, PagesCount, State, MemoryPermission.ReadAndWrite);
+
+ return 0;
+ }
+ }
+
+ return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+ }
+
+ public long SetProcessMemoryPermission(long Position, long Size, MemoryPermission Permission)
+ {
+ lock (Blocks)
+ {
+ if (CheckRange(
+ Position,
+ Size,
+ MemoryState.ProcessPermissionChangeAllowed,
+ MemoryState.ProcessPermissionChangeAllowed,
+ MemoryPermission.None,
+ MemoryPermission.None,
+ MemoryAttribute.Mask,
+ MemoryAttribute.None,
+ MemoryAttribute.IpcAndDeviceMapped,
+ out MemoryState State,
+ out _,
+ out _))
+ {
+ if (State == MemoryState.CodeStatic)
+ {
+ State = MemoryState.CodeMutable;
+ }
+ else if (State == MemoryState.ModCodeStatic)
+ {
+ State = MemoryState.ModCodeMutable;
+ }
+ else
+ {
+ throw new InvalidOperationException();
+ }
+
+ long PagesCount = Size / PageSize;
+
+ InsertBlock(Position, PagesCount, State, Permission);
+
+ return 0;
+ }
+ }
+
+ return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+ }
+
+ public long MapPhysicalMemory(long Position, long Size)
+ {
+ long End = Position + Size;
+
+ lock (Blocks)
+ {
+ long MappedSize = 0;
+
+ KMemoryInfo Info;
+
+ LinkedListNode<KMemoryBlock> BaseNode = FindBlockNode(Position);
+
+ LinkedListNode<KMemoryBlock> Node = BaseNode;
+
+ do
+ {
+ Info = Node.Value.GetInfo();
+
+ if (Info.State != MemoryState.Unmapped)
+ {
+ MappedSize += GetSizeInRange(Info, Position, End);
+ }
+
+ Node = Node.Next;
+ }
+ while ((ulong)(Info.Position + Info.Size) < (ulong)End && Node != null);
+
+ if (MappedSize == Size)
+ {
+ return 0;
+ }
+
+ long RemainingSize = Size - MappedSize;
+
+ if (!Allocator.TryAllocate(RemainingSize, out long PA))
+ {
+ return MakeError(ErrorModule.Kernel, KernelErr.OutOfMemory);
+ }
+
+ Node = BaseNode;
+
+ do
+ {
+ Info = Node.Value.GetInfo();
+
+ if (Info.State == MemoryState.Unmapped)
+ {
+ long CurrSize = GetSizeInRange(Info, Position, End);
+
+ CpuMemory.Map(Info.Position, PA, CurrSize);
+
+ PA += CurrSize;
+ }
+
+ Node = Node.Next;
+ }
+ while ((ulong)(Info.Position + Info.Size) < (ulong)End && Node != null);
+
+ PersonalMmHeapUsage += RemainingSize;
+
+ long PagesCount = Size / PageSize;
+
+ InsertBlock(
+ Position,
+ PagesCount,
+ MemoryState.Unmapped,
+ MemoryPermission.None,
+ MemoryAttribute.None,
+ MemoryState.Heap,
+ MemoryPermission.ReadAndWrite,
+ MemoryAttribute.None);
+ }
+
+ return 0;
+ }
+
+ public long UnmapPhysicalMemory(long Position, long Size)
+ {
+ long End = Position + Size;
+
+ lock (Blocks)
+ {
+ long HeapMappedSize = 0;
+
+ long CurrPosition = Position;
+
+ KMemoryInfo Info;
+
+ LinkedListNode<KMemoryBlock> Node = FindBlockNode(CurrPosition);
+
+ do
+ {
+ Info = Node.Value.GetInfo();
+
+ if (Info.State == MemoryState.Heap)
+ {
+ if (Info.Attribute != MemoryAttribute.None)
+ {
+ return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+ }
+
+ HeapMappedSize += GetSizeInRange(Info, Position, End);
+ }
+ else if (Info.State != MemoryState.Unmapped)
+ {
+ return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+ }
+
+ Node = Node.Next;
+ }
+ while ((ulong)(Info.Position + Info.Size) < (ulong)End && Node != null);
+
+ if (HeapMappedSize == 0)
+ {
+ return 0;
+ }
+
+ PersonalMmHeapUsage -= HeapMappedSize;
+
+ long PagesCount = Size / PageSize;
+
+ InsertBlock(Position, PagesCount, MemoryState.Unmapped);
+
+ CpuMemory.Unmap(Position, Size);
+
+ FreePages(Position, PagesCount);
+
+ return 0;
+ }
+ }
+
+ private long GetSizeInRange(KMemoryInfo Info, long Start, long End)
+ {
+ long CurrEnd = Info.Size + Info.Position;
+ long CurrSize = Info.Size;
+
+ if ((ulong)Info.Position < (ulong)Start)
+ {
+ CurrSize -= Start - Info.Position;
+ }
+
+ if ((ulong)CurrEnd > (ulong)End)
+ {
+ CurrSize -= CurrEnd - End;
+ }
+
+ return CurrSize;
+ }
+
+ private void FreePages(long Position, long PagesCount)
+ {
+ for (long Page = 0; Page < PagesCount; Page++)
+ {
+ long VA = Position + Page * PageSize;
+
+ long PA = CpuMemory.GetPhysicalAddress(VA);
+
+ Allocator.Free(PA, PageSize);
+ }
+ }
+
+ private bool IsUnmapped(long Position, long Size)
+ {
+ return CheckRange(
+ Position,
+ Size,
+ MemoryState.Mask,
+ MemoryState.Unmapped,
+ MemoryPermission.Mask,
+ MemoryPermission.None,
+ MemoryAttribute.Mask,
+ MemoryAttribute.None,
+ MemoryAttribute.IpcAndDeviceMapped,
+ out _,
+ out _,
+ out _);
+ }
+
+ private bool CheckRange(
+ long Position,
+ long Size,
+ MemoryState StateMask,
+ MemoryState StateExpected,
+ MemoryPermission PermissionMask,
+ MemoryPermission PermissionExpected,
+ MemoryAttribute AttributeMask,
+ MemoryAttribute AttributeExpected,
+ MemoryAttribute AttributeIgnoreMask,
+ out MemoryState OutState,
+ out MemoryPermission OutPermission,
+ out MemoryAttribute OutAttribute)
+ {
+ KMemoryInfo BlkInfo = FindBlock(Position).GetInfo();
+
+ ulong Start = (ulong)Position;
+ ulong End = (ulong)Size + Start;
+
+ if (End <= (ulong)(BlkInfo.Position + BlkInfo.Size))
+ {
+ if ((BlkInfo.Attribute & AttributeMask) == AttributeExpected &&
+ (BlkInfo.State & StateMask) == StateExpected &&
+ (BlkInfo.Permission & PermissionMask) == PermissionExpected)
+ {
+ OutState = BlkInfo.State;
+ OutPermission = BlkInfo.Permission;
+ OutAttribute = BlkInfo.Attribute & ~AttributeIgnoreMask;
+
+ return true;
+ }
+ }
+
+ OutState = MemoryState.Unmapped;
+ OutPermission = MemoryPermission.None;
+ OutAttribute = MemoryAttribute.None;
+
+ return false;
+ }
+
+ private void InsertBlock(
+ long BasePosition,
+ long PagesCount,
+ MemoryState OldState,
+ MemoryPermission OldPermission,
+ MemoryAttribute OldAttribute,
+ MemoryState NewState,
+ MemoryPermission NewPermission,
+ MemoryAttribute NewAttribute)
+ {
+ //Insert new block on the list only on areas where the state
+ //of the block matches the state specified on the Old* state
+ //arguments, otherwise leave it as is.
+ OldAttribute |= MemoryAttribute.IpcAndDeviceMapped;
+
+ ulong Start = (ulong)BasePosition;
+ ulong End = (ulong)PagesCount * PageSize + Start;
+
+ LinkedListNode<KMemoryBlock> Node = Blocks.First;
+
+ while (Node != null)
+ {
+ LinkedListNode<KMemoryBlock> NewNode = Node;
+ LinkedListNode<KMemoryBlock> NextNode = Node.Next;
+
+ KMemoryBlock CurrBlock = Node.Value;
+
+ ulong CurrStart = (ulong)CurrBlock.BasePosition;
+ ulong CurrEnd = (ulong)CurrBlock.PagesCount * PageSize + CurrStart;
+
+ if (Start < CurrEnd && CurrStart < End)
+ {
+ MemoryAttribute CurrBlockAttr = CurrBlock.Attribute | MemoryAttribute.IpcAndDeviceMapped;
+
+ if (CurrBlock.State != OldState ||
+ CurrBlock.Permission != OldPermission ||
+ CurrBlockAttr != OldAttribute)
+ {
+ Node = NextNode;
+
+ continue;
+ }
+
+ if (CurrStart >= Start && CurrEnd <= End)
+ {
+ CurrBlock.State = NewState;
+ CurrBlock.Permission = NewPermission;
+ CurrBlock.Attribute &= ~MemoryAttribute.IpcAndDeviceMapped;
+ CurrBlock.Attribute |= NewAttribute;
+ }
+ else if (CurrStart >= Start)
+ {
+ CurrBlock.BasePosition = (long)End;
+
+ CurrBlock.PagesCount = (long)((CurrEnd - End) / PageSize);
+
+ long NewPagesCount = (long)((End - CurrStart) / PageSize);
+
+ NewNode = Blocks.AddBefore(Node, new KMemoryBlock(
+ (long)CurrStart,
+ NewPagesCount,
+ NewState,
+ NewPermission,
+ NewAttribute));
+ }
+ else if (CurrEnd <= End)
+ {
+ CurrBlock.PagesCount = (long)((Start - CurrStart) / PageSize);
+
+ long NewPagesCount = (long)((CurrEnd - Start) / PageSize);
+
+ NewNode = Blocks.AddAfter(Node, new KMemoryBlock(
+ BasePosition,
+ NewPagesCount,
+ NewState,
+ NewPermission,
+ NewAttribute));
+ }
+ else
+ {
+ CurrBlock.PagesCount = (long)((Start - CurrStart) / PageSize);
+
+ long NextPagesCount = (long)((CurrEnd - End) / PageSize);
+
+ NewNode = Blocks.AddAfter(Node, new KMemoryBlock(
+ BasePosition,
+ PagesCount,
+ NewState,
+ NewPermission,
+ NewAttribute));
+
+ Blocks.AddAfter(NewNode, new KMemoryBlock(
+ (long)End,
+ NextPagesCount,
+ CurrBlock.State,
+ CurrBlock.Permission,
+ CurrBlock.Attribute));
+
+ NextNode = null;
+ }
+
+ MergeEqualStateNeighbours(NewNode);
+ }
+
+ Node = NextNode;
+ }
+ }
+
+ private void InsertBlock(
+ long BasePosition,
+ long PagesCount,
+ MemoryState State,
+ MemoryPermission Permission = MemoryPermission.None,
+ MemoryAttribute Attribute = MemoryAttribute.None)
+ {
+ //Inserts new block at the list, replacing and spliting
+ //existing blocks as needed.
+ KMemoryBlock Block = new KMemoryBlock(BasePosition, PagesCount, State, Permission, Attribute);
+
+ ulong Start = (ulong)BasePosition;
+ ulong End = (ulong)PagesCount * PageSize + Start;
+
+ LinkedListNode<KMemoryBlock> NewNode = null;
+
+ LinkedListNode<KMemoryBlock> Node = Blocks.First;
+
+ while (Node != null)
+ {
+ KMemoryBlock CurrBlock = Node.Value;
+
+ LinkedListNode<KMemoryBlock> NextNode = Node.Next;
+
+ ulong CurrStart = (ulong)CurrBlock.BasePosition;
+ ulong CurrEnd = (ulong)CurrBlock.PagesCount * PageSize + CurrStart;
+
+ if (Start < CurrEnd && CurrStart < End)
+ {
+ if (Start >= CurrStart && End <= CurrEnd)
+ {
+ Block.Attribute |= CurrBlock.Attribute & MemoryAttribute.IpcAndDeviceMapped;
+ }
+
+ if (Start > CurrStart && End < CurrEnd)
+ {
+ CurrBlock.PagesCount = (long)((Start - CurrStart) / PageSize);
+
+ long NextPagesCount = (long)((CurrEnd - End) / PageSize);
+
+ NewNode = Blocks.AddAfter(Node, Block);
+
+ Blocks.AddAfter(NewNode, new KMemoryBlock(
+ (long)End,
+ NextPagesCount,
+ CurrBlock.State,
+ CurrBlock.Permission,
+ CurrBlock.Attribute));
+
+ break;
+ }
+ else if (Start <= CurrStart && End < CurrEnd)
+ {
+ CurrBlock.BasePosition = (long)End;
+
+ CurrBlock.PagesCount = (long)((CurrEnd - End) / PageSize);
+
+ if (NewNode == null)
+ {
+ NewNode = Blocks.AddBefore(Node, Block);
+ }
+ }
+ else if (Start > CurrStart && End >= CurrEnd)
+ {
+ CurrBlock.PagesCount = (long)((Start - CurrStart) / PageSize);
+
+ if (NewNode == null)
+ {
+ NewNode = Blocks.AddAfter(Node, Block);
+ }
+ }
+ else
+ {
+ if (NewNode == null)
+ {
+ NewNode = Blocks.AddBefore(Node, Block);
+ }
+
+ Blocks.Remove(Node);
+ }
+ }
+
+ Node = NextNode;
+ }
+
+ if (NewNode == null)
+ {
+ NewNode = Blocks.AddFirst(Block);
+ }
+
+ MergeEqualStateNeighbours(NewNode);
+ }
+
+ private void MergeEqualStateNeighbours(LinkedListNode<KMemoryBlock> Node)
+ {
+ KMemoryBlock Block = Node.Value;
+
+ ulong Start = (ulong)Block.BasePosition;
+ ulong End = (ulong)Block.PagesCount * PageSize + Start;
+
+ if (Node.Previous != null)
+ {
+ KMemoryBlock Previous = Node.Previous.Value;
+
+ if (BlockStateEquals(Block, Previous))
+ {
+ Blocks.Remove(Node.Previous);
+
+ Block.BasePosition = Previous.BasePosition;
+
+ Start = (ulong)Block.BasePosition;
+ }
+ }
+
+ if (Node.Next != null)
+ {
+ KMemoryBlock Next = Node.Next.Value;
+
+ if (BlockStateEquals(Block, Next))
+ {
+ Blocks.Remove(Node.Next);
+
+ End = (ulong)(Next.BasePosition + Next.PagesCount * PageSize);
+ }
+ }
+
+ Block.PagesCount = (long)((End - Start) / PageSize);
+ }
+
+ private static bool BlockStateEquals(KMemoryBlock LHS, KMemoryBlock RHS)
+ {
+ return LHS.State == RHS.State &&
+ LHS.Permission == RHS.Permission &&
+ LHS.Attribute == RHS.Attribute &&
+ LHS.DeviceRefCount == RHS.DeviceRefCount &&
+ LHS.IpcRefCount == RHS.IpcRefCount;
+ }
+
+ private KMemoryBlock FindBlock(long Position)
+ {
+ return FindBlockNode(Position)?.Value;
+ }
+
+ private LinkedListNode<KMemoryBlock> FindBlockNode(long Position)
+ {
+ ulong Addr = (ulong)Position;
+
+ lock (Blocks)
+ {
+ LinkedListNode<KMemoryBlock> Node = Blocks.First;
+
+ while (Node != null)
+ {
+ KMemoryBlock Block = Node.Value;
+
+ ulong Start = (ulong)Block.BasePosition;
+ ulong End = (ulong)Block.PagesCount * PageSize + Start;
+
+ if (Start <= Addr && End - 1 >= Addr)
+ {
+ return Node;
+ }
+
+ Node = Node.Next;
+ }
+ }
+
+ return null;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/KProcessHandleTable.cs b/Ryujinx.HLE/HOS/Kernel/KProcessHandleTable.cs
new file mode 100644
index 00000000..db0eaa44
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/KProcessHandleTable.cs
@@ -0,0 +1,34 @@
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ class KProcessHandleTable
+ {
+ private IdDictionary Handles;
+
+ public KProcessHandleTable()
+ {
+ Handles = new IdDictionary();
+ }
+
+ public int OpenHandle(object Obj)
+ {
+ return Handles.Add(Obj);
+ }
+
+ public T GetData<T>(int Handle)
+ {
+ return Handles.GetData<T>(Handle);
+ }
+
+ public object CloseHandle(int Handle)
+ {
+ return Handles.Delete(Handle);
+ }
+
+ public ICollection<object> Clear()
+ {
+ return Handles.Clear();
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/KProcessScheduler.cs b/Ryujinx.HLE/HOS/Kernel/KProcessScheduler.cs
new file mode 100644
index 00000000..2120f16c
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/KProcessScheduler.cs
@@ -0,0 +1,370 @@
+using Ryujinx.HLE.Logging;
+using System;
+using System.Collections.Concurrent;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ class KProcessScheduler : IDisposable
+ {
+ private ConcurrentDictionary<KThread, SchedulerThread> AllThreads;
+
+ private ThreadQueue WaitingToRun;
+
+ private KThread[] CoreThreads;
+
+ private bool[] CoreReschedule;
+
+ private object SchedLock;
+
+ private Logger Log;
+
+ public KProcessScheduler(Logger Log)
+ {
+ this.Log = Log;
+
+ AllThreads = new ConcurrentDictionary<KThread, SchedulerThread>();
+
+ WaitingToRun = new ThreadQueue();
+
+ CoreThreads = new KThread[4];
+
+ CoreReschedule = new bool[4];
+
+ SchedLock = new object();
+ }
+
+ public void StartThread(KThread Thread)
+ {
+ lock (SchedLock)
+ {
+ SchedulerThread SchedThread = new SchedulerThread(Thread);
+
+ if (!AllThreads.TryAdd(Thread, SchedThread))
+ {
+ return;
+ }
+
+ if (TryAddToCore(Thread))
+ {
+ Thread.Thread.Execute();
+
+ PrintDbgThreadInfo(Thread, "running.");
+ }
+ else
+ {
+ WaitingToRun.Push(SchedThread);
+
+ PrintDbgThreadInfo(Thread, "waiting to run.");
+ }
+ }
+ }
+
+ public void RemoveThread(KThread Thread)
+ {
+ PrintDbgThreadInfo(Thread, "exited.");
+
+ lock (SchedLock)
+ {
+ if (AllThreads.TryRemove(Thread, out SchedulerThread SchedThread))
+ {
+ WaitingToRun.Remove(SchedThread);
+
+ SchedThread.Dispose();
+ }
+
+ int ActualCore = Thread.ActualCore;
+
+ SchedulerThread NewThread = WaitingToRun.Pop(ActualCore);
+
+ if (NewThread == null)
+ {
+ Log.PrintDebug(LogClass.KernelScheduler, $"Nothing to run on core {ActualCore}!");
+
+ CoreThreads[ActualCore] = null;
+
+ return;
+ }
+
+ NewThread.Thread.ActualCore = ActualCore;
+
+ RunThread(NewThread);
+ }
+ }
+
+ public void SetThreadActivity(KThread Thread, bool Active)
+ {
+ SchedulerThread SchedThread = AllThreads[Thread];
+
+ SchedThread.IsActive = Active;
+
+ if (Active)
+ {
+ SchedThread.WaitActivity.Set();
+ }
+ else
+ {
+ SchedThread.WaitActivity.Reset();
+ }
+ }
+
+ public void EnterWait(KThread Thread, int TimeoutMs = Timeout.Infinite)
+ {
+ SchedulerThread SchedThread = AllThreads[Thread];
+
+ Suspend(Thread);
+
+ SchedThread.WaitSync.WaitOne(TimeoutMs);
+
+ TryResumingExecution(SchedThread);
+ }
+
+ public void WakeUp(KThread Thread)
+ {
+ AllThreads[Thread].WaitSync.Set();
+ }
+
+ public void ForceWakeUp(KThread Thread)
+ {
+ if (AllThreads.TryGetValue(Thread, out SchedulerThread SchedThread))
+ {
+ SchedThread.WaitSync.Set();
+ SchedThread.WaitActivity.Set();
+ SchedThread.WaitSched.Set();
+ }
+ }
+
+ public void ChangeCore(KThread Thread, int IdealCore, int CoreMask)
+ {
+ lock (SchedLock)
+ {
+ if (IdealCore != -3)
+ {
+ Thread.IdealCore = IdealCore;
+ }
+
+ Thread.CoreMask = CoreMask;
+
+ if (AllThreads.ContainsKey(Thread))
+ {
+ SetReschedule(Thread.ActualCore);
+
+ SchedulerThread SchedThread = AllThreads[Thread];
+
+ //Note: Aways if the thread is on the queue first, and try
+ //adding to a new core later, to ensure that a thread that
+ //is already running won't be added to another core.
+ if (WaitingToRun.HasThread(SchedThread) && TryAddToCore(Thread))
+ {
+ WaitingToRun.Remove(SchedThread);
+
+ RunThread(SchedThread);
+ }
+ }
+ }
+ }
+
+ public void Suspend(KThread Thread)
+ {
+ lock (SchedLock)
+ {
+ PrintDbgThreadInfo(Thread, "suspended.");
+
+ int ActualCore = Thread.ActualCore;
+
+ CoreReschedule[ActualCore] = false;
+
+ SchedulerThread SchedThread = WaitingToRun.Pop(ActualCore);
+
+ if (SchedThread != null)
+ {
+ SchedThread.Thread.ActualCore = ActualCore;
+
+ CoreThreads[ActualCore] = SchedThread.Thread;
+
+ RunThread(SchedThread);
+ }
+ else
+ {
+ Log.PrintDebug(LogClass.KernelScheduler, $"Nothing to run on core {Thread.ActualCore}!");
+
+ CoreThreads[ActualCore] = null;
+ }
+ }
+ }
+
+ public void SetReschedule(int Core)
+ {
+ lock (SchedLock)
+ {
+ CoreReschedule[Core] = true;
+ }
+ }
+
+ public void Reschedule(KThread Thread)
+ {
+ bool NeedsReschedule;
+
+ lock (SchedLock)
+ {
+ int ActualCore = Thread.ActualCore;
+
+ NeedsReschedule = CoreReschedule[ActualCore];
+
+ CoreReschedule[ActualCore] = false;
+ }
+
+ if (NeedsReschedule)
+ {
+ Yield(Thread, Thread.ActualPriority - 1);
+ }
+ }
+
+ public void Yield(KThread Thread)
+ {
+ Yield(Thread, Thread.ActualPriority);
+ }
+
+ private void Yield(KThread Thread, int MinPriority)
+ {
+ PrintDbgThreadInfo(Thread, "yielded execution.");
+
+ lock (SchedLock)
+ {
+ int ActualCore = Thread.ActualCore;
+
+ SchedulerThread NewThread = WaitingToRun.Pop(ActualCore, MinPriority);
+
+ if (NewThread != null)
+ {
+ NewThread.Thread.ActualCore = ActualCore;
+
+ CoreThreads[ActualCore] = NewThread.Thread;
+
+ RunThread(NewThread);
+ }
+ else
+ {
+ CoreThreads[ActualCore] = null;
+ }
+ }
+
+ Resume(Thread);
+ }
+
+ public void Resume(KThread Thread)
+ {
+ TryResumingExecution(AllThreads[Thread]);
+ }
+
+ private void TryResumingExecution(SchedulerThread SchedThread)
+ {
+ KThread Thread = SchedThread.Thread;
+
+ PrintDbgThreadInfo(Thread, "trying to resume...");
+
+ SchedThread.WaitActivity.WaitOne();
+
+ lock (SchedLock)
+ {
+ if (TryAddToCore(Thread))
+ {
+ PrintDbgThreadInfo(Thread, "resuming execution...");
+
+ return;
+ }
+
+ WaitingToRun.Push(SchedThread);
+
+ SetReschedule(Thread.ProcessorId);
+
+ PrintDbgThreadInfo(Thread, "entering wait state...");
+ }
+
+ SchedThread.WaitSched.WaitOne();
+
+ PrintDbgThreadInfo(Thread, "resuming execution...");
+ }
+
+ private void RunThread(SchedulerThread SchedThread)
+ {
+ if (!SchedThread.Thread.Thread.Execute())
+ {
+ PrintDbgThreadInfo(SchedThread.Thread, "waked.");
+
+ SchedThread.WaitSched.Set();
+ }
+ else
+ {
+ PrintDbgThreadInfo(SchedThread.Thread, "running.");
+ }
+ }
+
+ public void Resort(KThread Thread)
+ {
+ if (AllThreads.TryGetValue(Thread, out SchedulerThread SchedThread))
+ {
+ WaitingToRun.Resort(SchedThread);
+ }
+ }
+
+ private bool TryAddToCore(KThread Thread)
+ {
+ //First, try running it on Ideal Core.
+ int IdealCore = Thread.IdealCore;
+
+ if (IdealCore != -1 && CoreThreads[IdealCore] == null)
+ {
+ Thread.ActualCore = IdealCore;
+
+ CoreThreads[IdealCore] = Thread;
+
+ return true;
+ }
+
+ //If that fails, then try running on any core allowed by Core Mask.
+ int CoreMask = Thread.CoreMask;
+
+ for (int Core = 0; Core < CoreThreads.Length; Core++, CoreMask >>= 1)
+ {
+ if ((CoreMask & 1) != 0 && CoreThreads[Core] == null)
+ {
+ Thread.ActualCore = Core;
+
+ CoreThreads[Core] = Thread;
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private void PrintDbgThreadInfo(KThread Thread, string Message)
+ {
+ Log.PrintDebug(LogClass.KernelScheduler, "(" +
+ "ThreadId = " + Thread.ThreadId + ", " +
+ "CoreMask = 0x" + Thread.CoreMask.ToString("x1") + ", " +
+ "ActualCore = " + Thread.ActualCore + ", " +
+ "IdealCore = " + Thread.IdealCore + ", " +
+ "ActualPriority = " + Thread.ActualPriority + ", " +
+ "WantedPriority = " + Thread.WantedPriority + ") " + Message);
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool Disposing)
+ {
+ if (Disposing)
+ {
+ foreach (SchedulerThread SchedThread in AllThreads.Values)
+ {
+ SchedThread.Dispose();
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/KSession.cs b/Ryujinx.HLE/HOS/Kernel/KSession.cs
new file mode 100644
index 00000000..4b21d3a6
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/KSession.cs
@@ -0,0 +1,31 @@
+using Ryujinx.HLE.HOS.Services;
+using System;
+
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ class KSession : IDisposable
+ {
+ public IpcService Service { get; private set; }
+
+ public string ServiceName { get; private set; }
+
+ public KSession(IpcService Service, string ServiceName)
+ {
+ this.Service = Service;
+ this.ServiceName = ServiceName;
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool Disposing)
+ {
+ if (Disposing && Service is IDisposable DisposableService)
+ {
+ DisposableService.Dispose();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/KSharedMemory.cs b/Ryujinx.HLE/HOS/Kernel/KSharedMemory.cs
new file mode 100644
index 00000000..cdd31667
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/KSharedMemory.cs
@@ -0,0 +1,14 @@
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ class KSharedMemory
+ {
+ public long PA { get; private set; }
+ public long Size { get; private set; }
+
+ public KSharedMemory(long PA, long Size)
+ {
+ this.PA = PA;
+ this.Size = Size;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/KSynchronizationObject.cs b/Ryujinx.HLE/HOS/Kernel/KSynchronizationObject.cs
new file mode 100644
index 00000000..b83b0004
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/KSynchronizationObject.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ class KSynchronizationObject : IDisposable
+ {
+ public ManualResetEvent WaitEvent { get; private set; }
+
+ public KSynchronizationObject()
+ {
+ WaitEvent = new ManualResetEvent(false);
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool Disposing)
+ {
+ if (Disposing)
+ {
+ WaitEvent.Dispose();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/KThread.cs b/Ryujinx.HLE/HOS/Kernel/KThread.cs
new file mode 100644
index 00000000..171fefc5
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/KThread.cs
@@ -0,0 +1,98 @@
+using ChocolArm64;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ class KThread : KSynchronizationObject
+ {
+ public AThread Thread { get; private set; }
+
+ public int CoreMask { get; set; }
+
+ public long MutexAddress { get; set; }
+ public long CondVarAddress { get; set; }
+ public long ArbiterWaitAddress { get; set; }
+
+ public bool CondVarSignaled { get; set; }
+ public bool ArbiterSignaled { get; set; }
+
+ private Process Process;
+
+ public List<KThread> MutexWaiters { get; private set; }
+
+ public KThread MutexOwner { get; set; }
+
+ public int ActualPriority { get; private set; }
+ public int WantedPriority { get; private set; }
+
+ public int ActualCore { get; set; }
+ public int ProcessorId { get; set; }
+ public int IdealCore { get; set; }
+
+ public int WaitHandle { get; set; }
+
+ public long LastPc { get; set; }
+
+ public int ThreadId { get; private set; }
+
+ public KThread(
+ AThread Thread,
+ Process Process,
+ int ProcessorId,
+ int Priority,
+ int ThreadId)
+ {
+ this.Thread = Thread;
+ this.Process = Process;
+ this.ProcessorId = ProcessorId;
+ this.IdealCore = ProcessorId;
+ this.ThreadId = ThreadId;
+
+ MutexWaiters = new List<KThread>();
+
+ CoreMask = 1 << ProcessorId;
+
+ ActualPriority = WantedPriority = Priority;
+ }
+
+ public void SetPriority(int Priority)
+ {
+ WantedPriority = Priority;
+
+ UpdatePriority();
+ }
+
+ public void UpdatePriority()
+ {
+ bool PriorityChanged;
+
+ lock (Process.ThreadSyncLock)
+ {
+ int OldPriority = ActualPriority;
+
+ int CurrPriority = WantedPriority;
+
+ foreach (KThread Thread in MutexWaiters)
+ {
+ int WantedPriority = Thread.WantedPriority;
+
+ if (CurrPriority > WantedPriority)
+ {
+ CurrPriority = WantedPriority;
+ }
+ }
+
+ PriorityChanged = CurrPriority != OldPriority;
+
+ ActualPriority = CurrPriority;
+ }
+
+ if (PriorityChanged)
+ {
+ Process.Scheduler.Resort(this);
+
+ MutexOwner?.UpdatePriority();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/KTlsPageManager.cs b/Ryujinx.HLE/HOS/Kernel/KTlsPageManager.cs
new file mode 100644
index 00000000..1fb2ce6a
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/KTlsPageManager.cs
@@ -0,0 +1,60 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ class KTlsPageManager
+ {
+ private const int TlsEntrySize = 0x200;
+
+ private long PagePosition;
+
+ private int UsedSlots;
+
+ private bool[] Slots;
+
+ public bool IsEmpty => UsedSlots == 0;
+ public bool IsFull => UsedSlots == Slots.Length;
+
+ public KTlsPageManager(long PagePosition)
+ {
+ this.PagePosition = PagePosition;
+
+ Slots = new bool[KMemoryManager.PageSize / TlsEntrySize];
+ }
+
+ public bool TryGetFreeTlsAddr(out long Position)
+ {
+ Position = PagePosition;
+
+ for (int Index = 0; Index < Slots.Length; Index++)
+ {
+ if (!Slots[Index])
+ {
+ Slots[Index] = true;
+
+ UsedSlots++;
+
+ return true;
+ }
+
+ Position += TlsEntrySize;
+ }
+
+ Position = 0;
+
+ return false;
+ }
+
+ public void FreeTlsSlot(int Slot)
+ {
+ if ((uint)Slot > Slots.Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(Slot));
+ }
+
+ Slots[Slot] = false;
+
+ UsedSlots--;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/KTransferMemory.cs b/Ryujinx.HLE/HOS/Kernel/KTransferMemory.cs
new file mode 100644
index 00000000..6ebffa7e
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/KTransferMemory.cs
@@ -0,0 +1,14 @@
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ class KTransferMemory
+ {
+ public long Position { get; private set; }
+ public long Size { get; private set; }
+
+ public KTransferMemory(long Position, long Size)
+ {
+ this.Position = Position;
+ this.Size = Size;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/KernelErr.cs b/Ryujinx.HLE/HOS/Kernel/KernelErr.cs
new file mode 100644
index 00000000..e76ae11e
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/KernelErr.cs
@@ -0,0 +1,22 @@
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ static class KernelErr
+ {
+ public const int InvalidSize = 101;
+ public const int InvalidAddress = 102;
+ public const int OutOfMemory = 104;
+ public const int NoAccessPerm = 106;
+ public const int InvalidPermission = 108;
+ public const int InvalidMemRange = 110;
+ public const int InvalidPriority = 112;
+ public const int InvalidCoreId = 113;
+ public const int InvalidHandle = 114;
+ public const int InvalidMaskValue = 116;
+ public const int Timeout = 117;
+ public const int Canceled = 118;
+ public const int CountOutOfRange = 119;
+ public const int InvalidEnumValue = 120;
+ public const int InvalidThread = 122;
+ public const int InvalidState = 125;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/MemoryAttribute.cs b/Ryujinx.HLE/HOS/Kernel/MemoryAttribute.cs
new file mode 100644
index 00000000..8f3197cb
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/MemoryAttribute.cs
@@ -0,0 +1,22 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ [Flags]
+ enum MemoryAttribute : byte
+ {
+ None = 0,
+ Mask = 0xff,
+
+ Borrowed = 1 << 0,
+ IpcMapped = 1 << 1,
+ DeviceMapped = 1 << 2,
+ Uncached = 1 << 3,
+
+ IpcAndDeviceMapped = IpcMapped | DeviceMapped,
+
+ BorrowedAndIpcMapped = Borrowed | IpcMapped,
+
+ DeviceMappedAndUncached = DeviceMapped | Uncached
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/MemoryPermission.cs b/Ryujinx.HLE/HOS/Kernel/MemoryPermission.cs
new file mode 100644
index 00000000..63539c2e
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/MemoryPermission.cs
@@ -0,0 +1,18 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ [Flags]
+ enum MemoryPermission : byte
+ {
+ None = 0,
+ Mask = 0xff,
+
+ Read = 1 << 0,
+ Write = 1 << 1,
+ Execute = 1 << 2,
+
+ ReadAndWrite = Read | Write,
+ ReadAndExecute = Read | Execute
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/MemoryState.cs b/Ryujinx.HLE/HOS/Kernel/MemoryState.cs
new file mode 100644
index 00000000..2c37723c
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/MemoryState.cs
@@ -0,0 +1,49 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ [Flags]
+ enum MemoryState : uint
+ {
+ Unmapped = 0x00000000,
+ Io = 0x00002001,
+ Normal = 0x00042002,
+ CodeStatic = 0x00DC7E03,
+ CodeMutable = 0x03FEBD04,
+ Heap = 0x037EBD05,
+ SharedMemory = 0x00402006,
+ ModCodeStatic = 0x00DD7E08,
+ ModCodeMutable = 0x03FFBD09,
+ IpcBuffer0 = 0x005C3C0A,
+ MappedMemory = 0x005C3C0B,
+ ThreadLocal = 0x0040200C,
+ TransferMemoryIsolated = 0x015C3C0D,
+ TransferMemory = 0x005C380E,
+ ProcessMemory = 0x0040380F,
+ Reserved = 0x00000010,
+ IpcBuffer1 = 0x005C3811,
+ IpcBuffer3 = 0x004C2812,
+ KernelStack = 0x00002013,
+ CodeReadOnly = 0x00402214,
+ CodeWritable = 0x00402015,
+ Mask = 0xffffffff,
+
+ PermissionChangeAllowed = 1 << 8,
+ ForceReadWritableByDebugSyscalls = 1 << 9,
+ IpcSendAllowedType0 = 1 << 10,
+ IpcSendAllowedType3 = 1 << 11,
+ IpcSendAllowedType1 = 1 << 12,
+ ProcessPermissionChangeAllowed = 1 << 14,
+ MapAllowed = 1 << 15,
+ UnmapProcessCodeMemoryAllowed = 1 << 16,
+ TransferMemoryAllowed = 1 << 17,
+ QueryPhysicalAddressAllowed = 1 << 18,
+ MapDeviceAllowed = 1 << 19,
+ MapDeviceAlignedAllowed = 1 << 20,
+ IpcBufferAllowed = 1 << 21,
+ IsPoolAllocated = 1 << 22,
+ MapProcessAllowed = 1 << 23,
+ AttributeChangeAllowed = 1 << 24,
+ CodeMemoryAllowed = 1 << 25
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/NsTimeConverter.cs b/Ryujinx.HLE/HOS/Kernel/NsTimeConverter.cs
new file mode 100644
index 00000000..b8008f75
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/NsTimeConverter.cs
@@ -0,0 +1,19 @@
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ static class NsTimeConverter
+ {
+ public static int GetTimeMs(ulong Ns)
+ {
+ ulong Ms = Ns / 1_000_000;
+
+ if (Ms < int.MaxValue)
+ {
+ return (int)Ms;
+ }
+ else
+ {
+ return int.MaxValue;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/SchedulerThread.cs b/Ryujinx.HLE/HOS/Kernel/SchedulerThread.cs
new file mode 100644
index 00000000..bab7b03e
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/SchedulerThread.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ class SchedulerThread : IDisposable
+ {
+ public KThread Thread { get; private set; }
+
+ public SchedulerThread Next { get; set; }
+
+ public bool IsActive { get; set; }
+
+ public AutoResetEvent WaitSync { get; private set; }
+ public ManualResetEvent WaitActivity { get; private set; }
+ public AutoResetEvent WaitSched { get; private set; }
+
+ public SchedulerThread(KThread Thread)
+ {
+ this.Thread = Thread;
+
+ IsActive = true;
+
+ WaitSync = new AutoResetEvent(false);
+
+ WaitActivity = new ManualResetEvent(true);
+
+ WaitSched = new AutoResetEvent(false);
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool Disposing)
+ {
+ if (Disposing)
+ {
+ WaitSync.Dispose();
+
+ WaitActivity.Dispose();
+
+ WaitSched.Dispose();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/SvcHandler.cs b/Ryujinx.HLE/HOS/Kernel/SvcHandler.cs
new file mode 100644
index 00000000..fcb72c14
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/SvcHandler.cs
@@ -0,0 +1,123 @@
+using ChocolArm64.Events;
+using ChocolArm64.Memory;
+using ChocolArm64.State;
+using Ryujinx.HLE.Logging;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ partial class SvcHandler
+ {
+ private delegate void SvcFunc(AThreadState ThreadState);
+
+ private Dictionary<int, SvcFunc> SvcFuncs;
+
+ private Switch Device;
+ private Process Process;
+ private AMemory Memory;
+
+ private ConcurrentDictionary<KThread, AutoResetEvent> SyncWaits;
+
+ private const uint SelfThreadHandle = 0xffff8000;
+ private const uint SelfProcessHandle = 0xffff8001;
+
+ private static Random Rng;
+
+ public SvcHandler(Switch Device, Process Process)
+ {
+ SvcFuncs = new Dictionary<int, SvcFunc>()
+ {
+ { 0x01, SvcSetHeapSize },
+ { 0x03, SvcSetMemoryAttribute },
+ { 0x04, SvcMapMemory },
+ { 0x05, SvcUnmapMemory },
+ { 0x06, SvcQueryMemory },
+ { 0x07, SvcExitProcess },
+ { 0x08, SvcCreateThread },
+ { 0x09, SvcStartThread },
+ { 0x0a, SvcExitThread },
+ { 0x0b, SvcSleepThread },
+ { 0x0c, SvcGetThreadPriority },
+ { 0x0d, SvcSetThreadPriority },
+ { 0x0e, SvcGetThreadCoreMask },
+ { 0x0f, SvcSetThreadCoreMask },
+ { 0x10, SvcGetCurrentProcessorNumber },
+ { 0x12, SvcClearEvent },
+ { 0x13, SvcMapSharedMemory },
+ { 0x14, SvcUnmapSharedMemory },
+ { 0x15, SvcCreateTransferMemory },
+ { 0x16, SvcCloseHandle },
+ { 0x17, SvcResetSignal },
+ { 0x18, SvcWaitSynchronization },
+ { 0x19, SvcCancelSynchronization },
+ { 0x1a, SvcArbitrateLock },
+ { 0x1b, SvcArbitrateUnlock },
+ { 0x1c, SvcWaitProcessWideKeyAtomic },
+ { 0x1d, SvcSignalProcessWideKey },
+ { 0x1e, SvcGetSystemTick },
+ { 0x1f, SvcConnectToNamedPort },
+ { 0x21, SvcSendSyncRequest },
+ { 0x22, SvcSendSyncRequestWithUserBuffer },
+ { 0x25, SvcGetThreadId },
+ { 0x26, SvcBreak },
+ { 0x27, SvcOutputDebugString },
+ { 0x29, SvcGetInfo },
+ { 0x2c, SvcMapPhysicalMemory },
+ { 0x2d, SvcUnmapPhysicalMemory },
+ { 0x32, SvcSetThreadActivity },
+ { 0x33, SvcGetThreadContext3 },
+ { 0x34, SvcWaitForAddress }
+ };
+
+ this.Device = Device;
+ this.Process = Process;
+ this.Memory = Process.Memory;
+
+ SyncWaits = new ConcurrentDictionary<KThread, AutoResetEvent>();
+ }
+
+ static SvcHandler()
+ {
+ Rng = new Random();
+ }
+
+ public void SvcCall(object sender, AInstExceptionEventArgs e)
+ {
+ AThreadState ThreadState = (AThreadState)sender;
+
+ Process.GetThread(ThreadState.Tpidr).LastPc = e.Position;
+
+ if (SvcFuncs.TryGetValue(e.Id, out SvcFunc Func))
+ {
+ Device.Log.PrintDebug(LogClass.KernelSvc, $"{Func.Method.Name} called.");
+
+ Func(ThreadState);
+
+ Process.Scheduler.Reschedule(Process.GetThread(ThreadState.Tpidr));
+
+ Device.Log.PrintDebug(LogClass.KernelSvc, $"{Func.Method.Name} ended.");
+ }
+ else
+ {
+ Process.PrintStackTrace(ThreadState);
+
+ throw new NotImplementedException($"0x{e.Id:x4}");
+ }
+ }
+
+ private KThread GetThread(long Tpidr, int Handle)
+ {
+ if ((uint)Handle == SelfThreadHandle)
+ {
+ return Process.GetThread(Tpidr);
+ }
+ else
+ {
+ return Process.HandleTable.GetData<KThread>(Handle);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/SvcMemory.cs b/Ryujinx.HLE/HOS/Kernel/SvcMemory.cs
new file mode 100644
index 00000000..b9bca74a
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/SvcMemory.cs
@@ -0,0 +1,577 @@
+using ChocolArm64.State;
+using Ryujinx.HLE.Logging;
+
+using static Ryujinx.HLE.HOS.ErrorCode;
+
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ partial class SvcHandler
+ {
+ private void SvcSetHeapSize(AThreadState ThreadState)
+ {
+ long Size = (long)ThreadState.X1;
+
+ if ((Size & 0x1fffff) != 0 || Size != (uint)Size)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Heap size 0x{Size:x16} is not aligned!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize);
+
+ return;
+ }
+
+ long Result = Process.MemoryManager.TrySetHeapSize(Size, out long Position);
+
+ ThreadState.X0 = (ulong)Result;
+
+ if (Result == 0)
+ {
+ ThreadState.X1 = (ulong)Position;
+ }
+ else
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!");
+ }
+ }
+
+ private void SvcSetMemoryAttribute(AThreadState ThreadState)
+ {
+ long Position = (long)ThreadState.X0;
+ long Size = (long)ThreadState.X1;
+
+ if (!PageAligned(Position))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} is not page aligned!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
+
+ return;
+ }
+
+ if (!PageAligned(Size) || Size == 0)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize);
+
+ return;
+ }
+
+ MemoryAttribute AttributeMask = (MemoryAttribute)ThreadState.X2;
+ MemoryAttribute AttributeValue = (MemoryAttribute)ThreadState.X3;
+
+ MemoryAttribute Attributes = AttributeMask | AttributeValue;
+
+ if (Attributes != AttributeMask ||
+ (Attributes | MemoryAttribute.Uncached) != MemoryAttribute.Uncached)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, "Invalid memory attributes!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMaskValue);
+
+ return;
+ }
+
+ long Result = Process.MemoryManager.SetMemoryAttribute(
+ Position,
+ Size,
+ AttributeMask,
+ AttributeValue);
+
+ if (Result != 0)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!");
+ }
+ else
+ {
+ Memory.StopObservingRegion(Position, Size);
+ }
+
+ ThreadState.X0 = (ulong)Result;
+ }
+
+ private void SvcMapMemory(AThreadState ThreadState)
+ {
+ long Dst = (long)ThreadState.X0;
+ long Src = (long)ThreadState.X1;
+ long Size = (long)ThreadState.X2;
+
+ if (!PageAligned(Src | Dst))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, "Addresses are not page aligned!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
+
+ return;
+ }
+
+ if (!PageAligned(Size) || Size == 0)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize);
+
+ return;
+ }
+
+ if ((ulong)(Src + Size) <= (ulong)Src || (ulong)(Dst + Size) <= (ulong)Dst)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, "Addresses outside of range!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+
+ return;
+ }
+
+ if (!InsideAddrSpace(Src, Size))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Src address 0x{Src:x16} out of range!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+
+ return;
+ }
+
+ if (!InsideNewMapRegion(Dst, Size))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Dst address 0x{Dst:x16} out of range!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange);
+
+ return;
+ }
+
+ long Result = Process.MemoryManager.Map(Src, Dst, Size);
+
+ if (Result != 0)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!");
+ }
+
+ ThreadState.X0 = (ulong)Result;
+ }
+
+ private void SvcUnmapMemory(AThreadState ThreadState)
+ {
+ long Dst = (long)ThreadState.X0;
+ long Src = (long)ThreadState.X1;
+ long Size = (long)ThreadState.X2;
+
+ if (!PageAligned(Src | Dst))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, "Addresses are not page aligned!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
+
+ return;
+ }
+
+ if (!PageAligned(Size) || Size == 0)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize);
+
+ return;
+ }
+
+ if ((ulong)(Src + Size) <= (ulong)Src || (ulong)(Dst + Size) <= (ulong)Dst)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, "Addresses outside of range!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+
+ return;
+ }
+
+ if (!InsideAddrSpace(Src, Size))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Src address 0x{Src:x16} out of range!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+
+ return;
+ }
+
+ if (!InsideNewMapRegion(Dst, Size))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Dst address 0x{Dst:x16} out of range!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange);
+
+ return;
+ }
+
+ long Result = Process.MemoryManager.Unmap(Src, Dst, Size);
+
+ if (Result != 0)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!");
+ }
+
+ ThreadState.X0 = (ulong)Result;
+ }
+
+ private void SvcQueryMemory(AThreadState ThreadState)
+ {
+ long InfoPtr = (long)ThreadState.X0;
+ long Position = (long)ThreadState.X2;
+
+ KMemoryInfo BlkInfo = Process.MemoryManager.QueryMemory(Position);
+
+ Memory.WriteInt64(InfoPtr + 0x00, BlkInfo.Position);
+ Memory.WriteInt64(InfoPtr + 0x08, BlkInfo.Size);
+ Memory.WriteInt32(InfoPtr + 0x10, (int)BlkInfo.State & 0xff);
+ Memory.WriteInt32(InfoPtr + 0x14, (int)BlkInfo.Attribute);
+ Memory.WriteInt32(InfoPtr + 0x18, (int)BlkInfo.Permission);
+ Memory.WriteInt32(InfoPtr + 0x1c, BlkInfo.IpcRefCount);
+ Memory.WriteInt32(InfoPtr + 0x20, BlkInfo.DeviceRefCount);
+ Memory.WriteInt32(InfoPtr + 0x24, 0);
+
+ ThreadState.X0 = 0;
+ ThreadState.X1 = 0;
+ }
+
+ private void SvcMapSharedMemory(AThreadState ThreadState)
+ {
+ int Handle = (int)ThreadState.X0;
+ long Position = (long)ThreadState.X1;
+ long Size = (long)ThreadState.X2;
+
+ if (!PageAligned(Position))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} is not page aligned!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
+
+ return;
+ }
+
+ if (!PageAligned(Size) || Size == 0)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize);
+
+ return;
+ }
+
+ if ((ulong)(Position + Size) <= (ulong)Position)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid region address 0x{Position:x16} / size 0x{Size:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+
+ return;
+ }
+
+ MemoryPermission Permission = (MemoryPermission)ThreadState.X3;
+
+ if ((Permission | MemoryPermission.Write) != MemoryPermission.ReadAndWrite)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid permission {Permission}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidPermission);
+
+ return;
+ }
+
+ KSharedMemory SharedMemory = Process.HandleTable.GetData<KSharedMemory>(Handle);
+
+ if (SharedMemory == null)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid shared memory handle 0x{Handle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+
+ return;
+ }
+
+ if (!InsideAddrSpace(Position, Size) || InsideMapRegion(Position, Size) || InsideHeapRegion(Position, Size))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} out of range!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+
+ return;
+ }
+
+ if (SharedMemory.Size != Size)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} does not match shared memory size 0x{SharedMemory.Size:16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize);
+
+ return;
+ }
+
+ long Result = Process.MemoryManager.MapSharedMemory(SharedMemory, Permission, Position);
+
+ if (Result != 0)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!");
+ }
+
+ ThreadState.X0 = (ulong)Result;
+ }
+
+ private void SvcUnmapSharedMemory(AThreadState ThreadState)
+ {
+ int Handle = (int)ThreadState.X0;
+ long Position = (long)ThreadState.X1;
+ long Size = (long)ThreadState.X2;
+
+ if (!PageAligned(Position))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} is not page aligned!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
+
+ return;
+ }
+
+ if (!PageAligned(Size) || Size == 0)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize);
+
+ return;
+ }
+
+ if ((ulong)(Position + Size) <= (ulong)Position)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid region address 0x{Position:x16} / size 0x{Size:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+
+ return;
+ }
+
+ KSharedMemory SharedMemory = Process.HandleTable.GetData<KSharedMemory>(Handle);
+
+ if (SharedMemory == null)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid shared memory handle 0x{Handle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+
+ return;
+ }
+
+ if (!InsideAddrSpace(Position, Size) || InsideMapRegion(Position, Size) || InsideHeapRegion(Position, Size))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} out of range!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+
+ return;
+ }
+
+ long Result = Process.MemoryManager.UnmapSharedMemory(Position, Size);
+
+ if (Result != 0)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!");
+ }
+
+ ThreadState.X0 = (ulong)Result;
+ }
+
+ private void SvcCreateTransferMemory(AThreadState ThreadState)
+ {
+ long Position = (long)ThreadState.X1;
+ long Size = (long)ThreadState.X2;
+
+ if (!PageAligned(Position))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} is not page aligned!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
+
+ return;
+ }
+
+ if (!PageAligned(Size) || Size == 0)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
+
+ return;
+ }
+
+ if ((ulong)(Position + Size) <= (ulong)Position)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid region address 0x{Position:x16} / size 0x{Size:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+
+ return;
+ }
+
+ MemoryPermission Permission = (MemoryPermission)ThreadState.X3;
+
+ if (Permission > MemoryPermission.ReadAndWrite || Permission == MemoryPermission.Write)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid permission {Permission}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidPermission);
+
+ return;
+ }
+
+ Process.MemoryManager.ReserveTransferMemory(Position, Size, Permission);
+
+ KTransferMemory TransferMemory = new KTransferMemory(Position, Size);
+
+ int Handle = Process.HandleTable.OpenHandle(TransferMemory);
+
+ ThreadState.X0 = 0;
+ ThreadState.X1 = (ulong)Handle;
+ }
+
+ private void SvcMapPhysicalMemory(AThreadState ThreadState)
+ {
+ long Position = (long)ThreadState.X0;
+ long Size = (long)ThreadState.X1;
+
+ if (!PageAligned(Position))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} is not page aligned!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
+
+ return;
+ }
+
+ if (!PageAligned(Size) || Size == 0)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize);
+
+ return;
+ }
+
+ if ((ulong)(Position + Size) <= (ulong)Position)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid region address 0x{Position:x16} / size 0x{Size:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+
+ return;
+ }
+
+ if (!InsideAddrSpace(Position, Size))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid address {Position:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+
+ return;
+ }
+
+ long Result = Process.MemoryManager.MapPhysicalMemory(Position, Size);
+
+ if (Result != 0)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!");
+ }
+
+ ThreadState.X0 = (ulong)Result;
+ }
+
+ private void SvcUnmapPhysicalMemory(AThreadState ThreadState)
+ {
+ long Position = (long)ThreadState.X0;
+ long Size = (long)ThreadState.X1;
+
+ if (!PageAligned(Position))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} is not page aligned!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
+
+ return;
+ }
+
+ if (!PageAligned(Size) || Size == 0)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize);
+
+ return;
+ }
+
+ if ((ulong)(Position + Size) <= (ulong)Position)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid region address 0x{Position:x16} / size 0x{Size:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+
+ return;
+ }
+
+ if (!InsideAddrSpace(Position, Size))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid address {Position:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+
+ return;
+ }
+
+ long Result = Process.MemoryManager.UnmapPhysicalMemory(Position, Size);
+
+ if (Result != 0)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!");
+ }
+
+ ThreadState.X0 = (ulong)Result;
+ }
+
+ private static bool PageAligned(long Position)
+ {
+ return (Position & (KMemoryManager.PageSize - 1)) == 0;
+ }
+
+ private bool InsideAddrSpace(long Position, long Size)
+ {
+ ulong Start = (ulong)Position;
+ ulong End = (ulong)Size + Start;
+
+ return Start >= (ulong)Process.MemoryManager.AddrSpaceStart &&
+ End < (ulong)Process.MemoryManager.AddrSpaceEnd;
+ }
+
+ private bool InsideMapRegion(long Position, long Size)
+ {
+ ulong Start = (ulong)Position;
+ ulong End = (ulong)Size + Start;
+
+ return Start >= (ulong)Process.MemoryManager.MapRegionStart &&
+ End < (ulong)Process.MemoryManager.MapRegionEnd;
+ }
+
+ private bool InsideHeapRegion(long Position, long Size)
+ {
+ ulong Start = (ulong)Position;
+ ulong End = (ulong)Size + Start;
+
+ return Start >= (ulong)Process.MemoryManager.HeapRegionStart &&
+ End < (ulong)Process.MemoryManager.HeapRegionEnd;
+ }
+
+ private bool InsideNewMapRegion(long Position, long Size)
+ {
+ ulong Start = (ulong)Position;
+ ulong End = (ulong)Size + Start;
+
+ return Start >= (ulong)Process.MemoryManager.NewMapRegionStart &&
+ End < (ulong)Process.MemoryManager.NewMapRegionEnd;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/SvcSystem.cs b/Ryujinx.HLE/HOS/Kernel/SvcSystem.cs
new file mode 100644
index 00000000..7cc1c858
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/SvcSystem.cs
@@ -0,0 +1,373 @@
+using ChocolArm64.Memory;
+using ChocolArm64.State;
+using Ryujinx.HLE.Exceptions;
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Services;
+using Ryujinx.HLE.Logging;
+using System;
+using System.Threading;
+
+using static Ryujinx.HLE.HOS.ErrorCode;
+
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ partial class SvcHandler
+ {
+ private const int AllowedCpuIdBitmask = 0b1111;
+
+ private const bool EnableProcessDebugging = false;
+
+ private void SvcExitProcess(AThreadState ThreadState)
+ {
+ Device.System.ExitProcess(Process.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)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid handle 0x{Handle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+
+ return;
+ }
+
+ if (Obj is KSession Session)
+ {
+ Session.Dispose();
+ }
+ else if (Obj is KTransferMemory TransferMemory)
+ {
+ Process.MemoryManager.ResetTransferMemory(
+ TransferMemory.Position,
+ TransferMemory.Size);
+ }
+
+ 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
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid event handle 0x{Handle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+ }
+ }
+
+ private void SvcWaitSynchronization(AThreadState ThreadState)
+ {
+ long HandlesPtr = (long)ThreadState.X1;
+ int HandlesCount = (int)ThreadState.X2;
+ ulong Timeout = ThreadState.X3;
+
+ Device.Log.PrintDebug(LogClass.KernelSvc,
+ "HandlesPtr = 0x" + HandlesPtr .ToString("x16") + ", " +
+ "HandlesCount = 0x" + HandlesCount.ToString("x8") + ", " +
+ "Timeout = 0x" + Timeout .ToString("x16"));
+
+ if ((uint)HandlesCount > 0x40)
+ {
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.CountOutOfRange);
+
+ return;
+ }
+
+ KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
+
+ WaitHandle[] Handles = new WaitHandle[HandlesCount + 1];
+
+ for (int Index = 0; Index < HandlesCount; Index++)
+ {
+ int Handle = Memory.ReadInt32(HandlesPtr + Index * 4);
+
+ KSynchronizationObject SyncObj = Process.HandleTable.GetData<KSynchronizationObject>(Handle);
+
+ if (SyncObj == null)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid handle 0x{Handle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+
+ return;
+ }
+
+ Handles[Index] = SyncObj.WaitEvent;
+ }
+
+ using (AutoResetEvent WaitEvent = new AutoResetEvent(false))
+ {
+ if (!SyncWaits.TryAdd(CurrThread, WaitEvent))
+ {
+ throw new InvalidOperationException();
+ }
+
+ Handles[HandlesCount] = WaitEvent;
+
+ Process.Scheduler.Suspend(CurrThread);
+
+ int HandleIndex;
+
+ ulong Result = 0;
+
+ if (Timeout != ulong.MaxValue)
+ {
+ HandleIndex = WaitHandle.WaitAny(Handles, NsTimeConverter.GetTimeMs(Timeout));
+ }
+ else
+ {
+ HandleIndex = WaitHandle.WaitAny(Handles);
+ }
+
+ if (HandleIndex == WaitHandle.WaitTimeout)
+ {
+ Result = MakeError(ErrorModule.Kernel, KernelErr.Timeout);
+ }
+ else if (HandleIndex == HandlesCount)
+ {
+ Result = MakeError(ErrorModule.Kernel, KernelErr.Canceled);
+ }
+
+ SyncWaits.TryRemove(CurrThread, out _);
+
+ Process.Scheduler.Resume(CurrThread);
+
+ ThreadState.X0 = Result;
+
+ if (Result == 0)
+ {
+ ThreadState.X1 = (ulong)HandleIndex;
+ }
+ }
+ }
+
+ private void SvcCancelSynchronization(AThreadState ThreadState)
+ {
+ int ThreadHandle = (int)ThreadState.X0;
+
+ KThread Thread = GetThread(ThreadState.Tpidr, ThreadHandle);
+
+ if (Thread == null)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{ThreadHandle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+
+ return;
+ }
+
+ if (SyncWaits.TryRemove(Thread, out AutoResetEvent WaitEvent))
+ {
+ WaitEvent.Set();
+ }
+
+ ThreadState.X0 = 0;
+ }
+
+ private void SvcGetSystemTick(AThreadState ThreadState)
+ {
+ ThreadState.X0 = ThreadState.CntpctEl0;
+ }
+
+ private void SvcConnectToNamedPort(AThreadState ThreadState)
+ {
+ long StackPtr = (long)ThreadState.X0;
+ long NamePtr = (long)ThreadState.X1;
+
+ string Name = AMemoryHelper.ReadAsciiString(Memory, NamePtr, 8);
+
+ //TODO: Validate that app has perms to access the service, and that the service
+ //actually exists, return error codes otherwise.
+ KSession Session = new KSession(ServiceFactory.MakeService(Name), Name);
+
+ ulong Handle = (ulong)Process.HandleTable.OpenHandle(Session);
+
+ ThreadState.X0 = 0;
+ ThreadState.X1 = Handle;
+ }
+
+ private void SvcSendSyncRequest(AThreadState ThreadState)
+ {
+ SendSyncRequest(ThreadState, ThreadState.Tpidr, 0x100, (int)ThreadState.X0);
+ }
+
+ private void SvcSendSyncRequestWithUserBuffer(AThreadState ThreadState)
+ {
+ SendSyncRequest(
+ ThreadState,
+ (long)ThreadState.X0,
+ (long)ThreadState.X1,
+ (int)ThreadState.X2);
+ }
+
+ private void SendSyncRequest(AThreadState ThreadState, long CmdPtr, long Size, int Handle)
+ {
+ KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
+
+ byte[] CmdData = Memory.ReadBytes(CmdPtr, Size);
+
+ KSession Session = Process.HandleTable.GetData<KSession>(Handle);
+
+ if (Session != null)
+ {
+ Process.Scheduler.Suspend(CurrThread);
+
+ IpcMessage Cmd = new IpcMessage(CmdData, CmdPtr);
+
+ long Result = IpcHandler.IpcCall(Device, Process, Memory, Session, Cmd, CmdPtr);
+
+ Thread.Yield();
+
+ Process.Scheduler.Resume(CurrThread);
+
+ ThreadState.X0 = (ulong)Result;
+ }
+ else
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid session handle 0x{Handle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+ }
+ }
+
+ private void SvcBreak(AThreadState ThreadState)
+ {
+ long Reason = (long)ThreadState.X0;
+ long Unknown = (long)ThreadState.X1;
+ long Info = (long)ThreadState.X2;
+
+ Process.PrintStackTrace(ThreadState);
+
+ throw new GuestBrokeExecutionException();
+ }
+
+ private void SvcOutputDebugString(AThreadState ThreadState)
+ {
+ long Position = (long)ThreadState.X0;
+ long Size = (long)ThreadState.X1;
+
+ string Str = AMemoryHelper.ReadAsciiString(Memory, Position, Size);
+
+ Device.Log.PrintWarning(LogClass.KernelSvc, Str);
+
+ ThreadState.X0 = 0;
+ }
+
+ private void SvcGetInfo(AThreadState ThreadState)
+ {
+ long StackPtr = (long)ThreadState.X0;
+ int InfoType = (int)ThreadState.X1;
+ long Handle = (long)ThreadState.X2;
+ int InfoId = (int)ThreadState.X3;
+
+ //Fail for info not available on older Kernel versions.
+ if (InfoType == 18 ||
+ InfoType == 19 ||
+ InfoType == 20)
+ {
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidEnumValue);
+
+ return;
+ }
+
+ switch (InfoType)
+ {
+ case 0:
+ ThreadState.X1 = AllowedCpuIdBitmask;
+ break;
+
+ case 2:
+ ThreadState.X1 = (ulong)Process.MemoryManager.MapRegionStart;
+ break;
+
+ case 3:
+ ThreadState.X1 = (ulong)Process.MemoryManager.MapRegionEnd -
+ (ulong)Process.MemoryManager.MapRegionStart;
+ break;
+
+ case 4:
+ ThreadState.X1 = (ulong)Process.MemoryManager.HeapRegionStart;
+ break;
+
+ case 5:
+ ThreadState.X1 = (ulong)Process.MemoryManager.HeapRegionEnd -
+ (ulong)Process.MemoryManager.HeapRegionStart;
+ break;
+
+ case 6:
+ ThreadState.X1 = (ulong)Process.Device.Memory.Allocator.TotalAvailableSize;
+ break;
+
+ case 7:
+ ThreadState.X1 = (ulong)Process.Device.Memory.Allocator.TotalUsedSize;
+ 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 = (ulong)Process.MemoryManager.AddrSpaceStart;
+ break;
+
+ case 13:
+ ThreadState.X1 = (ulong)Process.MemoryManager.AddrSpaceEnd -
+ (ulong)Process.MemoryManager.AddrSpaceStart;
+ break;
+
+ case 14:
+ ThreadState.X1 = (ulong)Process.MemoryManager.NewMapRegionStart;
+ break;
+
+ case 15:
+ ThreadState.X1 = (ulong)Process.MemoryManager.NewMapRegionEnd -
+ (ulong)Process.MemoryManager.NewMapRegionStart;
+ break;
+
+ case 16:
+ ThreadState.X1 = (ulong)(Process.MetaData?.SystemResourceSize ?? 0);
+ break;
+
+ case 17:
+ ThreadState.X1 = (ulong)Process.MemoryManager.PersonalMmHeapUsage;
+ break;
+
+ default:
+ Process.PrintStackTrace(ThreadState);
+
+ throw new NotImplementedException($"SvcGetInfo: {InfoType} 0x{Handle:x8} {InfoId}");
+ }
+
+ ThreadState.X0 = 0;
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Kernel/SvcThread.cs b/Ryujinx.HLE/HOS/Kernel/SvcThread.cs
new file mode 100644
index 00000000..69e75ec0
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/SvcThread.cs
@@ -0,0 +1,385 @@
+using ChocolArm64.State;
+using Ryujinx.HLE.Logging;
+using System.Threading;
+
+using static Ryujinx.HLE.HOS.ErrorCode;
+
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ partial class SvcHandler
+ {
+ private void SvcCreateThread(AThreadState ThreadState)
+ {
+ long EntryPoint = (long)ThreadState.X1;
+ long ArgsPtr = (long)ThreadState.X2;
+ long StackTop = (long)ThreadState.X3;
+ int Priority = (int)ThreadState.X4;
+ int ProcessorId = (int)ThreadState.X5;
+
+ if ((uint)Priority > 0x3f)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid priority 0x{Priority:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidPriority);
+
+ return;
+ }
+
+ if (ProcessorId == -2)
+ {
+ //TODO: Get this value from the NPDM file.
+ ProcessorId = 0;
+ }
+ else if ((uint)ProcessorId > 3)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid core id 0x{ProcessorId:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidCoreId);
+
+ return;
+ }
+
+ int Handle = Process.MakeThread(
+ EntryPoint,
+ StackTop,
+ ArgsPtr,
+ Priority,
+ ProcessorId);
+
+ ThreadState.X0 = 0;
+ ThreadState.X1 = (ulong)Handle;
+ }
+
+ private void SvcStartThread(AThreadState ThreadState)
+ {
+ int Handle = (int)ThreadState.X0;
+
+ KThread NewThread = Process.HandleTable.GetData<KThread>(Handle);
+
+ if (NewThread != null)
+ {
+ Process.Scheduler.StartThread(NewThread);
+ Process.Scheduler.SetReschedule(NewThread.ProcessorId);
+
+ ThreadState.X0 = 0;
+ }
+ else
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+ }
+ }
+
+ private void SvcExitThread(AThreadState ThreadState)
+ {
+ KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
+
+ CurrThread.Thread.StopExecution();
+ }
+
+ private void SvcSleepThread(AThreadState ThreadState)
+ {
+ ulong TimeoutNs = ThreadState.X0;
+
+ Device.Log.PrintDebug(LogClass.KernelSvc, "Timeout = 0x" + TimeoutNs.ToString("x16"));
+
+ KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
+
+ if (TimeoutNs == 0 || TimeoutNs == ulong.MaxValue)
+ {
+ Process.Scheduler.Yield(CurrThread);
+ }
+ else
+ {
+ Process.Scheduler.Suspend(CurrThread);
+
+ Thread.Sleep(NsTimeConverter.GetTimeMs(TimeoutNs));
+
+ Process.Scheduler.Resume(CurrThread);
+ }
+ }
+
+ private void SvcGetThreadPriority(AThreadState ThreadState)
+ {
+ int Handle = (int)ThreadState.X1;
+
+ KThread Thread = GetThread(ThreadState.Tpidr, Handle);
+
+ if (Thread != null)
+ {
+ ThreadState.X0 = 0;
+ ThreadState.X1 = (ulong)Thread.ActualPriority;
+ }
+ else
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+ }
+ }
+
+ private void SvcSetThreadPriority(AThreadState ThreadState)
+ {
+ int Handle = (int)ThreadState.X0;
+ int Priority = (int)ThreadState.X1;
+
+ Device.Log.PrintDebug(LogClass.KernelSvc,
+ "Handle = 0x" + Handle .ToString("x8") + ", " +
+ "Priority = 0x" + Priority.ToString("x8"));
+
+ KThread Thread = GetThread(ThreadState.Tpidr, Handle);
+
+ if (Thread != null)
+ {
+ Thread.SetPriority(Priority);
+
+ ThreadState.X0 = 0;
+ }
+ else
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+ }
+ }
+
+ private void SvcGetThreadCoreMask(AThreadState ThreadState)
+ {
+ int Handle = (int)ThreadState.X2;
+
+ Device.Log.PrintDebug(LogClass.KernelSvc, "Handle = 0x" + Handle.ToString("x8"));
+
+ KThread Thread = GetThread(ThreadState.Tpidr, Handle);
+
+ if (Thread != null)
+ {
+ ThreadState.X0 = 0;
+ ThreadState.X1 = (ulong)Thread.IdealCore;
+ ThreadState.X2 = (ulong)Thread.CoreMask;
+ }
+ else
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+ }
+ }
+
+ private void SvcSetThreadCoreMask(AThreadState ThreadState)
+ {
+ int Handle = (int)ThreadState.X0;
+ int IdealCore = (int)ThreadState.X1;
+ long CoreMask = (long)ThreadState.X2;
+
+ Device.Log.PrintDebug(LogClass.KernelSvc,
+ "Handle = 0x" + Handle .ToString("x8") + ", " +
+ "IdealCore = 0x" + IdealCore.ToString("x8") + ", " +
+ "CoreMask = 0x" + CoreMask .ToString("x16"));
+
+ KThread Thread = GetThread(ThreadState.Tpidr, Handle);
+
+ if (IdealCore == -2)
+ {
+ //TODO: Get this value from the NPDM file.
+ IdealCore = 0;
+
+ CoreMask = 1 << IdealCore;
+ }
+ else
+ {
+ if ((uint)IdealCore > 3)
+ {
+ if ((IdealCore | 2) != -1)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid core id 0x{IdealCore:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidCoreId);
+
+ return;
+ }
+ }
+ else if ((CoreMask & (1 << IdealCore)) == 0)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid core mask 0x{CoreMask:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMaskValue);
+
+ return;
+ }
+ }
+
+ if (Thread == null)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+
+ return;
+ }
+
+ //-1 is used as "don't care", so the IdealCore value is ignored.
+ //-2 is used as "use NPDM default core id" (handled above).
+ //-3 is used as "don't update", the old IdealCore value is kept.
+ if (IdealCore == -3 && (CoreMask & (1 << Thread.IdealCore)) == 0)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid core mask 0x{CoreMask:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMaskValue);
+
+ return;
+ }
+
+ Process.Scheduler.ChangeCore(Thread, IdealCore, (int)CoreMask);
+
+ ThreadState.X0 = 0;
+ }
+
+ private void SvcGetCurrentProcessorNumber(AThreadState ThreadState)
+ {
+ ThreadState.X0 = (ulong)Process.GetThread(ThreadState.Tpidr).ActualCore;
+ }
+
+ private void SvcGetThreadId(AThreadState ThreadState)
+ {
+ int Handle = (int)ThreadState.X1;
+
+ KThread Thread = GetThread(ThreadState.Tpidr, Handle);
+
+ if (Thread != null)
+ {
+ ThreadState.X0 = 0;
+ ThreadState.X1 = (ulong)Thread.ThreadId;
+ }
+ else
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+ }
+ }
+
+ private void SvcSetThreadActivity(AThreadState ThreadState)
+ {
+ int Handle = (int)ThreadState.X0;
+ bool Active = (int)ThreadState.X1 == 0;
+
+ KThread Thread = Process.HandleTable.GetData<KThread>(Handle);
+
+ if (Thread != null)
+ {
+ Process.Scheduler.SetThreadActivity(Thread, Active);
+
+ ThreadState.X0 = 0;
+ }
+ else
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+ }
+ }
+
+ private void SvcGetThreadContext3(AThreadState ThreadState)
+ {
+ long Position = (long)ThreadState.X0;
+ int Handle = (int)ThreadState.X1;
+
+ KThread Thread = Process.HandleTable.GetData<KThread>(Handle);
+
+ if (Thread == null)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+
+ return;
+ }
+
+ if (Process.GetThread(ThreadState.Tpidr) == Thread)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Thread handle 0x{Handle:x8} is current thread!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidThread);
+
+ return;
+ }
+
+ Memory.WriteUInt64(Position + 0x0, ThreadState.X0);
+ Memory.WriteUInt64(Position + 0x8, ThreadState.X1);
+ Memory.WriteUInt64(Position + 0x10, ThreadState.X2);
+ Memory.WriteUInt64(Position + 0x18, ThreadState.X3);
+ Memory.WriteUInt64(Position + 0x20, ThreadState.X4);
+ Memory.WriteUInt64(Position + 0x28, ThreadState.X5);
+ Memory.WriteUInt64(Position + 0x30, ThreadState.X6);
+ Memory.WriteUInt64(Position + 0x38, ThreadState.X7);
+ Memory.WriteUInt64(Position + 0x40, ThreadState.X8);
+ Memory.WriteUInt64(Position + 0x48, ThreadState.X9);
+ Memory.WriteUInt64(Position + 0x50, ThreadState.X10);
+ Memory.WriteUInt64(Position + 0x58, ThreadState.X11);
+ Memory.WriteUInt64(Position + 0x60, ThreadState.X12);
+ Memory.WriteUInt64(Position + 0x68, ThreadState.X13);
+ Memory.WriteUInt64(Position + 0x70, ThreadState.X14);
+ Memory.WriteUInt64(Position + 0x78, ThreadState.X15);
+ Memory.WriteUInt64(Position + 0x80, ThreadState.X16);
+ Memory.WriteUInt64(Position + 0x88, ThreadState.X17);
+ Memory.WriteUInt64(Position + 0x90, ThreadState.X18);
+ Memory.WriteUInt64(Position + 0x98, ThreadState.X19);
+ Memory.WriteUInt64(Position + 0xa0, ThreadState.X20);
+ Memory.WriteUInt64(Position + 0xa8, ThreadState.X21);
+ Memory.WriteUInt64(Position + 0xb0, ThreadState.X22);
+ Memory.WriteUInt64(Position + 0xb8, ThreadState.X23);
+ Memory.WriteUInt64(Position + 0xc0, ThreadState.X24);
+ Memory.WriteUInt64(Position + 0xc8, ThreadState.X25);
+ Memory.WriteUInt64(Position + 0xd0, ThreadState.X26);
+ Memory.WriteUInt64(Position + 0xd8, ThreadState.X27);
+ Memory.WriteUInt64(Position + 0xe0, ThreadState.X28);
+ Memory.WriteUInt64(Position + 0xe8, ThreadState.X29);
+ Memory.WriteUInt64(Position + 0xf0, ThreadState.X30);
+ Memory.WriteUInt64(Position + 0xf8, ThreadState.X31);
+
+ Memory.WriteInt64(Position + 0x100, Thread.LastPc);
+
+ Memory.WriteUInt64(Position + 0x108, (ulong)ThreadState.Psr);
+
+ Memory.WriteVector128(Position + 0x110, ThreadState.V0);
+ Memory.WriteVector128(Position + 0x120, ThreadState.V1);
+ Memory.WriteVector128(Position + 0x130, ThreadState.V2);
+ Memory.WriteVector128(Position + 0x140, ThreadState.V3);
+ Memory.WriteVector128(Position + 0x150, ThreadState.V4);
+ Memory.WriteVector128(Position + 0x160, ThreadState.V5);
+ Memory.WriteVector128(Position + 0x170, ThreadState.V6);
+ Memory.WriteVector128(Position + 0x180, ThreadState.V7);
+ Memory.WriteVector128(Position + 0x190, ThreadState.V8);
+ Memory.WriteVector128(Position + 0x1a0, ThreadState.V9);
+ Memory.WriteVector128(Position + 0x1b0, ThreadState.V10);
+ Memory.WriteVector128(Position + 0x1c0, ThreadState.V11);
+ Memory.WriteVector128(Position + 0x1d0, ThreadState.V12);
+ Memory.WriteVector128(Position + 0x1e0, ThreadState.V13);
+ Memory.WriteVector128(Position + 0x1f0, ThreadState.V14);
+ Memory.WriteVector128(Position + 0x200, ThreadState.V15);
+ Memory.WriteVector128(Position + 0x210, ThreadState.V16);
+ Memory.WriteVector128(Position + 0x220, ThreadState.V17);
+ Memory.WriteVector128(Position + 0x230, ThreadState.V18);
+ Memory.WriteVector128(Position + 0x240, ThreadState.V19);
+ Memory.WriteVector128(Position + 0x250, ThreadState.V20);
+ Memory.WriteVector128(Position + 0x260, ThreadState.V21);
+ Memory.WriteVector128(Position + 0x270, ThreadState.V22);
+ Memory.WriteVector128(Position + 0x280, ThreadState.V23);
+ Memory.WriteVector128(Position + 0x290, ThreadState.V24);
+ Memory.WriteVector128(Position + 0x2a0, ThreadState.V25);
+ Memory.WriteVector128(Position + 0x2b0, ThreadState.V26);
+ Memory.WriteVector128(Position + 0x2c0, ThreadState.V27);
+ Memory.WriteVector128(Position + 0x2d0, ThreadState.V28);
+ Memory.WriteVector128(Position + 0x2e0, ThreadState.V29);
+ Memory.WriteVector128(Position + 0x2f0, ThreadState.V30);
+ Memory.WriteVector128(Position + 0x300, ThreadState.V31);
+
+ Memory.WriteInt32(Position + 0x310, ThreadState.Fpcr);
+ Memory.WriteInt32(Position + 0x314, ThreadState.Fpsr);
+ Memory.WriteInt64(Position + 0x318, ThreadState.Tpidr);
+
+ ThreadState.X0 = 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/SvcThreadSync.cs b/Ryujinx.HLE/HOS/Kernel/SvcThreadSync.cs
new file mode 100644
index 00000000..7097d0f7
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/SvcThreadSync.cs
@@ -0,0 +1,523 @@
+using ChocolArm64.State;
+using Ryujinx.HLE.Logging;
+using System;
+
+using static Ryujinx.HLE.HOS.ErrorCode;
+
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ partial class SvcHandler
+ {
+ private const int MutexHasListenersMask = 0x40000000;
+
+ private void SvcArbitrateLock(AThreadState ThreadState)
+ {
+ int OwnerThreadHandle = (int)ThreadState.X0;
+ long MutexAddress = (long)ThreadState.X1;
+ int WaitThreadHandle = (int)ThreadState.X2;
+
+ Device.Log.PrintDebug(LogClass.KernelSvc,
+ "OwnerThreadHandle = 0x" + OwnerThreadHandle.ToString("x8") + ", " +
+ "MutexAddress = 0x" + MutexAddress .ToString("x16") + ", " +
+ "WaitThreadHandle = 0x" + WaitThreadHandle .ToString("x8"));
+
+ if (IsPointingInsideKernel(MutexAddress))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid mutex address 0x{MutexAddress:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+
+ return;
+ }
+
+ if (IsAddressNotWordAligned(MutexAddress))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Unaligned mutex address 0x{MutexAddress:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
+
+ return;
+ }
+
+ KThread OwnerThread = Process.HandleTable.GetData<KThread>(OwnerThreadHandle);
+
+ if (OwnerThread == null)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid owner thread handle 0x{OwnerThreadHandle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+
+ return;
+ }
+
+ KThread WaitThread = Process.HandleTable.GetData<KThread>(WaitThreadHandle);
+
+ if (WaitThread == null)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid requesting thread handle 0x{WaitThreadHandle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+
+ return;
+ }
+
+ KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
+
+ MutexLock(CurrThread, WaitThread, OwnerThreadHandle, WaitThreadHandle, MutexAddress);
+
+ ThreadState.X0 = 0;
+ }
+
+ private void SvcArbitrateUnlock(AThreadState ThreadState)
+ {
+ long MutexAddress = (long)ThreadState.X0;
+
+ Device.Log.PrintDebug(LogClass.KernelSvc, "MutexAddress = 0x" + MutexAddress.ToString("x16"));
+
+ if (IsPointingInsideKernel(MutexAddress))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid mutex address 0x{MutexAddress:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+
+ return;
+ }
+
+ if (IsAddressNotWordAligned(MutexAddress))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Unaligned mutex address 0x{MutexAddress:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
+
+ return;
+ }
+
+ MutexUnlock(Process.GetThread(ThreadState.Tpidr), MutexAddress);
+
+ ThreadState.X0 = 0;
+ }
+
+ private void SvcWaitProcessWideKeyAtomic(AThreadState ThreadState)
+ {
+ long MutexAddress = (long)ThreadState.X0;
+ long CondVarAddress = (long)ThreadState.X1;
+ int ThreadHandle = (int)ThreadState.X2;
+ ulong Timeout = ThreadState.X3;
+
+ Device.Log.PrintDebug(LogClass.KernelSvc,
+ "MutexAddress = 0x" + MutexAddress .ToString("x16") + ", " +
+ "CondVarAddress = 0x" + CondVarAddress.ToString("x16") + ", " +
+ "ThreadHandle = 0x" + ThreadHandle .ToString("x8") + ", " +
+ "Timeout = 0x" + Timeout .ToString("x16"));
+
+ if (IsPointingInsideKernel(MutexAddress))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid mutex address 0x{MutexAddress:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+
+ return;
+ }
+
+ if (IsAddressNotWordAligned(MutexAddress))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Unaligned mutex address 0x{MutexAddress:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
+
+ return;
+ }
+
+ KThread Thread = Process.HandleTable.GetData<KThread>(ThreadHandle);
+
+ if (Thread == null)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{ThreadHandle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+
+ return;
+ }
+
+ KThread WaitThread = Process.GetThread(ThreadState.Tpidr);
+
+ if (!CondVarWait(WaitThread, ThreadHandle, MutexAddress, CondVarAddress, Timeout))
+ {
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.Timeout);
+
+ return;
+ }
+
+ ThreadState.X0 = 0;
+ }
+
+ private void SvcSignalProcessWideKey(AThreadState ThreadState)
+ {
+ long CondVarAddress = (long)ThreadState.X0;
+ int Count = (int)ThreadState.X1;
+
+ Device.Log.PrintDebug(LogClass.KernelSvc,
+ "CondVarAddress = 0x" + CondVarAddress.ToString("x16") + ", " +
+ "Count = 0x" + Count .ToString("x8"));
+
+ KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
+
+ CondVarSignal(ThreadState, CurrThread, CondVarAddress, Count);
+
+ ThreadState.X0 = 0;
+ }
+
+ private void MutexLock(
+ KThread CurrThread,
+ KThread WaitThread,
+ int OwnerThreadHandle,
+ int WaitThreadHandle,
+ long MutexAddress)
+ {
+ lock (Process.ThreadSyncLock)
+ {
+ int MutexValue = Memory.ReadInt32(MutexAddress);
+
+ Device.Log.PrintDebug(LogClass.KernelSvc, "MutexValue = 0x" + MutexValue.ToString("x8"));
+
+ if (MutexValue != (OwnerThreadHandle | MutexHasListenersMask))
+ {
+ return;
+ }
+
+ CurrThread.WaitHandle = WaitThreadHandle;
+ CurrThread.MutexAddress = MutexAddress;
+
+ InsertWaitingMutexThreadUnsafe(OwnerThreadHandle, WaitThread);
+ }
+
+ Device.Log.PrintDebug(LogClass.KernelSvc, "Entering wait state...");
+
+ Process.Scheduler.EnterWait(CurrThread);
+ }
+
+ private void SvcWaitForAddress(AThreadState ThreadState)
+ {
+ long Address = (long)ThreadState.X0;
+ ArbitrationType Type = (ArbitrationType)ThreadState.X1;
+ int Value = (int)ThreadState.X2;
+ ulong Timeout = ThreadState.X3;
+
+ Device.Log.PrintDebug(LogClass.KernelSvc,
+ "Address = 0x" + Address.ToString("x16") + ", " +
+ "ArbitrationType = 0x" + Type .ToString() + ", " +
+ "Value = 0x" + Value .ToString("x8") + ", " +
+ "Timeout = 0x" + Timeout.ToString("x16"));
+
+ if (IsPointingInsideKernel(Address))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid address 0x{Address:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+
+ return;
+ }
+
+ if (IsAddressNotWordAligned(Address))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Unaligned address 0x{Address:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
+
+ return;
+ }
+
+ switch (Type)
+ {
+ case ArbitrationType.WaitIfLessThan:
+ ThreadState.X0 = AddressArbiter.WaitForAddressIfLessThan(Process, ThreadState, Memory, Address, Value, Timeout, false);
+ break;
+
+ case ArbitrationType.DecrementAndWaitIfLessThan:
+ ThreadState.X0 = AddressArbiter.WaitForAddressIfLessThan(Process, ThreadState, Memory, Address, Value, Timeout, true);
+ break;
+
+ case ArbitrationType.WaitIfEqual:
+ ThreadState.X0 = AddressArbiter.WaitForAddressIfEqual(Process, ThreadState, Memory, Address, Value, Timeout);
+ break;
+
+ default:
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidEnumValue);
+ break;
+ }
+ }
+
+ private void MutexUnlock(KThread CurrThread, long MutexAddress)
+ {
+ lock (Process.ThreadSyncLock)
+ {
+ //This is the new thread that will now own the mutex.
+ //If no threads are waiting for the lock, then it should be null.
+ (KThread OwnerThread, int Count) = PopMutexThreadUnsafe(CurrThread, MutexAddress);
+
+ if (OwnerThread == CurrThread)
+ {
+ throw new InvalidOperationException();
+ }
+
+ if (OwnerThread != null)
+ {
+ //Remove all waiting mutex from the old owner,
+ //and insert then on the new owner.
+ UpdateMutexOwnerUnsafe(CurrThread, OwnerThread, MutexAddress);
+
+ CurrThread.UpdatePriority();
+
+ int HasListeners = Count >= 2 ? MutexHasListenersMask : 0;
+
+ Memory.WriteInt32ToSharedAddr(MutexAddress, HasListeners | OwnerThread.WaitHandle);
+
+ OwnerThread.WaitHandle = 0;
+ OwnerThread.MutexAddress = 0;
+ OwnerThread.CondVarAddress = 0;
+ OwnerThread.MutexOwner = null;
+
+ OwnerThread.UpdatePriority();
+
+ Process.Scheduler.WakeUp(OwnerThread);
+
+ Device.Log.PrintDebug(LogClass.KernelSvc, "Gave mutex to thread id " + OwnerThread.ThreadId + "!");
+ }
+ else
+ {
+ Memory.WriteInt32ToSharedAddr(MutexAddress, 0);
+
+ Device.Log.PrintDebug(LogClass.KernelSvc, "No threads waiting mutex!");
+ }
+ }
+ }
+
+ private bool CondVarWait(
+ KThread WaitThread,
+ int WaitThreadHandle,
+ long MutexAddress,
+ long CondVarAddress,
+ ulong Timeout)
+ {
+ WaitThread.WaitHandle = WaitThreadHandle;
+ WaitThread.MutexAddress = MutexAddress;
+ WaitThread.CondVarAddress = CondVarAddress;
+
+ lock (Process.ThreadSyncLock)
+ {
+ MutexUnlock(WaitThread, MutexAddress);
+
+ WaitThread.CondVarSignaled = false;
+
+ Process.ThreadArbiterList.Add(WaitThread);
+ }
+
+ Device.Log.PrintDebug(LogClass.KernelSvc, "Entering wait state...");
+
+ if (Timeout != ulong.MaxValue)
+ {
+ Process.Scheduler.EnterWait(WaitThread, NsTimeConverter.GetTimeMs(Timeout));
+
+ lock (Process.ThreadSyncLock)
+ {
+ if (!WaitThread.CondVarSignaled || WaitThread.MutexOwner != null)
+ {
+ if (WaitThread.MutexOwner != null)
+ {
+ WaitThread.MutexOwner.MutexWaiters.Remove(WaitThread);
+ WaitThread.MutexOwner.UpdatePriority();
+
+ WaitThread.MutexOwner = null;
+ }
+
+ Process.ThreadArbiterList.Remove(WaitThread);
+
+ Device.Log.PrintDebug(LogClass.KernelSvc, "Timed out...");
+
+ return false;
+ }
+ }
+ }
+ else
+ {
+ Process.Scheduler.EnterWait(WaitThread);
+ }
+
+ return true;
+ }
+
+ private void CondVarSignal(
+ AThreadState ThreadState,
+ KThread CurrThread,
+ long CondVarAddress,
+ int Count)
+ {
+ lock (Process.ThreadSyncLock)
+ {
+ while (Count == -1 || Count-- > 0)
+ {
+ KThread WaitThread = PopCondVarThreadUnsafe(CondVarAddress);
+
+ if (WaitThread == null)
+ {
+ Device.Log.PrintDebug(LogClass.KernelSvc, "No more threads to wake up!");
+
+ break;
+ }
+
+ WaitThread.CondVarSignaled = true;
+
+ long MutexAddress = WaitThread.MutexAddress;
+
+ Memory.SetExclusive(ThreadState, MutexAddress);
+
+ int MutexValue = Memory.ReadInt32(MutexAddress);
+
+ while (MutexValue != 0)
+ {
+ if (Memory.TestExclusive(ThreadState, MutexAddress))
+ {
+ //Wait until the lock is released.
+ InsertWaitingMutexThreadUnsafe(MutexValue & ~MutexHasListenersMask, WaitThread);
+
+ Memory.WriteInt32(MutexAddress, MutexValue | MutexHasListenersMask);
+
+ Memory.ClearExclusiveForStore(ThreadState);
+
+ break;
+ }
+
+ Memory.SetExclusive(ThreadState, MutexAddress);
+
+ MutexValue = Memory.ReadInt32(MutexAddress);
+ }
+
+ Device.Log.PrintDebug(LogClass.KernelSvc, "MutexValue = 0x" + MutexValue.ToString("x8"));
+
+ if (MutexValue == 0)
+ {
+ //Give the lock to this thread.
+ Memory.WriteInt32ToSharedAddr(MutexAddress, WaitThread.WaitHandle);
+
+ WaitThread.WaitHandle = 0;
+ WaitThread.MutexAddress = 0;
+ WaitThread.CondVarAddress = 0;
+
+ WaitThread.MutexOwner?.UpdatePriority();
+
+ WaitThread.MutexOwner = null;
+
+ Process.Scheduler.WakeUp(WaitThread);
+ }
+ }
+ }
+ }
+
+ private void UpdateMutexOwnerUnsafe(KThread CurrThread, KThread NewOwner, long MutexAddress)
+ {
+ //Go through all threads waiting for the mutex,
+ //and update the MutexOwner field to point to the new owner.
+ for (int Index = 0; Index < CurrThread.MutexWaiters.Count; Index++)
+ {
+ KThread Thread = CurrThread.MutexWaiters[Index];
+
+ if (Thread.MutexAddress == MutexAddress)
+ {
+ CurrThread.MutexWaiters.RemoveAt(Index--);
+
+ InsertWaitingMutexThreadUnsafe(NewOwner, Thread);
+ }
+ }
+ }
+
+ private void InsertWaitingMutexThreadUnsafe(int OwnerThreadHandle, KThread WaitThread)
+ {
+ KThread OwnerThread = Process.HandleTable.GetData<KThread>(OwnerThreadHandle);
+
+ if (OwnerThread == null)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{OwnerThreadHandle:x8}!");
+
+ return;
+ }
+
+ InsertWaitingMutexThreadUnsafe(OwnerThread, WaitThread);
+ }
+
+ private void InsertWaitingMutexThreadUnsafe(KThread OwnerThread, KThread WaitThread)
+ {
+ WaitThread.MutexOwner = OwnerThread;
+
+ if (!OwnerThread.MutexWaiters.Contains(WaitThread))
+ {
+ OwnerThread.MutexWaiters.Add(WaitThread);
+
+ OwnerThread.UpdatePriority();
+ }
+ }
+
+ private (KThread, int) PopMutexThreadUnsafe(KThread OwnerThread, long MutexAddress)
+ {
+ int Count = 0;
+
+ KThread WakeThread = null;
+
+ foreach (KThread Thread in OwnerThread.MutexWaiters)
+ {
+ if (Thread.MutexAddress != MutexAddress)
+ {
+ continue;
+ }
+
+ if (WakeThread == null || Thread.ActualPriority < WakeThread.ActualPriority)
+ {
+ WakeThread = Thread;
+ }
+
+ Count++;
+ }
+
+ if (WakeThread != null)
+ {
+ OwnerThread.MutexWaiters.Remove(WakeThread);
+ }
+
+ return (WakeThread, Count);
+ }
+
+ private KThread PopCondVarThreadUnsafe(long CondVarAddress)
+ {
+ KThread WakeThread = null;
+
+ foreach (KThread Thread in Process.ThreadArbiterList)
+ {
+ if (Thread.CondVarAddress != CondVarAddress)
+ {
+ continue;
+ }
+
+ if (WakeThread == null || Thread.ActualPriority < WakeThread.ActualPriority)
+ {
+ WakeThread = Thread;
+ }
+ }
+
+ if (WakeThread != null)
+ {
+ Process.ThreadArbiterList.Remove(WakeThread);
+ }
+
+ return WakeThread;
+ }
+
+ private bool IsPointingInsideKernel(long Address)
+ {
+ return ((ulong)Address + 0x1000000000) < 0xffffff000;
+ }
+
+ private bool IsAddressNotWordAligned(long Address)
+ {
+ return (Address & 3) != 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/ThreadQueue.cs b/Ryujinx.HLE/HOS/Kernel/ThreadQueue.cs
new file mode 100644
index 00000000..815e86ad
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/ThreadQueue.cs
@@ -0,0 +1,158 @@
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ class ThreadQueue
+ {
+ private const int LowestPriority = 0x3f;
+
+ private SchedulerThread Head;
+
+ private object ListLock;
+
+ public ThreadQueue()
+ {
+ ListLock = new object();
+ }
+
+ public void Push(SchedulerThread Wait)
+ {
+ lock (ListLock)
+ {
+ //Ensure that we're not creating circular references
+ //by adding a thread that is already on the list.
+ if (HasThread(Wait))
+ {
+ return;
+ }
+
+ if (Head == null || Head.Thread.ActualPriority >= Wait.Thread.ActualPriority)
+ {
+ Wait.Next = Head;
+
+ Head = Wait;
+
+ return;
+ }
+
+ SchedulerThread Curr = Head;
+
+ while (Curr.Next != null)
+ {
+ if (Curr.Next.Thread.ActualPriority >= Wait.Thread.ActualPriority)
+ {
+ break;
+ }
+
+ Curr = Curr.Next;
+ }
+
+ Wait.Next = Curr.Next;
+ Curr.Next = Wait;
+ }
+ }
+
+ public SchedulerThread Pop(int Core, int MinPriority = LowestPriority)
+ {
+ lock (ListLock)
+ {
+ int CoreMask = 1 << Core;
+
+ SchedulerThread Prev = null;
+ SchedulerThread Curr = Head;
+
+ while (Curr != null)
+ {
+ KThread Thread = Curr.Thread;
+
+ if (Thread.ActualPriority <= MinPriority && (Thread.CoreMask & CoreMask) != 0)
+ {
+ if (Prev != null)
+ {
+ Prev.Next = Curr.Next;
+ }
+ else
+ {
+ Head = Head.Next;
+ }
+
+ break;
+ }
+
+ Prev = Curr;
+ Curr = Curr.Next;
+ }
+
+ return Curr;
+ }
+ }
+
+ public bool Remove(SchedulerThread Thread)
+ {
+ lock (ListLock)
+ {
+ if (Head == null)
+ {
+ return false;
+ }
+ else if (Head == Thread)
+ {
+ Head = Head.Next;
+
+ return true;
+ }
+
+ SchedulerThread Prev = Head;
+ SchedulerThread Curr = Head.Next;
+
+ while (Curr != null)
+ {
+ if (Curr == Thread)
+ {
+ Prev.Next = Curr.Next;
+
+ return true;
+ }
+
+ Prev = Curr;
+ Curr = Curr.Next;
+ }
+
+ return false;
+ }
+ }
+
+ public bool Resort(SchedulerThread Thread)
+ {
+ lock (ListLock)
+ {
+ if (Remove(Thread))
+ {
+ Push(Thread);
+
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+ public bool HasThread(SchedulerThread Thread)
+ {
+ lock (ListLock)
+ {
+ SchedulerThread Curr = Head;
+
+ while (Curr != null)
+ {
+ if (Curr == Thread)
+ {
+ return true;
+ }
+
+ Curr = Curr.Next;
+ }
+
+ return false;
+ }
+ }
+ }
+} \ No newline at end of file