diff options
| author | gdkchan <gab.dark.100@gmail.com> | 2018-08-16 20:47:36 -0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2018-08-16 20:47:36 -0300 |
| commit | 521751795a1c97c0d97f6f8904a3be69b13d3a9d (patch) | |
| tree | 942a05899c40e2de6d92a38b93a494bd96ee64b8 /Ryujinx.HLE/HOS/Kernel | |
| parent | 182d716867ae477c2b15a5332430dc2641fa1cc3 (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')
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 |
