From 22bacc618815170c0d186a82e1ea4558e36b7063 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Fri, 18 Jan 2019 20:26:39 -0200 Subject: [PATCH] Improve kernel IPC implementation (#550) * Implement some IPC related kernel SVCs properly * Fix BLZ decompression when the segment also has a uncompressed chunck * Set default cpu core on process start from ProgramLoader, remove debug message * Load process capabilities properly on KIPs * Fix a copy/paste error in UnmapPhysicalMemory64 * Implement smarter switching between old and new IPC system to support the old HLE services implementation without the manual switch * Implement RegisterService on sm and AcceptSession (partial) * Misc fixes and improvements on new IPC methods * Move IPC related SVCs into a separate file, and logging on RegisterService (sm) * Some small fixes related to receive list buffers and error cases * Load NSOs using the correct pool partition * Fix corner case on GetMaskFromMinMax where range is 64, doesn't happen in pratice however * Fix send static buffer copy * Session release, implement closing requests on client disconnect * Implement ConnectToPort SVC * KLightSession init --- Ryujinx.HLE/DeviceMemory.cs | 57 + Ryujinx.HLE/HOS/Horizon.cs | 15 +- Ryujinx.HLE/HOS/Ipc/IpcHandler.cs | 12 +- Ryujinx.HLE/HOS/Kernel/Common/KAutoObject.cs | 21 + .../HOS/Kernel/Common/KResourceLimit.cs | 14 +- .../Kernel/Common/KSynchronizationObject.cs | 2 +- Ryujinx.HLE/HOS/Kernel/Common/KernelResult.cs | 57 +- .../HOS/Kernel/Common/KernelTransfer.cs | 20 + Ryujinx.HLE/HOS/Kernel/Ipc/ChannelState.cs | 10 + .../HOS/Kernel/Ipc/KBufferDescriptor.cs | 20 + .../HOS/Kernel/Ipc/KBufferDescriptorTable.cs | 216 +++ Ryujinx.HLE/HOS/Kernel/Ipc/KClientPort.cs | 110 +- Ryujinx.HLE/HOS/Kernel/Ipc/KClientSession.cs | 60 + .../HOS/Kernel/Ipc/KLightClientSession.cs | 14 + .../HOS/Kernel/Ipc/KLightServerSession.cs | 14 + Ryujinx.HLE/HOS/Kernel/Ipc/KLightSession.cs | 20 + Ryujinx.HLE/HOS/Kernel/Ipc/KPort.cs | 65 +- Ryujinx.HLE/HOS/Kernel/Ipc/KServerPort.cs | 75 +- Ryujinx.HLE/HOS/Kernel/Ipc/KServerSession.cs | 1262 ++++++++++++++++ Ryujinx.HLE/HOS/Kernel/Ipc/KSession.cs | 50 +- Ryujinx.HLE/HOS/Kernel/Ipc/KSessionRequest.cs | 31 + Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlock.cs | 106 +- Ryujinx.HLE/HOS/Kernel/Memory/KMemoryInfo.cs | 31 +- .../HOS/Kernel/Memory/KMemoryManager.cs | 1314 +++++++++++++---- .../HOS/Kernel/Memory/KMemoryRegionManager.cs | 291 ++-- .../HOS/Kernel/Memory/KSharedMemory.cs | 5 +- .../HOS/Kernel/Memory/KTransferMemory.cs | 6 +- .../HOS/Kernel/Process/KHandleEntry.cs | 6 +- .../HOS/Kernel/Process/KHandleTable.cs | 89 +- Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs | 20 +- .../Kernel/Process/KProcessCapabilities.cs | 5 + .../HOS/Kernel/SupervisorCall/SvcHandler.cs | 23 - .../HOS/Kernel/SupervisorCall/SvcIpc.cs | 532 +++++++ .../HOS/Kernel/SupervisorCall/SvcMemory.cs | 4 +- .../HOS/Kernel/SupervisorCall/SvcSystem.cs | 181 +-- .../HOS/Kernel/SupervisorCall/SvcTable.cs | 6 +- .../HOS/Kernel/SupervisorCall/SvcThread.cs | 32 +- Ryujinx.HLE/HOS/Kernel/Threading/KEvent.cs | 2 +- .../HOS/Kernel/Threading/KScheduler.cs | 20 + .../HOS/Kernel/Threading/KSynchronization.cs | 2 +- Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs | 64 +- .../HOS/Kernel/Threading/KWritableEvent.cs | 4 +- Ryujinx.HLE/HOS/ProgramLoader.cs | 21 +- Ryujinx.HLE/HOS/ServiceCtx.cs | 32 +- Ryujinx.HLE/HOS/Services/IpcService.cs | 10 +- Ryujinx.HLE/HOS/Services/Psm/IPsmSession.cs | 2 +- Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs | 116 +- Ryujinx.HLE/HOS/Services/Sm/SmErr.cs | 9 + .../Loaders/Compression/BackwardsLz.cs | 60 +- .../Executables/KernelInitialProcess.cs | 10 +- .../Loaders/Npdm/ServiceAccessControl.cs | 2 +- 51 files changed, 4310 insertions(+), 840 deletions(-) create mode 100644 Ryujinx.HLE/HOS/Kernel/Ipc/ChannelState.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptor.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptorTable.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Ipc/KClientSession.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Ipc/KLightClientSession.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Ipc/KLightServerSession.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Ipc/KLightSession.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Ipc/KServerSession.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Ipc/KSessionRequest.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcIpc.cs create mode 100644 Ryujinx.HLE/HOS/Services/Sm/SmErr.cs diff --git a/Ryujinx.HLE/DeviceMemory.cs b/Ryujinx.HLE/DeviceMemory.cs index 3db89c72..310942b8 100644 --- a/Ryujinx.HLE/DeviceMemory.cs +++ b/Ryujinx.HLE/DeviceMemory.cs @@ -113,6 +113,63 @@ namespace Ryujinx.HLE } } + public void Set(ulong address, byte value, ulong size) + { + if (address + size < address) + { + throw new ArgumentOutOfRangeException(nameof(size)); + } + + if (address + size > RamSize) + { + throw new ArgumentOutOfRangeException(nameof(address)); + } + + ulong size8 = size & ~7UL; + + ulong valueRep = (ulong)value * 0x0101010101010101; + + for (ulong offs = 0; offs < size8; offs += 8) + { + WriteUInt64((long)(address + offs), valueRep); + } + + for (ulong offs = size8; offs < (size - size8); offs++) + { + WriteByte((long)(address + offs), value); + } + } + + public void Copy(ulong dst, ulong src, ulong size) + { + if (dst + size < dst || src + size < src) + { + throw new ArgumentOutOfRangeException(nameof(size)); + } + + if (dst + size > RamSize) + { + throw new ArgumentOutOfRangeException(nameof(dst)); + } + + if (src + size > RamSize) + { + throw new ArgumentOutOfRangeException(nameof(src)); + } + + ulong size8 = size & ~7UL; + + for (ulong offs = 0; offs < size8; offs += 8) + { + WriteUInt64((long)(dst + offs), ReadUInt64((long)(src + offs))); + } + + for (ulong offs = size8; offs < (size - size8); offs++) + { + WriteByte((long)(dst + offs), ReadByte((long)(src + offs))); + } + } + public void Dispose() { Dispose(true); diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs index 94b2e6c6..6b5f5723 100644 --- a/Ryujinx.HLE/HOS/Horizon.cs +++ b/Ryujinx.HLE/HOS/Horizon.cs @@ -7,6 +7,7 @@ using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Memory; using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Sm; using Ryujinx.HLE.HOS.SystemState; using Ryujinx.HLE.Loaders.Executables; using Ryujinx.HLE.Loaders.Npdm; @@ -157,8 +158,8 @@ namespace Ryujinx.HLE.HOS hidPageList .AddRange(hidPa, HidSize / KMemoryManager.PageSize); fontPageList.AddRange(fontPa, FontSize / KMemoryManager.PageSize); - HidSharedMem = new KSharedMemory(hidPageList, 0, 0, MemoryPermission.Read); - FontSharedMem = new KSharedMemory(fontPageList, 0, 0, MemoryPermission.Read); + HidSharedMem = new KSharedMemory(this, hidPageList, 0, 0, MemoryPermission.Read); + FontSharedMem = new KSharedMemory(this, fontPageList, 0, 0, MemoryPermission.Read); AppletState = new AppletStateMgr(this); @@ -166,6 +167,8 @@ namespace Ryujinx.HLE.HOS Font = new SharedFontManager(device, (long)(fontPa - DramMemoryMap.DramBase)); + IUserInterface.InitializePort(this); + VsyncEvent = new KEvent(this); LoadKeySet(); @@ -259,6 +262,14 @@ namespace Ryujinx.HLE.HOS LoadNca(mainNca, controlNca); } + public void LoadKip(string kipFile) + { + using (FileStream fs = new FileStream(kipFile, FileMode.Open)) + { + ProgramLoader.LoadKernelInitalProcess(this, new KernelInitialProcess(fs)); + } + } + private (Nca Main, Nca Control) GetXciGameData(Xci xci) { if (xci.SecurePartition == null) diff --git a/Ryujinx.HLE/HOS/Ipc/IpcHandler.cs b/Ryujinx.HLE/HOS/Ipc/IpcHandler.cs index ecfa25ed..1eba4b41 100644 --- a/Ryujinx.HLE/HOS/Ipc/IpcHandler.cs +++ b/Ryujinx.HLE/HOS/Ipc/IpcHandler.cs @@ -10,12 +10,12 @@ namespace Ryujinx.HLE.HOS.Ipc static class IpcHandler { public static KernelResult IpcCall( - Switch device, - KProcess process, - MemoryManager memory, - KSession session, - IpcMessage request, - long cmdPtr) + Switch device, + KProcess process, + MemoryManager memory, + KClientSession session, + IpcMessage request, + long cmdPtr) { IpcMessage response = new IpcMessage(); diff --git a/Ryujinx.HLE/HOS/Kernel/Common/KAutoObject.cs b/Ryujinx.HLE/HOS/Kernel/Common/KAutoObject.cs index ddb0c71f..3b30dd80 100644 --- a/Ryujinx.HLE/HOS/Kernel/Common/KAutoObject.cs +++ b/Ryujinx.HLE/HOS/Kernel/Common/KAutoObject.cs @@ -1,12 +1,18 @@ +using System.Threading; + namespace Ryujinx.HLE.HOS.Kernel.Common { class KAutoObject { protected Horizon System; + private int _referenceCount; + public KAutoObject(Horizon system) { System = system; + + _referenceCount = 1; } public virtual KernelResult SetName(string name) @@ -38,5 +44,20 @@ namespace Ryujinx.HLE.HOS.Kernel.Common return null; } + + public void IncrementReferenceCount() + { + Interlocked.Increment(ref _referenceCount); + } + + public void DecrementReferenceCount() + { + if (Interlocked.Decrement(ref _referenceCount) == 0) + { + Destroy(); + } + } + + protected virtual void Destroy() { } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Common/KResourceLimit.cs b/Ryujinx.HLE/HOS/Kernel/Common/KResourceLimit.cs index 01bba65f..a7955d7a 100644 --- a/Ryujinx.HLE/HOS/Kernel/Common/KResourceLimit.cs +++ b/Ryujinx.HLE/HOS/Kernel/Common/KResourceLimit.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; namespace Ryujinx.HLE.HOS.Kernel.Common { - class KResourceLimit + class KResourceLimit : KAutoObject { private const int Time10SecondsMs = 10000; @@ -18,9 +18,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common private int _waitingThreadsCount; - private Horizon _system; - - public KResourceLimit(Horizon system) + public KResourceLimit(Horizon system) : base(system) { _current = new long[(int)LimitableResource.Count]; _limit = new long[(int)LimitableResource.Count]; @@ -29,8 +27,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Common _lockObj = new object(); _waitingThreads = new LinkedList(); - - _system = system; } public bool Reserve(LimitableResource resource, ulong amount) @@ -61,7 +57,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common { _waitingThreadsCount++; - KConditionVariable.Wait(_system, _waitingThreads, _lockObj, timeout); + KConditionVariable.Wait(System, _waitingThreads, _lockObj, timeout); _waitingThreadsCount--; @@ -94,7 +90,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common Release(resource, amount, amount); } - private void Release(LimitableResource resource, long usedAmount, long availableAmount) + public void Release(LimitableResource resource, long usedAmount, long availableAmount) { int index = GetIndex(resource); @@ -105,7 +101,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common if (_waitingThreadsCount > 0) { - KConditionVariable.NotifyAll(_system, _waitingThreads); + KConditionVariable.NotifyAll(System, _waitingThreads); } } } diff --git a/Ryujinx.HLE/HOS/Kernel/Common/KSynchronizationObject.cs b/Ryujinx.HLE/HOS/Kernel/Common/KSynchronizationObject.cs index 87e55312..77d8fbff 100644 --- a/Ryujinx.HLE/HOS/Kernel/Common/KSynchronizationObject.cs +++ b/Ryujinx.HLE/HOS/Kernel/Common/KSynchronizationObject.cs @@ -5,7 +5,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common { class KSynchronizationObject : KAutoObject { - public LinkedList WaitingThreads; + public LinkedList WaitingThreads { get; } public KSynchronizationObject(Horizon system) : base(system) { diff --git a/Ryujinx.HLE/HOS/Kernel/Common/KernelResult.cs b/Ryujinx.HLE/HOS/Kernel/Common/KernelResult.cs index cea24693..357b01ea 100644 --- a/Ryujinx.HLE/HOS/Kernel/Common/KernelResult.cs +++ b/Ryujinx.HLE/HOS/Kernel/Common/KernelResult.cs @@ -2,31 +2,36 @@ namespace Ryujinx.HLE.HOS.Kernel.Common { enum KernelResult { - Success = 0, - InvalidCapability = 0x1c01, - ThreadNotStarted = 0x7201, - ThreadTerminating = 0x7601, - InvalidSize = 0xca01, - InvalidAddress = 0xcc01, - OutOfResource = 0xce01, - OutOfMemory = 0xd001, - HandleTableFull = 0xd201, - InvalidMemState = 0xd401, - InvalidPermission = 0xd801, - InvalidMemRange = 0xdc01, - InvalidPriority = 0xe001, - InvalidCpuCore = 0xe201, - InvalidHandle = 0xe401, - UserCopyFailed = 0xe601, - InvalidCombination = 0xe801, - TimedOut = 0xea01, - Cancelled = 0xec01, - MaximumExceeded = 0xee01, - InvalidEnumValue = 0xf001, - NotFound = 0xf201, - InvalidThread = 0xf401, - InvalidState = 0xfa01, - ReservedValue = 0xfc01, - ResLimitExceeded = 0x10801 + Success = 0, + SessionCountExceeded = 0xe01, + InvalidCapability = 0x1c01, + ThreadNotStarted = 0x7201, + ThreadTerminating = 0x7601, + InvalidSize = 0xca01, + InvalidAddress = 0xcc01, + OutOfResource = 0xce01, + OutOfMemory = 0xd001, + HandleTableFull = 0xd201, + InvalidMemState = 0xd401, + InvalidPermission = 0xd801, + InvalidMemRange = 0xdc01, + InvalidPriority = 0xe001, + InvalidCpuCore = 0xe201, + InvalidHandle = 0xe401, + UserCopyFailed = 0xe601, + InvalidCombination = 0xe801, + TimedOut = 0xea01, + Cancelled = 0xec01, + MaximumExceeded = 0xee01, + InvalidEnumValue = 0xf001, + NotFound = 0xf201, + InvalidThread = 0xf401, + PortRemoteClosed = 0xf601, + InvalidState = 0xfa01, + ReservedValue = 0xfc01, + PortClosed = 0x10601, + ResLimitExceeded = 0x10801, + OutOfVaSpace = 0x20601, + CmdBufferTooSmall = 0x20801 } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Common/KernelTransfer.cs b/Ryujinx.HLE/HOS/Kernel/Common/KernelTransfer.cs index a29b1722..2b759140 100644 --- a/Ryujinx.HLE/HOS/Kernel/Common/KernelTransfer.cs +++ b/Ryujinx.HLE/HOS/Kernel/Common/KernelTransfer.cs @@ -22,6 +22,26 @@ namespace Ryujinx.HLE.HOS.Kernel.Common return false; } + public static bool UserToKernelInt32Array(Horizon system, ulong address, int[] values) + { + KProcess currentProcess = system.Scheduler.GetCurrentProcess(); + + for (int index = 0; index < values.Length; index++, address += 4) + { + if (currentProcess.CpuMemory.IsMapped((long)address) && + currentProcess.CpuMemory.IsMapped((long)address + 3)) + { + values[index]= currentProcess.CpuMemory.ReadInt32((long)address); + } + else + { + return false; + } + } + + return true; + } + public static bool UserToKernelString(Horizon system, ulong address, int size, out string value) { KProcess currentProcess = system.Scheduler.GetCurrentProcess(); diff --git a/Ryujinx.HLE/HOS/Kernel/Ipc/ChannelState.cs b/Ryujinx.HLE/HOS/Kernel/Ipc/ChannelState.cs new file mode 100644 index 00000000..4827384e --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Ipc/ChannelState.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Kernel.Ipc +{ + enum ChannelState + { + NotInitialized, + Open, + ClientDisconnected, + ServerDisconnected + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptor.cs b/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptor.cs new file mode 100644 index 00000000..e28244d4 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptor.cs @@ -0,0 +1,20 @@ +using Ryujinx.HLE.HOS.Kernel.Memory; + +namespace Ryujinx.HLE.HOS.Kernel.Ipc +{ + class KBufferDescriptor + { + public ulong ClientAddress { get; } + public ulong ServerAddress { get; } + public ulong Size { get; } + public MemoryState State { get; } + + public KBufferDescriptor(ulong src, ulong dst, ulong size, MemoryState state) + { + ClientAddress = src; + ServerAddress = dst; + Size = size; + State = state; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptorTable.cs b/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptorTable.cs new file mode 100644 index 00000000..ac805bee --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptorTable.cs @@ -0,0 +1,216 @@ +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Memory; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Kernel.Ipc +{ + class KBufferDescriptorTable + { + private const int MaxInternalBuffersCount = 8; + + private List _sendBufferDescriptors; + private List _receiveBufferDescriptors; + private List _exchangeBufferDescriptors; + + public KBufferDescriptorTable() + { + _sendBufferDescriptors = new List(MaxInternalBuffersCount); + _receiveBufferDescriptors = new List(MaxInternalBuffersCount); + _exchangeBufferDescriptors = new List(MaxInternalBuffersCount); + } + + public KernelResult AddSendBuffer(ulong src, ulong dst, ulong size, MemoryState state) + { + return Add(_sendBufferDescriptors, src, dst, size, state); + } + + public KernelResult AddReceiveBuffer(ulong src, ulong dst, ulong size, MemoryState state) + { + return Add(_receiveBufferDescriptors, src, dst, size, state); + } + + public KernelResult AddExchangeBuffer(ulong src, ulong dst, ulong size, MemoryState state) + { + return Add(_exchangeBufferDescriptors, src, dst, size, state); + } + + private KernelResult Add(List list, ulong src, ulong dst, ulong size, MemoryState state) + { + if (list.Count < MaxInternalBuffersCount) + { + list.Add(new KBufferDescriptor(src, dst, size, state)); + + return KernelResult.Success; + } + + return KernelResult.OutOfMemory; + } + + public KernelResult CopyBuffersToClient(KMemoryManager memoryManager) + { + KernelResult result = CopyToClient(memoryManager, _receiveBufferDescriptors); + + if (result != KernelResult.Success) + { + return result; + } + + return CopyToClient(memoryManager, _exchangeBufferDescriptors); + } + + private KernelResult CopyToClient(KMemoryManager memoryManager, List list) + { + foreach (KBufferDescriptor desc in list) + { + MemoryState stateMask; + + switch (desc.State) + { + case MemoryState.IpcBuffer0: stateMask = MemoryState.IpcSendAllowedType0; break; + case MemoryState.IpcBuffer1: stateMask = MemoryState.IpcSendAllowedType1; break; + case MemoryState.IpcBuffer3: stateMask = MemoryState.IpcSendAllowedType3; break; + + default: return KernelResult.InvalidCombination; + } + + MemoryAttribute attributeMask = MemoryAttribute.Borrowed | MemoryAttribute.Uncached; + + if (desc.State == MemoryState.IpcBuffer0) + { + attributeMask |= MemoryAttribute.DeviceMapped; + } + + ulong clientAddrTruncated = BitUtils.AlignDown(desc.ClientAddress, KMemoryManager.PageSize); + ulong clientAddrRounded = BitUtils.AlignUp (desc.ClientAddress, KMemoryManager.PageSize); + + //Check if address is not aligned, in this case we need to perform 2 copies. + if (clientAddrTruncated != clientAddrRounded) + { + ulong copySize = clientAddrRounded - desc.ClientAddress; + + if (copySize > desc.Size) + { + copySize = desc.Size; + } + + KernelResult result = memoryManager.CopyDataFromCurrentProcess( + desc.ClientAddress, + copySize, + stateMask, + stateMask, + MemoryPermission.ReadAndWrite, + attributeMask, + MemoryAttribute.None, + desc.ServerAddress); + + if (result != KernelResult.Success) + { + return result; + } + } + + ulong clientEndAddr = desc.ClientAddress + desc.Size; + ulong serverEndAddr = desc.ServerAddress + desc.Size; + + ulong clientEndAddrTruncated = BitUtils.AlignDown(clientEndAddr, KMemoryManager.PageSize); + ulong clientEndAddrRounded = BitUtils.AlignUp (clientEndAddr, KMemoryManager.PageSize); + ulong serverEndAddrTruncated = BitUtils.AlignDown(clientEndAddr, KMemoryManager.PageSize); + + if (clientEndAddrTruncated < clientAddrRounded) + { + KernelResult result = memoryManager.CopyDataToCurrentProcess( + clientEndAddrTruncated, + clientEndAddr - clientEndAddrTruncated, + serverEndAddrTruncated, + stateMask, + stateMask, + MemoryPermission.ReadAndWrite, + attributeMask, + MemoryAttribute.None); + + if (result != KernelResult.Success) + { + return result; + } + } + } + + return KernelResult.Success; + } + + public KernelResult UnmapServerBuffers(KMemoryManager memoryManager) + { + KernelResult result = UnmapServer(memoryManager, _sendBufferDescriptors); + + if (result != KernelResult.Success) + { + return result; + } + + result = UnmapServer(memoryManager, _receiveBufferDescriptors); + + if (result != KernelResult.Success) + { + return result; + } + + return UnmapServer(memoryManager, _exchangeBufferDescriptors); + } + + private KernelResult UnmapServer(KMemoryManager memoryManager, List list) + { + foreach (KBufferDescriptor descriptor in list) + { + KernelResult result = memoryManager.UnmapNoAttributeIfStateEquals( + descriptor.ServerAddress, + descriptor.Size, + descriptor.State); + + if (result != KernelResult.Success) + { + return result; + } + } + + return KernelResult.Success; + } + + public KernelResult RestoreClientBuffers(KMemoryManager memoryManager) + { + KernelResult result = RestoreClient(memoryManager, _sendBufferDescriptors); + + if (result != KernelResult.Success) + { + return result; + } + + result = RestoreClient(memoryManager, _receiveBufferDescriptors); + + if (result != KernelResult.Success) + { + return result; + } + + return RestoreClient(memoryManager, _exchangeBufferDescriptors); + } + + private KernelResult RestoreClient(KMemoryManager memoryManager, List list) + { + foreach (KBufferDescriptor descriptor in list) + { + KernelResult result = memoryManager.UnmapIpcRestorePermission( + descriptor.ClientAddress, + descriptor.Size, + descriptor.State); + + if (result != KernelResult.Success) + { + return result; + } + } + + return KernelResult.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Ipc/KClientPort.cs b/Ryujinx.HLE/HOS/Kernel/Ipc/KClientPort.cs index ddfe2096..eaa4322d 100644 --- a/Ryujinx.HLE/HOS/Kernel/Ipc/KClientPort.cs +++ b/Ryujinx.HLE/HOS/Kernel/Ipc/KClientPort.cs @@ -1,4 +1,6 @@ using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Services; namespace Ryujinx.HLE.HOS.Kernel.Ipc { @@ -10,12 +12,116 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc private KPort _parent; - public KClientPort(Horizon system) : base(system) { } + public bool IsLight => _parent.IsLight; - public void Initialize(KPort parent, int maxSessions) + private object _countIncLock; + + //TODO: Remove that, we need it for now to allow HLE + //SM implementation to work with the new IPC system. + public IpcService Service { get; set; } + + public KClientPort(Horizon system, KPort parent, int maxSessions) : base(system) { _maxSessions = maxSessions; _parent = parent; + + _countIncLock = new object(); + } + + public KernelResult Connect(out KClientSession clientSession) + { + clientSession = null; + + KProcess currentProcess = System.Scheduler.GetCurrentProcess(); + + if (currentProcess.ResourceLimit != null && + !currentProcess.ResourceLimit.Reserve(LimitableResource.Session, 1)) + { + return KernelResult.ResLimitExceeded; + } + + lock (_countIncLock) + { + if (_sessionsCount < _maxSessions) + { + _sessionsCount++; + } + else + { + currentProcess.ResourceLimit?.Release(LimitableResource.Session, 1); + + return KernelResult.SessionCountExceeded; + } + + if (_currentCapacity < _sessionsCount) + { + _currentCapacity = _sessionsCount; + } + } + + KSession session = new KSession(System); + + if (Service != null) + { + session.ClientSession.Service = Service; + } + + KernelResult result = _parent.EnqueueIncomingSession(session.ServerSession); + + if (result != KernelResult.Success) + { + session.ClientSession.DecrementReferenceCount(); + session.ServerSession.DecrementReferenceCount(); + + return result; + } + + clientSession = session.ClientSession; + + return result; + } + + public KernelResult ConnectLight(out KLightClientSession clientSession) + { + clientSession = null; + + KProcess currentProcess = System.Scheduler.GetCurrentProcess(); + + if (currentProcess.ResourceLimit != null && + !currentProcess.ResourceLimit.Reserve(LimitableResource.Session, 1)) + { + return KernelResult.ResLimitExceeded; + } + + lock (_countIncLock) + { + if (_sessionsCount < _maxSessions) + { + _sessionsCount++; + } + else + { + currentProcess.ResourceLimit?.Release(LimitableResource.Session, 1); + + return KernelResult.SessionCountExceeded; + } + } + + KLightSession session = new KLightSession(System); + + KernelResult result = _parent.EnqueueIncomingLightSession(session.ServerSession); + + if (result != KernelResult.Success) + { + session.ClientSession.DecrementReferenceCount(); + session.ServerSession.DecrementReferenceCount(); + + return result; + } + + clientSession = session.ClientSession; + + return result; } public new static KernelResult RemoveName(Horizon system, string name) diff --git a/Ryujinx.HLE/HOS/Kernel/Ipc/KClientSession.cs b/Ryujinx.HLE/HOS/Kernel/Ipc/KClientSession.cs new file mode 100644 index 00000000..f139d3d4 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Ipc/KClientSession.cs @@ -0,0 +1,60 @@ +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services; + +namespace Ryujinx.HLE.HOS.Kernel.Ipc +{ + class KClientSession : KSynchronizationObject + { + public KProcess CreatorProcess { get; } + + private KSession _parent; + + public ChannelState State { get; set; } + + //TODO: Remove that, we need it for now to allow HLE + //services implementation to work with the new IPC system. + public IpcService Service { get; set; } + + public KClientSession(Horizon system, KSession parent) : base(system) + { + _parent = parent; + + State = ChannelState.Open; + + CreatorProcess = system.Scheduler.GetCurrentProcess(); + + CreatorProcess.IncrementReferenceCount(); + } + + public KernelResult SendSyncRequest(ulong customCmdBuffAddr = 0, ulong customCmdBuffSize = 0) + { + KThread currentThread = System.Scheduler.GetCurrentThread(); + + KSessionRequest request = new KSessionRequest(currentThread, customCmdBuffAddr, customCmdBuffSize); + + System.CriticalSection.Enter(); + + currentThread.SignaledObj = null; + currentThread.ObjSyncResult = KernelResult.Success; + + KernelResult result = _parent.ServerSession.EnqueueRequest(request); + + System.CriticalSection.Leave(); + + if (result == KernelResult.Success) + { + result = currentThread.ObjSyncResult; + } + + return result; + } + + protected override void Destroy() + { + _parent.DisconnectClient(); + _parent.DecrementReferenceCount(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Ipc/KLightClientSession.cs b/Ryujinx.HLE/HOS/Kernel/Ipc/KLightClientSession.cs new file mode 100644 index 00000000..62c352bf --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Ipc/KLightClientSession.cs @@ -0,0 +1,14 @@ +using Ryujinx.HLE.HOS.Kernel.Common; + +namespace Ryujinx.HLE.HOS.Kernel.Ipc +{ + class KLightClientSession : KAutoObject + { + private KLightSession _parent; + + public KLightClientSession(Horizon system, KLightSession parent) : base(system) + { + _parent = parent; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Ipc/KLightServerSession.cs b/Ryujinx.HLE/HOS/Kernel/Ipc/KLightServerSession.cs new file mode 100644 index 00000000..1ea2205d --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Ipc/KLightServerSession.cs @@ -0,0 +1,14 @@ +using Ryujinx.HLE.HOS.Kernel.Common; + +namespace Ryujinx.HLE.HOS.Kernel.Ipc +{ + class KLightServerSession : KAutoObject + { + private KLightSession _parent; + + public KLightServerSession(Horizon system, KLightSession parent) : base(system) + { + _parent = parent; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Ipc/KLightSession.cs b/Ryujinx.HLE/HOS/Kernel/Ipc/KLightSession.cs new file mode 100644 index 00000000..a12a1986 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Ipc/KLightSession.cs @@ -0,0 +1,20 @@ +using Ryujinx.HLE.HOS.Kernel.Common; + +namespace Ryujinx.HLE.HOS.Kernel.Ipc +{ + class KLightSession : KAutoObject + { + public KLightServerSession ServerSession { get; } + public KLightClientSession ClientSession { get; } + + private bool _hasBeenInitialized; + + public KLightSession(Horizon system) : base(system) + { + ServerSession = new KLightServerSession(system, this); + ClientSession = new KLightClientSession(system, this); + + _hasBeenInitialized = true; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Ipc/KPort.cs b/Ryujinx.HLE/HOS/Kernel/Ipc/KPort.cs index 16e9a111..9d93cf7b 100644 --- a/Ryujinx.HLE/HOS/Kernel/Ipc/KPort.cs +++ b/Ryujinx.HLE/HOS/Kernel/Ipc/KPort.cs @@ -4,25 +4,68 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc { class KPort : KAutoObject { - public KServerPort ServerPort { get; private set; } - public KClientPort ClientPort { get; private set; } + public KServerPort ServerPort { get; } + public KClientPort ClientPort { get; } private long _nameAddress; - private bool _isLight; - public KPort(Horizon system) : base(system) + private ChannelState _state; + + public bool IsLight { get; private set; } + + public KPort(Horizon system, int maxSessions, bool isLight, long nameAddress) : base(system) { - ServerPort = new KServerPort(system); - ClientPort = new KClientPort(system); + ServerPort = new KServerPort(system, this); + ClientPort = new KClientPort(system, this, maxSessions); + + IsLight = isLight; + _nameAddress = nameAddress; + + _state = ChannelState.Open; } - public void Initialize(int maxSessions, bool isLight, long nameAddress) + public KernelResult EnqueueIncomingSession(KServerSession session) { - ServerPort.Initialize(this); - ClientPort.Initialize(this, maxSessions); + KernelResult result; - _isLight = isLight; - _nameAddress = nameAddress; + System.CriticalSection.Enter(); + + if (_state == ChannelState.Open) + { + ServerPort.EnqueueIncomingSession(session); + + result = KernelResult.Success; + } + else + { + result = KernelResult.PortClosed; + } + + System.CriticalSection.Leave(); + + return result; + } + + public KernelResult EnqueueIncomingLightSession(KLightServerSession session) + { + KernelResult result; + + System.CriticalSection.Enter(); + + if (_state == ChannelState.Open) + { + ServerPort.EnqueueIncomingLightSession(session); + + result = KernelResult.Success; + } + else + { + result = KernelResult.PortClosed; + } + + System.CriticalSection.Leave(); + + return result; } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Ipc/KServerPort.cs b/Ryujinx.HLE/HOS/Kernel/Ipc/KServerPort.cs index d4d3bcd2..919df357 100644 --- a/Ryujinx.HLE/HOS/Kernel/Ipc/KServerPort.cs +++ b/Ryujinx.HLE/HOS/Kernel/Ipc/KServerPort.cs @@ -1,16 +1,87 @@ using Ryujinx.HLE.HOS.Kernel.Common; +using System.Collections.Generic; namespace Ryujinx.HLE.HOS.Kernel.Ipc { class KServerPort : KSynchronizationObject { + private LinkedList _incomingConnections; + private LinkedList _lightIncomingConnections; + private KPort _parent; - public KServerPort(Horizon system) : base(system) { } + public bool IsLight => _parent.IsLight; - public void Initialize(KPort parent) + public KServerPort(Horizon system, KPort parent) : base(system) { _parent = parent; + + _incomingConnections = new LinkedList(); + _lightIncomingConnections = new LinkedList(); + } + + public void EnqueueIncomingSession(KServerSession session) + { + AcceptIncomingConnection(_incomingConnections, session); + } + + public void EnqueueIncomingLightSession(KLightServerSession session) + { + AcceptIncomingConnection(_lightIncomingConnections, session); + } + + private void AcceptIncomingConnection(LinkedList list, T session) + { + System.CriticalSection.Enter(); + + list.AddLast(session); + + if (list.Count == 1) + { + Signal(); + } + + System.CriticalSection.Leave(); + } + + public KServerSession AcceptIncomingConnection() + { + return AcceptIncomingConnection(_incomingConnections); + } + + public KLightServerSession AcceptIncomingLightConnection() + { + return AcceptIncomingConnection(_lightIncomingConnections); + } + + private T AcceptIncomingConnection(LinkedList list) + { + T session = default(T); + + System.CriticalSection.Enter(); + + if (list.Count != 0) + { + session = list.First.Value; + + list.RemoveFirst(); + } + + System.CriticalSection.Leave(); + + return session; + } + + public override bool IsSignaled() + { + if (_parent.IsLight) + { + return _lightIncomingConnections.Count != 0; + } + else + { + return _incomingConnections.Count != 0; + } } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Ipc/KServerSession.cs b/Ryujinx.HLE/HOS/Kernel/Ipc/KServerSession.cs new file mode 100644 index 00000000..5a45ff4a --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Ipc/KServerSession.cs @@ -0,0 +1,1262 @@ +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Kernel.Threading; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Kernel.Ipc +{ + class KServerSession : KSynchronizationObject + { + private static readonly MemoryState[] IpcMemoryStates = new MemoryState[] + { + MemoryState.IpcBuffer3, + MemoryState.IpcBuffer0, + MemoryState.IpcBuffer1, + (MemoryState)0xfffce5d4 //This is invalid, shouldn't be accessed. + }; + + private struct Message + { + public ulong Address { get; } + public ulong DramAddress { get; } + public ulong Size { get; } + public bool IsCustom { get; } + + public Message(KThread thread, ulong customCmdBuffAddress, ulong customCmdBuffSize) + { + IsCustom = customCmdBuffAddress != 0; + + if (IsCustom) + { + Address = customCmdBuffAddress; + Size = customCmdBuffSize; + + KProcess process = thread.Owner; + + DramAddress = process.MemoryManager.GetDramAddressFromVa(Address); + } + else + { + Address = thread.TlsAddress; + DramAddress = thread.TlsDramAddress; + Size = 0x100; + } + } + + public Message(KSessionRequest request) : this( + request.ClientThread, + request.CustomCmdBuffAddr, + request.CustomCmdBuffSize) { } + } + + private struct MessageHeader + { + public uint Word0 { get; } + public uint Word1 { get; } + public uint Word2 { get; } + + public uint PointerBuffersCount { get; } + public uint SendBuffersCount { get; } + public uint ReceiveBuffersCount { get; } + public uint ExchangeBuffersCount { get; } + + public uint RawDataSizeInWords { get; } + + public uint ReceiveListType { get; } + + public uint MessageSizeInWords { get; } + public uint ReceiveListOffsetInWords { get; } + public uint ReceiveListOffset { get; } + + public bool HasHandles { get; } + + public bool HasPid { get; } + + public uint CopyHandlesCount { get; } + public uint MoveHandlesCount { get; } + + public MessageHeader(uint word0, uint word1, uint word2) + { + Word0 = word0; + Word1 = word1; + Word2 = word2; + + HasHandles = word1 >> 31 != 0; + + uint handleDescSizeInWords = 0; + + if (HasHandles) + { + uint pidSize = (word2 & 1) * 8; + + HasPid = pidSize != 0; + + CopyHandlesCount = (word2 >> 1) & 0xf; + MoveHandlesCount = (word2 >> 5) & 0xf; + + handleDescSizeInWords = (pidSize + CopyHandlesCount * 4 + MoveHandlesCount * 4) / 4; + } + else + { + HasPid = false; + + CopyHandlesCount = 0; + MoveHandlesCount = 0; + } + + PointerBuffersCount = (word0 >> 16) & 0xf; + SendBuffersCount = (word0 >> 20) & 0xf; + ReceiveBuffersCount = (word0 >> 24) & 0xf; + ExchangeBuffersCount = word0 >> 28; + + uint pointerDescSizeInWords = PointerBuffersCount * 2; + uint sendDescSizeInWords = SendBuffersCount * 3; + uint receiveDescSizeInWords = ReceiveBuffersCount * 3; + uint exchangeDescSizeInWords = ExchangeBuffersCount * 3; + + RawDataSizeInWords = word1 & 0x3ff; + + ReceiveListType = (word1 >> 10) & 0xf; + + ReceiveListOffsetInWords = (word1 >> 20) & 0x7ff; + + uint paddingSizeInWords = HasHandles ? 3u : 2u; + + MessageSizeInWords = pointerDescSizeInWords + + sendDescSizeInWords + + receiveDescSizeInWords + + exchangeDescSizeInWords + + RawDataSizeInWords + + paddingSizeInWords + + handleDescSizeInWords; + + if (ReceiveListOffsetInWords == 0) + { + ReceiveListOffsetInWords = MessageSizeInWords; + } + + ReceiveListOffset = ReceiveListOffsetInWords * 4; + } + } + + private struct PointerBufferDesc + { + public uint ReceiveIndex { get; } + + public uint BufferSize { get; } + public ulong BufferAddress { get; set; } + + public PointerBufferDesc(ulong dword) + { + ReceiveIndex = (uint)dword & 0xf; + BufferSize = (uint)dword >> 16; + + BufferAddress = (dword >> 2) & 0x70; + BufferAddress |= (dword >> 12) & 0xf; + + BufferAddress = (BufferAddress << 32) | (dword >> 32); + } + + public ulong Pack() + { + ulong dword = (ReceiveIndex & 0xf) | ((BufferSize & 0xffff) << 16); + + dword |= BufferAddress << 32; + dword |= (BufferAddress >> 20) & 0xf000; + dword |= (BufferAddress >> 30) & 0xffc0; + + return dword; + } + } + + private KSession _parent; + + private LinkedList _requests; + + private KSessionRequest _activeRequest; + + public KServerSession(Horizon system, KSession parent) : base(system) + { + _parent = parent; + + _requests = new LinkedList(); + } + + public KernelResult EnqueueRequest(KSessionRequest request) + { + if (_parent.ClientSession.State != ChannelState.Open) + { + return KernelResult.PortRemoteClosed; + } + + if (request.AsyncEvent == null) + { + if (request.ClientThread.ShallBeTerminated || + request.ClientThread.SchedFlags == ThreadSchedState.TerminationPending) + { + return KernelResult.ThreadTerminating; + } + + request.ClientThread.Reschedule(ThreadSchedState.Paused); + } + + _requests.AddLast(request); + + if (_requests.Count == 1) + { + Signal(); + } + + return KernelResult.Success; + } + + public KernelResult Receive(ulong customCmdBuffAddr = 0, ulong customCmdBuffSize = 0) + { + KThread serverThread = System.Scheduler.GetCurrentThread(); + KProcess serverProcess = serverThread.Owner; + + System.CriticalSection.Enter(); + + if (_parent.ClientSession.State != ChannelState.Open) + { + System.CriticalSection.Leave(); + + return KernelResult.PortRemoteClosed; + } + + if (_activeRequest != null || !DequeueRequest(out KSessionRequest request)) + { + System.CriticalSection.Leave(); + + return KernelResult.NotFound; + } + + if (request.ClientThread == null) + { + System.CriticalSection.Leave(); + + return KernelResult.PortRemoteClosed; + } + + KThread clientThread = request.ClientThread; + KProcess clientProcess = clientThread.Owner; + + System.CriticalSection.Leave(); + + _activeRequest = request; + + request.ServerProcess = serverProcess; + + Message clientMsg = new Message(request); + Message serverMsg = new Message(serverThread, customCmdBuffAddr, customCmdBuffSize); + + MessageHeader clientHeader = GetClientMessageHeader(clientMsg); + MessageHeader serverHeader = GetServerMessageHeader(serverMsg); + + KernelResult serverResult = KernelResult.NotFound; + KernelResult clientResult = KernelResult.Success; + + void CleanUpForError() + { + if (request.BufferDescriptorTable.UnmapServerBuffers(serverProcess.MemoryManager) == KernelResult.Success) + { + request.BufferDescriptorTable.RestoreClientBuffers(clientProcess.MemoryManager); + } + + CloseAllHandles(serverMsg, clientHeader, serverProcess); + + System.CriticalSection.Enter(); + + _activeRequest = null; + + if (_requests.Count != 0) + { + Signal(); + } + + System.CriticalSection.Leave(); + + WakeClientThread(request, clientResult); + } + + if (clientHeader.ReceiveListType < 2 && + clientHeader.ReceiveListOffset > clientMsg.Size) + { + CleanUpForError(); + + return KernelResult.InvalidCombination; + } + else if (clientHeader.ReceiveListType == 2 && + clientHeader.ReceiveListOffset + 8 > clientMsg.Size) + { + CleanUpForError(); + + return KernelResult.InvalidCombination; + } + else if (clientHeader.ReceiveListType > 2 && + clientHeader.ReceiveListType * 8 - 0x10 + clientHeader.ReceiveListOffset > clientMsg.Size) + { + CleanUpForError(); + + return KernelResult.InvalidCombination; + } + + if (clientHeader.ReceiveListOffsetInWords < clientHeader.MessageSizeInWords) + { + CleanUpForError(); + + return KernelResult.InvalidCombination; + } + + if (clientHeader.MessageSizeInWords * 4 > clientMsg.Size) + { + CleanUpForError(); + + return KernelResult.CmdBufferTooSmall; + } + + ulong[] receiveList = GetReceiveList( + serverMsg, + serverHeader.ReceiveListType, + serverHeader.ReceiveListOffset); + + serverProcess.CpuMemory.WriteUInt32((long)serverMsg.Address + 0, clientHeader.Word0); + serverProcess.CpuMemory.WriteUInt32((long)serverMsg.Address + 4, clientHeader.Word1); + + uint offset; + + //Copy handles. + if (clientHeader.HasHandles) + { + if (clientHeader.MoveHandlesCount != 0) + { + CleanUpForError(); + + return KernelResult.InvalidCombination; + } + + serverProcess.CpuMemory.WriteUInt32((long)serverMsg.Address + 8, clientHeader.Word2); + + offset = 3; + + if (clientHeader.HasPid) + { + serverProcess.CpuMemory.WriteInt64((long)serverMsg.Address + offset * 4, clientProcess.Pid); + + offset += 2; + } + + for (int index = 0; index < clientHeader.CopyHandlesCount; index++) + { + int newHandle = 0; + + int handle = System.Device.Memory.ReadInt32((long)clientMsg.DramAddress + offset * 4); + + if (clientResult == KernelResult.Success && handle != 0) + { + clientResult = GetCopyObjectHandle(clientThread, serverProcess, handle, out newHandle); + } + + serverProcess.CpuMemory.WriteInt32((long)serverMsg.Address + offset * 4, newHandle); + + offset++; + } + + for (int index = 0; index < clientHeader.MoveHandlesCount; index++) + { + int newHandle = 0; + + int handle = System.Device.Memory.ReadInt32((long)clientMsg.DramAddress + offset * 4); + + if (handle != 0) + { + if (clientResult == KernelResult.Success) + { + clientResult = GetMoveObjectHandle(clientProcess, serverProcess, handle, out newHandle); + } + else + { + clientProcess.HandleTable.CloseHandle(handle); + } + } + + serverProcess.CpuMemory.WriteInt32((long)serverMsg.Address + offset * 4, newHandle); + + offset++; + } + + if (clientResult != KernelResult.Success) + { + CleanUpForError(); + + return serverResult; + } + } + else + { + offset = 2; + } + + //Copy pointer/receive list buffers. + uint recvListDstOffset = 0; + + for (int index = 0; index < clientHeader.PointerBuffersCount; index++) + { + ulong pointerDesc = System.Device.Memory.ReadUInt64((long)clientMsg.DramAddress + offset * 4); + + PointerBufferDesc descriptor = new PointerBufferDesc(pointerDesc); + + if (descriptor.BufferSize != 0) + { + clientResult = GetReceiveListAddress( + descriptor, + serverMsg, + serverHeader.ReceiveListType, + clientHeader.MessageSizeInWords, + receiveList, + ref recvListDstOffset, + out ulong recvListBufferAddress); + + if (clientResult != KernelResult.Success) + { + CleanUpForError(); + + return serverResult; + } + + clientResult = clientProcess.MemoryManager.CopyDataToCurrentProcess( + recvListBufferAddress, + descriptor.BufferSize, + descriptor.BufferAddress, + MemoryState.IsPoolAllocated, + MemoryState.IsPoolAllocated, + MemoryPermission.Read, + MemoryAttribute.Uncached, + MemoryAttribute.None); + + if (clientResult != KernelResult.Success) + { + CleanUpForError(); + + return serverResult; + } + + descriptor.BufferAddress = recvListBufferAddress; + } + else + { + descriptor.BufferAddress = 0; + } + + serverProcess.CpuMemory.WriteUInt64((long)serverMsg.Address + offset * 4, descriptor.Pack()); + + offset += 2; + } + + //Copy send, receive and exchange buffers. + uint totalBuffersCount = + clientHeader.SendBuffersCount + + clientHeader.ReceiveBuffersCount + + clientHeader.ExchangeBuffersCount; + + for (int index = 0; index < totalBuffersCount; index++) + { + long clientDescAddress = (long)clientMsg.DramAddress + offset * 4; + + uint descWord0 = System.Device.Memory.ReadUInt32(clientDescAddress + 0); + uint descWord1 = System.Device.Memory.ReadUInt32(clientDescAddress + 4); + uint descWord2 = System.Device.Memory.ReadUInt32(clientDescAddress + 8); + + bool isSendDesc = index < clientHeader.SendBuffersCount; + bool isExchangeDesc = index >= clientHeader.SendBuffersCount + clientHeader.ReceiveBuffersCount; + + bool notReceiveDesc = isSendDesc || isExchangeDesc; + bool isReceiveDesc = !notReceiveDesc; + + MemoryPermission permission = index >= clientHeader.SendBuffersCount + ? MemoryPermission.ReadAndWrite + : MemoryPermission.Read; + + uint sizeHigh4 = (descWord2 >> 24) & 0xf; + + ulong bufferSize = descWord0 | (ulong)sizeHigh4 << 32; + + ulong dstAddress = 0; + + if (bufferSize != 0) + { + ulong bufferAddress; + + bufferAddress = descWord2 >> 28; + bufferAddress |= ((descWord2 >> 2) & 7) << 4; + + bufferAddress = (bufferAddress << 32) | descWord1; + + MemoryState state = IpcMemoryStates[(descWord2 + 1) & 3]; + + clientResult = serverProcess.MemoryManager.MapBufferFromClientProcess( + bufferSize, + bufferAddress, + clientProcess.MemoryManager, + permission, + state, + notReceiveDesc, + out dstAddress); + + if (clientResult != KernelResult.Success) + { + CleanUpForError(); + + return serverResult; + } + + if (isSendDesc) + { + clientResult = request.BufferDescriptorTable.AddSendBuffer(bufferAddress, dstAddress, bufferSize, state); + } + else if (isReceiveDesc) + { + clientResult = request.BufferDescriptorTable.AddReceiveBuffer(bufferAddress, dstAddress, bufferSize, state); + } + else /* if (isExchangeDesc) */ + { + clientResult = request.BufferDescriptorTable.AddExchangeBuffer(bufferAddress, dstAddress, bufferSize, state); + } + + if (clientResult != KernelResult.Success) + { + CleanUpForError(); + + return serverResult; + } + } + + descWord1 = (uint)dstAddress; + + descWord2 &= 3; + + descWord2 |= sizeHigh4 << 24; + + descWord2 |= (uint)(dstAddress >> 34) & 0x3ffffffc; + descWord2 |= (uint)(dstAddress >> 4) & 0xf0000000; + + long serverDescAddress = (long)serverMsg.Address + offset * 4; + + serverProcess.CpuMemory.WriteUInt32(serverDescAddress + 0, descWord0); + serverProcess.CpuMemory.WriteUInt32(serverDescAddress + 4, descWord1); + serverProcess.CpuMemory.WriteUInt32(serverDescAddress + 8, descWord2); + + offset += 3; + } + + //Copy raw data. + if (clientHeader.RawDataSizeInWords != 0) + { + ulong copySrc = clientMsg.Address + offset * 4; + ulong copyDst = serverMsg.Address + offset * 4; + + ulong copySize = clientHeader.RawDataSizeInWords * 4; + + if (serverMsg.IsCustom || clientMsg.IsCustom) + { + MemoryPermission permission = clientMsg.IsCustom + ? MemoryPermission.None + : MemoryPermission.Read; + + clientResult = clientProcess.MemoryManager.CopyDataToCurrentProcess( + copyDst, + copySize, + copySrc, + MemoryState.IsPoolAllocated, + MemoryState.IsPoolAllocated, + permission, + MemoryAttribute.Uncached, + MemoryAttribute.None); + } + else + { + copySrc = clientProcess.MemoryManager.GetDramAddressFromVa(copySrc); + copyDst = serverProcess.MemoryManager.GetDramAddressFromVa(copyDst); + + System.Device.Memory.Copy(copyDst, copySrc, copySize); + } + + if (clientResult != KernelResult.Success) + { + CleanUpForError(); + + return serverResult; + } + } + + return KernelResult.Success; + } + + public KernelResult Reply(ulong customCmdBuffAddr = 0, ulong customCmdBuffSize = 0) + { + KThread serverThread = System.Scheduler.GetCurrentThread(); + KProcess serverProcess = serverThread.Owner; + + System.CriticalSection.Enter(); + + if (_activeRequest == null) + { + System.CriticalSection.Leave(); + + return KernelResult.InvalidState; + } + + KSessionRequest request = _activeRequest; + + _activeRequest = null; + + if (_requests.Count != 0) + { + Signal(); + } + + System.CriticalSection.Leave(); + + KThread clientThread = request.ClientThread; + KProcess clientProcess = clientThread.Owner; + + Message clientMsg = new Message(request); + Message serverMsg = new Message(serverThread, customCmdBuffAddr, customCmdBuffSize); + + MessageHeader clientHeader = GetClientMessageHeader(clientMsg); + MessageHeader serverHeader = GetServerMessageHeader(serverMsg); + + KernelResult clientResult = KernelResult.Success; + KernelResult serverResult = KernelResult.Success; + + void CleanUpForError() + { + CloseAllHandles(clientMsg, serverHeader, clientProcess); + + CancelRequest(request, clientResult); + } + + if (clientHeader.ReceiveListType < 2 && + clientHeader.ReceiveListOffset > clientMsg.Size) + { + CleanUpForError(); + + return KernelResult.InvalidCombination; + } + else if (clientHeader.ReceiveListType == 2 && + clientHeader.ReceiveListOffset + 8 > clientMsg.Size) + { + CleanUpForError(); + + return KernelResult.InvalidCombination; + } + else if (clientHeader.ReceiveListType > 2 && + clientHeader.ReceiveListType * 8 - 0x10 + clientHeader.ReceiveListOffset > clientMsg.Size) + { + CleanUpForError(); + + return KernelResult.InvalidCombination; + } + + if (clientHeader.ReceiveListOffsetInWords < clientHeader.MessageSizeInWords) + { + CleanUpForError(); + + return KernelResult.InvalidCombination; + } + + if (serverHeader.MessageSizeInWords * 4 > clientMsg.Size) + { + CleanUpForError(); + + return KernelResult.CmdBufferTooSmall; + } + + if (serverHeader.SendBuffersCount != 0 || + serverHeader.ReceiveBuffersCount != 0 || + serverHeader.ExchangeBuffersCount != 0) + { + CleanUpForError(); + + return KernelResult.InvalidCombination; + } + + //Read receive list. + ulong[] receiveList = GetReceiveList( + clientMsg, + clientHeader.ReceiveListType, + clientHeader.ReceiveListOffset); + + //Copy receive and exchange buffers. + clientResult = request.BufferDescriptorTable.CopyBuffersToClient(clientProcess.MemoryManager); + + if (clientResult != KernelResult.Success) + { + CleanUpForError(); + + return serverResult; + } + + //Copy header. + System.Device.Memory.WriteUInt32((long)clientMsg.DramAddress + 0, serverHeader.Word0); + System.Device.Memory.WriteUInt32((long)clientMsg.DramAddress + 4, serverHeader.Word1); + + //Copy handles. + uint offset; + + if (serverHeader.HasHandles) + { + offset = 3; + + System.Device.Memory.WriteUInt32((long)clientMsg.DramAddress + 8, serverHeader.Word2); + + if (serverHeader.HasPid) + { + System.Device.Memory.WriteInt64((long)clientMsg.DramAddress + offset * 4, serverProcess.Pid); + + offset += 2; + } + + for (int index = 0; index < serverHeader.CopyHandlesCount; index++) + { + int newHandle = 0; + + int handle = serverProcess.CpuMemory.ReadInt32((long)serverMsg.Address + offset * 4); + + if (handle != 0) + { + GetCopyObjectHandle(serverThread, clientProcess, handle, out newHandle); + } + + System.Device.Memory.WriteInt32((long)clientMsg.DramAddress + offset * 4, newHandle); + + offset++; + } + + for (int index = 0; index < serverHeader.MoveHandlesCount; index++) + { + int newHandle = 0; + + int handle = serverProcess.CpuMemory.ReadInt32((long)serverMsg.Address + offset * 4); + + if (handle != 0) + { + if (clientResult == KernelResult.Success) + { + clientResult = GetMoveObjectHandle(serverProcess, clientProcess, handle, out newHandle); + } + else + { + serverProcess.HandleTable.CloseHandle(handle); + } + } + + System.Device.Memory.WriteInt32((long)clientMsg.DramAddress + offset * 4, newHandle); + + offset++; + } + } + else + { + offset = 2; + } + + //Copy pointer/receive list buffers. + uint recvListDstOffset = 0; + + for (int index = 0; index < serverHeader.PointerBuffersCount; index++) + { + ulong pointerDesc = serverProcess.CpuMemory.ReadUInt64((long)serverMsg.Address + offset * 4); + + PointerBufferDesc descriptor = new PointerBufferDesc(pointerDesc); + + if (descriptor.BufferSize != 0) + { + clientResult = GetReceiveListAddress( + descriptor, + clientMsg, + clientHeader.ReceiveListType, + serverHeader.MessageSizeInWords, + receiveList, + ref recvListDstOffset, + out ulong recvListBufferAddress); + + if (clientResult != KernelResult.Success) + { + CleanUpForError(); + + return serverResult; + } + + clientResult = clientProcess.MemoryManager.CopyDataFromCurrentProcess( + recvListBufferAddress, + descriptor.BufferSize, + MemoryState.IsPoolAllocated, + MemoryState.IsPoolAllocated, + MemoryPermission.Read, + MemoryAttribute.Uncached, + MemoryAttribute.None, + descriptor.BufferAddress); + + if (clientResult != KernelResult.Success) + { + CleanUpForError(); + + return serverResult; + } + } + + offset += 2; + } + + //Set send, receive and exchange buffer descriptors to zero. + uint totalBuffersCount = + serverHeader.SendBuffersCount + + serverHeader.ReceiveBuffersCount + + serverHeader.ExchangeBuffersCount; + + for (int index = 0; index < totalBuffersCount; index++) + { + long dstDescAddress = (long)clientMsg.DramAddress + offset * 4; + + System.Device.Memory.WriteUInt32(dstDescAddress + 0, 0); + System.Device.Memory.WriteUInt32(dstDescAddress + 4, 0); + System.Device.Memory.WriteUInt32(dstDescAddress + 8, 0); + + offset += 3; + } + + //Copy raw data. + if (serverHeader.RawDataSizeInWords != 0) + { + ulong copyDst = clientMsg.Address + offset * 4; + ulong copySrc = serverMsg.Address + offset * 4; + + ulong copySize = serverHeader.RawDataSizeInWords * 4; + + if (serverMsg.IsCustom || clientMsg.IsCustom) + { + MemoryPermission permission = clientMsg.IsCustom + ? MemoryPermission.None + : MemoryPermission.Read; + + clientResult = clientProcess.MemoryManager.CopyDataFromCurrentProcess( + copyDst, + copySize, + MemoryState.IsPoolAllocated, + MemoryState.IsPoolAllocated, + permission, + MemoryAttribute.Uncached, + MemoryAttribute.None, + copySrc); + } + else + { + copyDst = clientProcess.MemoryManager.GetDramAddressFromVa(copyDst); + copySrc = serverProcess.MemoryManager.GetDramAddressFromVa(copySrc); + + System.Device.Memory.Copy(copyDst, copySrc, copySize); + } + } + + //Unmap buffers from server. + clientResult = request.BufferDescriptorTable.UnmapServerBuffers(serverProcess.MemoryManager); + + if (clientResult != KernelResult.Success) + { + CleanUpForError(); + + return serverResult; + } + + WakeClientThread(request, clientResult); + + return serverResult; + } + + private MessageHeader GetClientMessageHeader(Message clientMsg) + { + uint word0 = System.Device.Memory.ReadUInt32((long)clientMsg.DramAddress + 0); + uint word1 = System.Device.Memory.ReadUInt32((long)clientMsg.DramAddress + 4); + uint word2 = System.Device.Memory.ReadUInt32((long)clientMsg.DramAddress + 8); + + return new MessageHeader(word0, word1, word2); + } + + private MessageHeader GetServerMessageHeader(Message serverMsg) + { + KProcess currentProcess = System.Scheduler.GetCurrentProcess(); + + uint word0 = currentProcess.CpuMemory.ReadUInt32((long)serverMsg.Address + 0); + uint word1 = currentProcess.CpuMemory.ReadUInt32((long)serverMsg.Address + 4); + uint word2 = currentProcess.CpuMemory.ReadUInt32((long)serverMsg.Address + 8); + + return new MessageHeader(word0, word1, word2); + } + + private KernelResult GetCopyObjectHandle( + KThread srcThread, + KProcess dstProcess, + int srcHandle, + out int dstHandle) + { + dstHandle = 0; + + KProcess srcProcess = srcThread.Owner; + + KAutoObject obj; + + if (srcHandle == KHandleTable.SelfProcessHandle) + { + obj = srcProcess; + } + else if (srcHandle == KHandleTable.SelfThreadHandle) + { + obj = srcThread; + } + else + { + obj = srcProcess.HandleTable.GetObject(srcHandle); + } + + if (obj != null) + { + return dstProcess.HandleTable.GenerateHandle(obj, out dstHandle); + } + else + { + return KernelResult.InvalidHandle; + } + } + + private KernelResult GetMoveObjectHandle( + KProcess srcProcess, + KProcess dstProcess, + int srcHandle, + out int dstHandle) + { + dstHandle = 0; + + KAutoObject obj = srcProcess.HandleTable.GetObject(srcHandle); + + if (obj != null) + { + KernelResult result = dstProcess.HandleTable.GenerateHandle(obj, out dstHandle); + + srcProcess.HandleTable.CloseHandle(srcHandle); + + return result; + } + else + { + return KernelResult.InvalidHandle; + } + } + + private ulong[] GetReceiveList(Message message, uint recvListType, uint recvListOffset) + { + int recvListSize = 0; + + if (recvListType >= 3) + { + recvListSize = (int)recvListType - 2; + } + else if (recvListType == 2) + { + recvListSize = 1; + } + + ulong[] receiveList = new ulong[recvListSize]; + + long recvListAddress = (long)message.DramAddress + recvListOffset; + + for (int index = 0; index < recvListSize; index++) + { + receiveList[index] = System.Device.Memory.ReadUInt64(recvListAddress + index * 8); + } + + return receiveList; + } + + private KernelResult GetReceiveListAddress( + PointerBufferDesc descriptor, + Message message, + uint recvListType, + uint messageSizeInWords, + ulong[] receiveList, + ref uint dstOffset, + out ulong address) + { + ulong recvListBufferAddress = address = 0; + + if (recvListType == 0) + { + return KernelResult.OutOfResource; + } + else if (recvListType == 1 || recvListType == 2) + { + ulong recvListBaseAddr; + ulong recvListEndAddr; + + if (recvListType == 1) + { + recvListBaseAddr = message.Address + messageSizeInWords * 4; + recvListEndAddr = message.Address + message.Size; + } + else /* if (recvListType == 2) */ + { + ulong packed = receiveList[0]; + + recvListBaseAddr = packed & 0x7fffffffff; + + uint size = (uint)(packed >> 48); + + if (size == 0) + { + return KernelResult.OutOfResource; + } + + recvListEndAddr = recvListBaseAddr + size; + } + + recvListBufferAddress = BitUtils.AlignUp(recvListBaseAddr + dstOffset, 0x10); + + ulong endAddress = recvListBufferAddress + descriptor.BufferSize; + + dstOffset = (uint)endAddress - (uint)recvListBaseAddr; + + if (recvListBufferAddress + descriptor.BufferSize <= recvListBufferAddress || + recvListBufferAddress + descriptor.BufferSize > recvListEndAddr) + { + return KernelResult.OutOfResource; + } + } + else /* if (recvListType > 2) */ + { + if (descriptor.ReceiveIndex >= receiveList.Length) + { + return KernelResult.OutOfResource; + } + + ulong packed = receiveList[descriptor.ReceiveIndex]; + + recvListBufferAddress = packed & 0x7fffffffff; + + uint size = (uint)(packed >> 48); + + if (recvListBufferAddress == 0 || size == 0 || size < descriptor.BufferSize) + { + return KernelResult.OutOfResource; + } + } + + address = recvListBufferAddress; + + return KernelResult.Success; + } + + private void CloseAllHandles(Message message, MessageHeader header, KProcess process) + { + if (header.HasHandles) + { + uint totalHandeslCount = header.CopyHandlesCount + header.MoveHandlesCount; + + uint offset = 3; + + if (header.HasPid) + { + process.CpuMemory.WriteInt64((long)message.Address + offset * 4, 0); + + offset += 2; + } + + for (int index = 0; index < totalHandeslCount; index++) + { + int handle = process.CpuMemory.ReadInt32((long)message.Address + offset * 4); + + if (handle != 0) + { + process.HandleTable.CloseHandle(handle); + + process.CpuMemory.WriteInt32((long)message.Address + offset * 4, 0); + } + + offset++; + } + } + } + + public override bool IsSignaled() + { + if (_parent.ClientSession.State != ChannelState.Open) + { + return true; + } + + return _requests.Count != 0 && _activeRequest == null; + } + + protected override void Destroy() + { + _parent.DisconnectServer(); + + CancelAllRequestsServerDisconnected(); + + _parent.DecrementReferenceCount(); + } + + private void CancelAllRequestsServerDisconnected() + { + foreach (KSessionRequest request in IterateWithRemovalOfAllRequests()) + { + CancelRequest(request, KernelResult.PortRemoteClosed); + } + } + + public void CancelAllRequestsClientDisconnected() + { + foreach (KSessionRequest request in IterateWithRemovalOfAllRequests()) + { + if (request.ClientThread.ShallBeTerminated || + request.ClientThread.SchedFlags == ThreadSchedState.TerminationPending) + { + continue; + } + + //Client sessions can only be disconnected on async requests (because + //the client would be otherwise blocked waiting for the response), so + //we only need to handle the async case here. + if (request.AsyncEvent != null) + { + SendResultToAsyncRequestClient(request, KernelResult.PortRemoteClosed); + } + } + + WakeServerThreads(KernelResult.PortRemoteClosed); + } + + private IEnumerable IterateWithRemovalOfAllRequests() + { + System.CriticalSection.Enter(); + + if (_activeRequest != null) + { + KSessionRequest request = _activeRequest; + + _activeRequest = null; + + System.CriticalSection.Leave(); + + yield return request; + } + else + { + System.CriticalSection.Leave(); + } + + while (DequeueRequest(out KSessionRequest request)) + { + yield return request; + } + } + + private bool DequeueRequest(out KSessionRequest request) + { + request = null; + + System.CriticalSection.Enter(); + + bool hasRequest = _requests.First != null; + + if (hasRequest) + { + request = _requests.First.Value; + + _requests.RemoveFirst(); + } + + System.CriticalSection.Leave(); + + return hasRequest; + } + + private void CancelRequest(KSessionRequest request, KernelResult result) + { + KProcess clientProcess = request.ClientThread.Owner; + KProcess serverProcess = request.ServerProcess; + + KernelResult unmapResult = KernelResult.Success; + + if (serverProcess != null) + { + unmapResult = request.BufferDescriptorTable.UnmapServerBuffers(serverProcess.MemoryManager); + } + + if (unmapResult == KernelResult.Success) + { + request.BufferDescriptorTable.RestoreClientBuffers(clientProcess.MemoryManager); + } + + WakeClientThread(request, result); + } + + private void WakeClientThread(KSessionRequest request, KernelResult result) + { + //Wait client thread waiting for a response for the given request. + if (request.AsyncEvent != null) + { + SendResultToAsyncRequestClient(request, result); + } + else + { + System.CriticalSection.Enter(); + + WakeAndSetResult(request.ClientThread, result); + + System.CriticalSection.Leave(); + } + } + + private void SendResultToAsyncRequestClient(KSessionRequest request, KernelResult result) + { + KProcess clientProcess = request.ClientThread.Owner; + + ulong address = clientProcess.MemoryManager.GetDramAddressFromVa(request.CustomCmdBuffAddr); + + System.Device.Memory.WriteInt64((long)address + 0, 0); + System.Device.Memory.WriteInt32((long)address + 8, (int)result); + + clientProcess.MemoryManager.UnborrowIpcBuffer( + request.CustomCmdBuffAddr, + request.CustomCmdBuffSize); + + request.AsyncEvent.Signal(); + } + + private void WakeServerThreads(KernelResult result) + { + //Wake all server threads waiting for requests. + System.CriticalSection.Enter(); + + foreach (KThread thread in WaitingThreads) + { + WakeAndSetResult(thread, result); + } + + System.CriticalSection.Leave(); + } + + private void WakeAndSetResult(KThread thread, KernelResult result) + { + if ((thread.SchedFlags & ThreadSchedState.LowMask) == ThreadSchedState.Paused) + { + thread.SignaledObj = null; + thread.ObjSyncResult = result; + + thread.Reschedule(ThreadSchedState.Running); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Ipc/KSession.cs b/Ryujinx.HLE/HOS/Kernel/Ipc/KSession.cs index f2b30493..cbf689a5 100644 --- a/Ryujinx.HLE/HOS/Kernel/Ipc/KSession.cs +++ b/Ryujinx.HLE/HOS/Kernel/Ipc/KSession.cs @@ -1,18 +1,40 @@ -using Ryujinx.HLE.HOS.Services; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Process; using System; namespace Ryujinx.HLE.HOS.Kernel.Ipc { - class KSession : IDisposable + class KSession : KAutoObject, IDisposable { - public IpcService Service { get; private set; } + public KServerSession ServerSession { get; } + public KClientSession ClientSession { get; } - public string ServiceName { get; private set; } + private bool _hasBeenInitialized; - public KSession(IpcService service, string serviceName) + public KSession(Horizon system) : base(system) { - Service = service; - ServiceName = serviceName; + ServerSession = new KServerSession(system, this); + ClientSession = new KClientSession(system, this); + + _hasBeenInitialized = true; + } + + public void DisconnectClient() + { + if (ClientSession.State == ChannelState.Open) + { + ClientSession.State = ChannelState.ClientDisconnected; + + ServerSession.CancelAllRequestsClientDisconnected(); + } + } + + public void DisconnectServer() + { + if (ClientSession.State == ChannelState.Open) + { + ClientSession.State = ChannelState.ServerDisconnected; + } } public void Dispose() @@ -22,10 +44,22 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc protected virtual void Dispose(bool disposing) { - if (disposing && Service is IDisposable disposableService) + if (disposing && ClientSession.Service is IDisposable disposableService) { disposableService.Dispose(); } } + + protected override void Destroy() + { + if (_hasBeenInitialized) + { + KProcess creatorProcess = ClientSession.CreatorProcess; + + creatorProcess.ResourceLimit?.Release(LimitableResource.Session, 1); + + creatorProcess.DecrementReferenceCount(); + } + } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Ipc/KSessionRequest.cs b/Ryujinx.HLE/HOS/Kernel/Ipc/KSessionRequest.cs new file mode 100644 index 00000000..f3467f39 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Ipc/KSessionRequest.cs @@ -0,0 +1,31 @@ +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Kernel.Threading; + +namespace Ryujinx.HLE.HOS.Kernel.Ipc +{ + class KSessionRequest + { + public KBufferDescriptorTable BufferDescriptorTable { get; } + + public KThread ClientThread { get; } + + public KProcess ServerProcess { get; set; } + + public KWritableEvent AsyncEvent { get; } + + public ulong CustomCmdBuffAddr { get; } + public ulong CustomCmdBuffSize { get; } + + public KSessionRequest( + KThread clientThread, + ulong customCmdBuffAddr, + ulong customCmdBuffSize) + { + ClientThread = clientThread; + CustomCmdBuffAddr = customCmdBuffAddr; + CustomCmdBuffSize = customCmdBuffSize; + + BufferDescriptorTable = new KBufferDescriptorTable(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlock.cs b/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlock.cs index 89a19498..b7c2b309 100644 --- a/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlock.cs +++ b/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlock.cs @@ -1,29 +1,108 @@ +using System; + namespace Ryujinx.HLE.HOS.Kernel.Memory { class KMemoryBlock { - public ulong BaseAddress { get; set; } - public ulong PagesCount { get; set; } + public ulong BaseAddress { get; private set; } + public ulong PagesCount { get; private set; } - public MemoryState State { get; set; } - public MemoryPermission Permission { get; set; } - public MemoryAttribute Attribute { get; set; } + public MemoryState State { get; private set; } + public MemoryPermission Permission { get; private set; } + public MemoryAttribute Attribute { get; private set; } + public MemoryPermission SourcePermission { get; private set; } - public int IpcRefCount { get; set; } - public int DeviceRefCount { get; set; } + public int IpcRefCount { get; private set; } + public int DeviceRefCount { get; private set; } public KMemoryBlock( ulong baseAddress, ulong pagesCount, MemoryState state, MemoryPermission permission, - MemoryAttribute attribute) + MemoryAttribute attribute, + int ipcRefCount = 0, + int deviceRefCount = 0) { - BaseAddress = baseAddress; - PagesCount = pagesCount; - State = state; - Attribute = attribute; - Permission = permission; + BaseAddress = baseAddress; + PagesCount = pagesCount; + State = state; + Attribute = attribute; + Permission = permission; + IpcRefCount = ipcRefCount; + DeviceRefCount = deviceRefCount; + } + + public void SetState(MemoryPermission permission, MemoryState state, MemoryAttribute attribute) + { + Permission = permission; + State = state; + Attribute &= MemoryAttribute.IpcAndDeviceMapped; + Attribute |= attribute; + } + + public void SetIpcMappingPermission(MemoryPermission permission) + { + int oldIpcRefCount = IpcRefCount++; + + if ((ushort)IpcRefCount == 0) + { + throw new InvalidOperationException("IPC reference count increment overflowed."); + } + + if (oldIpcRefCount == 0) + { + SourcePermission = permission; + + Permission &= ~MemoryPermission.ReadAndWrite; + Permission |= MemoryPermission.ReadAndWrite & permission; + } + + Attribute |= MemoryAttribute.IpcMapped; + } + + public void RestoreIpcMappingPermission() + { + int oldIpcRefCount = IpcRefCount--; + + if (oldIpcRefCount == 0) + { + throw new InvalidOperationException("IPC reference count decrement underflowed."); + } + + if (oldIpcRefCount == 1) + { + Permission = SourcePermission; + + SourcePermission = MemoryPermission.None; + + Attribute &= ~MemoryAttribute.IpcMapped; + } + } + + public KMemoryBlock SplitRightAtAddress(ulong address) + { + ulong leftAddress = BaseAddress; + + ulong leftPagesCount = (address - leftAddress) / KMemoryManager.PageSize; + + BaseAddress = address; + + PagesCount -= leftPagesCount; + + return new KMemoryBlock( + leftAddress, + leftPagesCount, + State, + Permission, + Attribute, + IpcRefCount, + DeviceRefCount); + } + + public void AddPages(ulong pagesCount) + { + PagesCount += pagesCount; } public KMemoryInfo GetInfo() @@ -36,6 +115,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory State, Permission, Attribute, + SourcePermission, IpcRefCount, DeviceRefCount); } diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryInfo.cs b/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryInfo.cs index 226ce77c..21e9e494 100644 --- a/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryInfo.cs +++ b/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryInfo.cs @@ -2,15 +2,16 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory { class KMemoryInfo { - public ulong Address { get; private set; } - public ulong Size { get; private set; } + public ulong Address { get; } + public ulong Size { get; } - public MemoryState State { get; private set; } - public MemoryPermission Permission { get; private set; } - public MemoryAttribute Attribute { get; private set; } + public MemoryState State { get; } + public MemoryPermission Permission { get; } + public MemoryAttribute Attribute { get; } + public MemoryPermission SourcePermission { get; } - public int IpcRefCount { get; private set; } - public int DeviceRefCount { get; private set; } + public int IpcRefCount { get; } + public int DeviceRefCount { get; } public KMemoryInfo( ulong address, @@ -18,16 +19,18 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory MemoryState state, MemoryPermission permission, MemoryAttribute attribute, + MemoryPermission sourcePermission, int ipcRefCount, int deviceRefCount) { - Address = address; - Size = size; - State = state; - Attribute = attribute; - Permission = permission; - IpcRefCount = ipcRefCount; - DeviceRefCount = deviceRefCount; + Address = address; + Size = size; + State = state; + Permission = permission; + Attribute = attribute; + SourcePermission = sourcePermission; + IpcRefCount = ipcRefCount; + DeviceRefCount = deviceRefCount; } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryManager.cs b/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryManager.cs index fb5dec04..7a40139c 100644 --- a/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryManager.cs +++ b/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryManager.cs @@ -9,6 +9,16 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory { class KMemoryManager { + private static readonly int[] MappingUnitSizes = new int[] + { + 0x1000, + 0x10000, + 0x200000, + 0x400000, + 0x2000000, + 0x40000000 + }; + public const int PageSize = 0x1000; private const int KMemoryBlockSize = 0x40; @@ -335,7 +345,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory ulong addrSpacePagesCount = (addrSpaceEnd - addrSpaceStart) / PageSize; - InsertBlock(addrSpaceStart, addrSpacePagesCount, MemoryState.Unmapped); + _blocks.AddFirst(new KMemoryBlock( + addrSpaceStart, + addrSpacePagesCount, + MemoryState.Unmapped, + MemoryPermission.None, + MemoryAttribute.None)); return KernelResult.Success; } @@ -488,67 +503,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory return KernelResult.OutOfMemory; } - ulong reservedPagesCount = _isKernel ? 1UL : 4UL; - lock (_blocks) { - if (_aslrEnabled) - { - ulong totalNeededSize = (reservedPagesCount + neededPagesCount) * PageSize; - - ulong remainingPages = regionPagesCount - neededPagesCount; - - ulong aslrMaxOffset = ((remainingPages + reservedPagesCount) * PageSize) / (ulong)alignment; - - for (int attempt = 0; attempt < 8; attempt++) - { - address = BitUtils.AlignDown(regionStart + GetRandomValue(0, aslrMaxOffset) * (ulong)alignment, alignment); - - ulong endAddr = address + totalNeededSize; - - KMemoryInfo info = FindBlock(address).GetInfo(); - - if (info.State != MemoryState.Unmapped) - { - continue; - } - - ulong currBaseAddr = info.Address + reservedPagesCount * PageSize; - ulong currEndAddr = info.Address + info.Size; - - if (address >= regionStart && - address >= currBaseAddr && - endAddr - 1 <= regionEndAddr - 1 && - endAddr - 1 <= currEndAddr - 1) - { - break; - } - } - - if (address == 0) - { - ulong aslrPage = GetRandomValue(0, aslrMaxOffset); - - address = FindFirstFit( - regionStart + aslrPage * PageSize, - regionPagesCount - aslrPage, - neededPagesCount, - alignment, - 0, - reservedPagesCount); - } - } - - if (address == 0) - { - address = FindFirstFit( - regionStart, - regionPagesCount, - neededPagesCount, - alignment, - 0, - reservedPagesCount); - } + address = AllocateVa(regionStart, regionPagesCount, neededPagesCount, alignment); if (address == 0) { @@ -977,6 +934,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory MemoryState.Reserved, MemoryPermission.None, MemoryAttribute.None, + MemoryPermission.None, 0, 0); } @@ -1325,22 +1283,13 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory { ulong mappedSize = 0; - KMemoryInfo info; - - LinkedListNode node = FindBlockNode(address); - - do + foreach (KMemoryInfo info in IterateOverRange(address, endAddr)) { - info = node.Value.GetInfo(); - if (info.State != MemoryState.Unmapped) { mappedSize += GetSizeInRange(info, address, endAddr); } - - node = node.Next; } - while (info.Address + info.Size < endAddr && node != null); if (mappedSize == size) { @@ -1419,16 +1368,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory KPageList pageList = new KPageList(); - KMemoryInfo info; - - LinkedListNode baseNode = FindBlockNode(address); - - LinkedListNode node = baseNode; - - do + foreach (KMemoryInfo info in IterateOverRange(address, endAddr)) { - info = node.Value.GetInfo(); - if (info.State == MemoryState.Heap) { if (info.Attribute != MemoryAttribute.None) @@ -1447,10 +1388,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory { return KernelResult.InvalidMemState; } - - node = node.Next; } - while (info.Address + info.Size < endAddr && node != null); if (heapMappedSize == 0) { @@ -1465,12 +1403,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory //Try to unmap all the heap mapped memory inside range. KernelResult result = KernelResult.Success; - node = baseNode; - - do + foreach (KMemoryInfo info in IterateOverRange(address, endAddr)) { - info = node.Value.GetInfo(); - if (info.State == MemoryState.Heap) { ulong blockSize = GetSizeInRange(info, address, endAddr); @@ -1488,10 +1422,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory break; } } - - node = node.Next; } - while (info.Address + info.Size < endAddr && node != null); if (result == KernelResult.Success) { @@ -1514,10 +1445,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory private void MapPhysicalMemory(KPageList pageList, ulong address, ulong endAddr) { - KMemoryInfo info; - - LinkedListNode node = FindBlockNode(address); - LinkedListNode pageListNode = pageList.Nodes.First; KPageNode pageNode = pageListNode.Value; @@ -1525,10 +1452,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory ulong srcPa = pageNode.Address; ulong srcPaPages = pageNode.PagesCount; - do + foreach (KMemoryInfo info in IterateOverRange(address, endAddr)) { - info = node.Value.GetInfo(); - if (info.State == MemoryState.Unmapped) { ulong blockSize = GetSizeInRange(info, address, endAddr); @@ -1570,10 +1495,777 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory dstVaPages -= pagesCount; } } - - node = node.Next; } - while (info.Address + info.Size < endAddr && node != null); + } + + public KernelResult CopyDataToCurrentProcess( + ulong dst, + ulong size, + ulong src, + MemoryState stateMask, + MemoryState stateExpected, + MemoryPermission permission, + MemoryAttribute attributeMask, + MemoryAttribute attributeExpected) + { + //Client -> server. + return CopyDataFromOrToCurrentProcess( + size, + src, + dst, + stateMask, + stateExpected, + permission, + attributeMask, + attributeExpected, + toServer: true); + } + + public KernelResult CopyDataFromCurrentProcess( + ulong dst, + ulong size, + MemoryState stateMask, + MemoryState stateExpected, + MemoryPermission permission, + MemoryAttribute attributeMask, + MemoryAttribute attributeExpected, + ulong src) + { + //Server -> client. + return CopyDataFromOrToCurrentProcess( + size, + dst, + src, + stateMask, + stateExpected, + permission, + attributeMask, + attributeExpected, + toServer: false); + } + + private KernelResult CopyDataFromOrToCurrentProcess( + ulong size, + ulong clientAddress, + ulong serverAddress, + MemoryState stateMask, + MemoryState stateExpected, + MemoryPermission permission, + MemoryAttribute attributeMask, + MemoryAttribute attributeExpected, + bool toServer) + { + if (AddrSpaceStart > clientAddress) + { + return KernelResult.InvalidMemState; + } + + ulong srcEndAddr = clientAddress + size; + + if (srcEndAddr <= clientAddress || srcEndAddr - 1 > AddrSpaceEnd - 1) + { + return KernelResult.InvalidMemState; + } + + lock (_blocks) + { + if (CheckRange( + clientAddress, + size, + stateMask, + stateExpected, + permission, + permission, + attributeMask | MemoryAttribute.Uncached, + attributeExpected)) + { + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + serverAddress = currentProcess.MemoryManager.GetDramAddressFromVa(serverAddress); + + if (toServer) + { + _system.Device.Memory.Copy(serverAddress, GetDramAddressFromVa(clientAddress), size); + } + else + { + _system.Device.Memory.Copy(GetDramAddressFromVa(clientAddress), serverAddress, size); + } + + return KernelResult.Success; + } + else + { + return KernelResult.InvalidMemState; + } + } + } + + public KernelResult MapBufferFromClientProcess( + ulong size, + ulong src, + KMemoryManager sourceMemMgr, + MemoryPermission permission, + MemoryState state, + bool copyData, + out ulong dst) + { + dst = 0; + + KernelResult result = sourceMemMgr.GetPagesForMappingIntoAnotherProcess( + src, + size, + permission, + state, + copyData, + _aslrDisabled, + _memRegion, + out KPageList pageList); + + if (result != KernelResult.Success) + { + return result; + } + + result = MapPagesFromAnotherProcess(size, src, permission, state, pageList, out ulong va); + + if (result != KernelResult.Success) + { + sourceMemMgr.UnmapIpcRestorePermission(src, size, state); + } + else + { + dst = va; + } + + return result; + } + + private KernelResult GetPagesForMappingIntoAnotherProcess( + ulong address, + ulong size, + MemoryPermission permission, + MemoryState state, + bool copyData, + bool aslrDisabled, + MemoryRegion region, + out KPageList pageList) + { + pageList = null; + + if (AddrSpaceStart > address) + { + return KernelResult.InvalidMemState; + } + + ulong endAddr = address + size; + + if (endAddr <= address || endAddr - 1 > AddrSpaceEnd - 1) + { + return KernelResult.InvalidMemState; + } + + MemoryState stateMask; + + switch (state) + { + case MemoryState.IpcBuffer0: stateMask = MemoryState.IpcSendAllowedType0; break; + case MemoryState.IpcBuffer1: stateMask = MemoryState.IpcSendAllowedType1; break; + case MemoryState.IpcBuffer3: stateMask = MemoryState.IpcSendAllowedType3; break; + + default: return KernelResult.InvalidCombination; + } + + MemoryPermission permissionMask = permission == MemoryPermission.ReadAndWrite + ? MemoryPermission.None + : MemoryPermission.Read; + + MemoryAttribute attributeMask = MemoryAttribute.Borrowed | MemoryAttribute.Uncached; + + if (state == MemoryState.IpcBuffer0) + { + attributeMask |= MemoryAttribute.DeviceMapped; + } + + ulong addressRounded = BitUtils.AlignUp (address, PageSize); + ulong endAddrRounded = BitUtils.AlignUp (endAddr, PageSize); + ulong endAddrTruncated = BitUtils.AlignDown(endAddr, PageSize); + + if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + ulong visitedSize = 0; + + void CleanUpForError() + { + ulong endAddrVisited = address + visitedSize; + + foreach (KMemoryInfo info in IterateOverRange(address, endAddrVisited)) + { + if ((info.Permission & MemoryPermission.ReadAndWrite) != permissionMask && info.IpcRefCount == 0) + { + ulong blockAddress = GetAddrInRange(info, addressRounded); + ulong blockSize = GetSizeInRange(info, addressRounded, endAddrVisited); + + ulong blockPagesCount = blockSize / PageSize; + + if (DoMmuOperation( + blockAddress, + blockPagesCount, + 0, + false, + info.Permission, + MemoryOperation.ChangePermRw) != KernelResult.Success) + { + throw new InvalidOperationException("Unexpected failure trying to restore permission."); + } + } + } + } + + lock (_blocks) + { + KernelResult result; + + foreach (KMemoryInfo info in IterateOverRange(address, endAddrRounded)) + { + //Check if the block state matches what we expect. + if ((info.State & stateMask) != stateMask || + (info.Permission & permission) != permission || + (info.Attribute & attributeMask) != MemoryAttribute.None) + { + CleanUpForError(); + + return KernelResult.InvalidMemState; + } + + ulong blockAddress = GetAddrInRange(info, addressRounded); + ulong blockSize = GetSizeInRange(info, addressRounded, endAddrTruncated); + + ulong blockPagesCount = blockSize / PageSize; + + if ((info.Permission & MemoryPermission.ReadAndWrite) != permissionMask && info.IpcRefCount == 0) + { + result = DoMmuOperation( + blockAddress, + blockPagesCount, + 0, + false, + permissionMask, + MemoryOperation.ChangePermRw); + + if (result != KernelResult.Success) + { + CleanUpForError(); + + return result; + } + } + + visitedSize += blockSize; + } + + result = GetPagesForIpcTransfer(address, size, copyData, aslrDisabled, region, out pageList); + + if (result != KernelResult.Success) + { + CleanUpForError(); + + return result; + } + + if (visitedSize != 0) + { + InsertBlock(address, visitedSize / PageSize, SetIpcMappingPermissions, permissionMask); + } + } + + return KernelResult.Success; + } + + private KernelResult GetPagesForIpcTransfer( + ulong address, + ulong size, + bool copyData, + bool aslrDisabled, + MemoryRegion region, + out KPageList pageList) + { + pageList = null; + + ulong addressTruncated = BitUtils.AlignDown(address, PageSize); + ulong addressRounded = BitUtils.AlignUp (address, PageSize); + + ulong endAddr = address + size; + + ulong dstFirstPagePa = AllocateSinglePage(region, aslrDisabled); + + if (dstFirstPagePa == 0) + { + return KernelResult.OutOfMemory; + } + + ulong dstLastPagePa = 0; + + void CleanUpForError() + { + FreeSinglePage(region, dstFirstPagePa); + + if (dstLastPagePa != 0) + { + FreeSinglePage(region, dstLastPagePa); + } + } + + ulong firstPageFillAddress = dstFirstPagePa; + + if (!ConvertVaToPa(addressTruncated, out ulong srcFirstPagePa)) + { + CleanUpForError(); + + return KernelResult.InvalidMemState; + } + + ulong unusedSizeAfter; + + //When the start address is unaligned, we can't safely map the + //first page as it would expose other undesirable information on the + //target process. So, instead we allocate new pages, copy the data + //inside the range, and then clear the remaining space. + //The same also holds for the last page, if the end address + //(address + size) is also not aligned. + if (copyData) + { + ulong unusedSizeBefore = address - addressTruncated; + + _system.Device.Memory.Set(dstFirstPagePa, 0, unusedSizeBefore); + + ulong copySize = addressRounded <= endAddr ? addressRounded - address : size; + + _system.Device.Memory.Copy( + GetDramAddressFromPa(dstFirstPagePa + unusedSizeBefore), + GetDramAddressFromPa(srcFirstPagePa + unusedSizeBefore), copySize); + + firstPageFillAddress += unusedSizeBefore + copySize; + + unusedSizeAfter = addressRounded > endAddr ? addressRounded - endAddr : 0; + } + else + { + unusedSizeAfter = PageSize; + } + + if (unusedSizeAfter != 0) + { + _system.Device.Memory.Set(firstPageFillAddress, 0, unusedSizeAfter); + } + + KPageList pages = new KPageList(); + + if (pages.AddRange(dstFirstPagePa, 1) != KernelResult.Success) + { + CleanUpForError(); + + return KernelResult.OutOfResource; + } + + ulong endAddrTruncated = BitUtils.AlignDown(endAddr, PageSize); + ulong endAddrRounded = BitUtils.AlignUp (endAddr, PageSize); + + if (endAddrTruncated > addressRounded) + { + ulong alignedPagesCount = (endAddrTruncated - addressRounded) / PageSize; + + AddVaRangeToPageList(pages, addressRounded, alignedPagesCount); + } + + if (endAddrTruncated != endAddrRounded) + { + //End is also not aligned... + dstLastPagePa = AllocateSinglePage(region, aslrDisabled); + + if (dstLastPagePa == 0) + { + CleanUpForError(); + + return KernelResult.OutOfMemory; + } + + ulong lastPageFillAddr = dstLastPagePa; + + if (!ConvertVaToPa(endAddrTruncated, out ulong srcLastPagePa)) + { + CleanUpForError(); + + return KernelResult.InvalidMemState; + } + + if (copyData) + { + ulong copySize = endAddr - endAddrTruncated; + + _system.Device.Memory.Copy( + GetDramAddressFromPa(dstLastPagePa), + GetDramAddressFromPa(srcLastPagePa), copySize); + + lastPageFillAddr += copySize; + + unusedSizeAfter = PageSize - copySize; + } + else + { + unusedSizeAfter = PageSize; + } + + _system.Device.Memory.Set(lastPageFillAddr, 0, unusedSizeAfter); + + if (pages.AddRange(dstFirstPagePa, 1) != KernelResult.Success) + { + CleanUpForError(); + + return KernelResult.OutOfResource; + } + } + + pageList = pages; + + return KernelResult.Success; + } + + private ulong AllocateSinglePage(MemoryRegion region, bool aslrDisabled) + { + KMemoryRegionManager regionMgr = _system.MemoryRegions[(int)region]; + + return regionMgr.AllocatePagesContiguous(1, aslrDisabled); + } + + private void FreeSinglePage(MemoryRegion region, ulong address) + { + KMemoryRegionManager regionMgr = _system.MemoryRegions[(int)region]; + + regionMgr.FreePage(address); + } + + private KernelResult MapPagesFromAnotherProcess( + ulong size, + ulong address, + MemoryPermission permission, + MemoryState state, + KPageList pageList, + out ulong mappedVa) + { + mappedVa = 0; + + lock (_blocks) + { + if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + ulong endAddr = address + size; + + ulong addressTruncated = BitUtils.AlignDown(address, PageSize); + ulong endAddrRounded = BitUtils.AlignUp (endAddr, PageSize); + + ulong neededSize = endAddrRounded - addressTruncated; + + ulong neededPagesCount = neededSize / PageSize; + + ulong regionPagesCount = (AliasRegionEnd - AliasRegionStart) / PageSize; + + ulong va = 0; + + for (int unit = MappingUnitSizes.Length - 1; unit >= 0 && va == 0; unit--) + { + int alignemnt = MappingUnitSizes[unit]; + + va = AllocateVa(AliasRegionStart, regionPagesCount, neededPagesCount, alignemnt); + } + + if (va == 0) + { + return KernelResult.OutOfVaSpace; + } + + if (pageList.Nodes.Count != 0) + { + KernelResult result = MapPages(va, pageList, permission); + + if (result != KernelResult.Success) + { + return result; + } + } + + InsertBlock(va, neededPagesCount, state, permission); + + mappedVa = va; + } + + return KernelResult.Success; + } + + public KernelResult UnmapNoAttributeIfStateEquals(ulong address, ulong size, MemoryState state) + { + if (AddrSpaceStart > address) + { + return KernelResult.InvalidMemState; + } + + ulong endAddr = address + size; + + if (endAddr <= address || endAddr - 1 > AddrSpaceEnd - 1) + { + return KernelResult.InvalidMemState; + } + + lock (_blocks) + { + if (CheckRange( + address, + size, + MemoryState.Mask, + state, + MemoryPermission.Read, + MemoryPermission.Read, + MemoryAttribute.Mask, + MemoryAttribute.None, + MemoryAttribute.IpcAndDeviceMapped, + out _, + out _, + out _)) + { + if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + ulong addressTruncated = BitUtils.AlignDown(address, PageSize); + ulong endAddrRounded = BitUtils.AlignUp (endAddr, PageSize); + + ulong pagesCount = (endAddrRounded - addressTruncated) / PageSize; + + KernelResult result = DoMmuOperation( + addressTruncated, + pagesCount, + 0, + false, + MemoryPermission.None, + MemoryOperation.Unmap); + + if (result == KernelResult.Success) + { + InsertBlock(addressTruncated, pagesCount, MemoryState.Unmapped); + } + + return result; + } + else + { + return KernelResult.InvalidMemState; + } + } + } + + public KernelResult UnmapIpcRestorePermission(ulong address, ulong size, MemoryState state) + { + ulong endAddr = address + size; + + ulong addressRounded = BitUtils.AlignUp (address, PageSize); + ulong endAddrTruncated = BitUtils.AlignDown(endAddr, PageSize); + + ulong pagesCount = (endAddrTruncated - addressRounded) / PageSize; + + MemoryState stateMask; + + switch (state) + { + case MemoryState.IpcBuffer0: stateMask = MemoryState.IpcSendAllowedType0; break; + case MemoryState.IpcBuffer1: stateMask = MemoryState.IpcSendAllowedType1; break; + case MemoryState.IpcBuffer3: stateMask = MemoryState.IpcSendAllowedType3; break; + + default: return KernelResult.InvalidCombination; + } + + MemoryAttribute attributeMask = + MemoryAttribute.Borrowed | + MemoryAttribute.IpcMapped | + MemoryAttribute.Uncached; + + if (state == MemoryState.IpcBuffer0) + { + attributeMask |= MemoryAttribute.DeviceMapped; + } + + if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + lock (_blocks) + { + foreach (KMemoryInfo info in IterateOverRange(address, endAddrTruncated)) + { + //Check if the block state matches what we expect. + if ((info.State & stateMask) != stateMask || + (info.Attribute & attributeMask) != MemoryAttribute.IpcMapped) + { + return KernelResult.InvalidMemState; + } + + if (info.Permission != info.SourcePermission && info.IpcRefCount == 1) + { + ulong blockAddress = GetAddrInRange(info, addressRounded); + ulong blockSize = GetSizeInRange(info, addressRounded, endAddrTruncated); + + ulong blockPagesCount = blockSize / PageSize; + + KernelResult result = DoMmuOperation( + blockAddress, + blockPagesCount, + 0, + false, + info.SourcePermission, + MemoryOperation.ChangePermRw); + + if (result != KernelResult.Success) + { + return result; + } + } + } + } + + InsertBlock(address, pagesCount, RestoreIpcMappingPermissions); + + return KernelResult.Success; + } + + public KernelResult UnborrowIpcBuffer(ulong address, ulong size) + { + return ClearAttributesAndChangePermission( + address, + size, + MemoryState.IpcBufferAllowed, + MemoryState.IpcBufferAllowed, + MemoryPermission.None, + MemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.Borrowed, + MemoryPermission.ReadAndWrite, + MemoryAttribute.Borrowed); + } + + private KernelResult ClearAttributesAndChangePermission( + ulong address, + ulong size, + MemoryState stateMask, + MemoryState stateExpected, + MemoryPermission permissionMask, + MemoryPermission permissionExpected, + MemoryAttribute attributeMask, + MemoryAttribute attributeExpected, + MemoryPermission newPermission, + MemoryAttribute attributeClearMask, + KPageList pageList = null) + { + lock (_blocks) + { + if (CheckRange( + address, + size, + stateMask | MemoryState.IsPoolAllocated, + stateExpected | MemoryState.IsPoolAllocated, + permissionMask, + permissionExpected, + attributeMask, + attributeExpected, + MemoryAttribute.IpcAndDeviceMapped, + out MemoryState oldState, + out MemoryPermission oldPermission, + out MemoryAttribute oldAttribute)) + { + ulong pagesCount = size / PageSize; + + if (pageList != null) + { + KPageList currPageList = new KPageList(); + + AddVaRangeToPageList(currPageList, address, pagesCount); + + if (!currPageList.IsEqual(pageList)) + { + return KernelResult.InvalidMemRange; + } + } + + if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + if (newPermission == MemoryPermission.None) + { + newPermission = oldPermission; + } + + if (newPermission != oldPermission) + { + KernelResult result = DoMmuOperation( + address, + pagesCount, + 0, + false, + newPermission, + MemoryOperation.ChangePermRw); + + if (result != KernelResult.Success) + { + return result; + } + } + + MemoryAttribute newAttribute = oldAttribute & ~attributeClearMask; + + InsertBlock(address, pagesCount, oldState, newPermission, newAttribute); + + return KernelResult.Success; + } + else + { + return KernelResult.InvalidMemState; + } + } + } + + private void AddVaRangeToPageList(KPageList pageList, ulong start, ulong pagesCount) + { + ulong address = start; + + while (address < start + pagesCount * PageSize) + { + if (!ConvertVaToPa(address, out ulong pa)) + { + throw new InvalidOperationException("Unexpected failure translating virtual address."); + } + + pageList.AddRange(pa, 1); + + address += PageSize; + } + } + + private static ulong GetAddrInRange(KMemoryInfo info, ulong start) + { + if (info.Address < start) + { + return start; + } + + return info.Address; } private static ulong GetSizeInRange(KMemoryInfo info, ulong start, ulong end) @@ -1594,35 +2286,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory return size; } - private static ulong GetAddrInRange(KMemoryInfo info, ulong start) - { - if (info.Address < start) - { - return start; - } - - return info.Address; - } - - private void AddVaRangeToPageList(KPageList pageList, ulong start, ulong pagesCount) - { - ulong address = start; - - while (address < start + pagesCount * PageSize) - { - KernelResult result = ConvertVaToPa(address, out ulong pa); - - if (result != KernelResult.Success) - { - throw new InvalidOperationException("Unexpected failure translating virtual address."); - } - - pageList.AddRange(pa, 1); - - address += PageSize; - } - } - private bool IsUnmapped(ulong address, ulong size) { return CheckRange( @@ -1654,7 +2317,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory out MemoryPermission outPermission, out MemoryAttribute outAttribute) { - ulong endAddr = address + size - 1; + ulong endAddr = address + size; LinkedListNode node = FindBlockNode(address); @@ -1676,28 +2339,20 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory (firstState & stateMask) != stateExpected || (firstPermission & permissionMask) != permissionExpected) { - break; + outState = MemoryState.Unmapped; + outPermission = MemoryPermission.None; + outAttribute = MemoryAttribute.None; + + return false; } - - //Check if this is the last block on the range, if so return success. - if (endAddr <= info.Address + info.Size - 1) - { - outState = firstState; - outPermission = firstPermission; - outAttribute = firstAttribute & ~attributeIgnoreMask; - - return true; - } - - node = node.Next; } - while (node != null); + while (info.Address + info.Size - 1 < endAddr - 1 && (node = node.Next) != null); - outState = MemoryState.Unmapped; - outPermission = MemoryPermission.None; - outAttribute = MemoryAttribute.None; + outState = firstState; + outPermission = firstPermission; + outAttribute = firstAttribute & ~attributeIgnoreMask; - return false; + return true; } private bool CheckRange( @@ -1710,33 +2365,33 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory MemoryAttribute attributeMask, MemoryAttribute attributeExpected) { - ulong endAddr = address + size - 1; - - LinkedListNode node = FindBlockNode(address); - - do + foreach (KMemoryInfo info in IterateOverRange(address, address + size)) { - KMemoryInfo info = node.Value.GetInfo(); - //Check if the block state matches what we expect. if ((info.State & stateMask) != stateExpected || (info.Permission & permissionMask) != permissionExpected || (info.Attribute & attributeMask) != attributeExpected) { - break; + return false; } - - //Check if this is the last block on the range, if so return success. - if (endAddr <= info.Address + info.Size - 1) - { - return true; - } - - node = node.Next; } - while (node != null); - return false; + return true; + } + + private IEnumerable IterateOverRange(ulong start, ulong end) + { + LinkedListNode node = FindBlockNode(start); + + KMemoryInfo info; + + do + { + info = node.Value.GetInfo(); + + yield return info; + } + while (info.Address + info.Size - 1 < end - 1 && (node = node.Next) != null); } private void InsertBlock( @@ -1750,23 +2405,22 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory 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 + //of the block matches the state specified on the old* state //arguments, otherwise leave it as is. int oldCount = _blocks.Count; oldAttribute |= MemoryAttribute.IpcAndDeviceMapped; - ulong endAddr = pagesCount * PageSize + baseAddress; + ulong endAddr = baseAddress + pagesCount * PageSize; LinkedListNode node = _blocks.First; while (node != null) { - LinkedListNode newNode = node; - LinkedListNode nextNode = node.Next; - KMemoryBlock currBlock = node.Value; + LinkedListNode nextNode = node.Next; + ulong currBaseAddr = currBlock.BaseAddress; ulong currEndAddr = currBlock.PagesCount * PageSize + currBaseAddr; @@ -1783,67 +2437,28 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory continue; } - if (currBaseAddr >= baseAddress && currEndAddr <= endAddr) + LinkedListNode newNode = node; + + if (baseAddress > currBaseAddr) { - currBlock.State = newState; - currBlock.Permission = newPermission; - currBlock.Attribute &= ~MemoryAttribute.IpcAndDeviceMapped; - currBlock.Attribute |= newAttribute; + _blocks.AddBefore(node, currBlock.SplitRightAtAddress(baseAddress)); } - else if (currBaseAddr >= baseAddress) + + if (endAddr < currEndAddr) { - currBlock.BaseAddress = endAddr; - - currBlock.PagesCount = (currEndAddr - endAddr) / PageSize; - - ulong newPagesCount = (endAddr - currBaseAddr) / PageSize; - - newNode = _blocks.AddBefore(node, new KMemoryBlock( - currBaseAddr, - newPagesCount, - newState, - newPermission, - newAttribute)); + newNode = _blocks.AddBefore(node, currBlock.SplitRightAtAddress(endAddr)); } - else if (currEndAddr <= endAddr) - { - currBlock.PagesCount = (baseAddress - currBaseAddr) / PageSize; - ulong newPagesCount = (currEndAddr - baseAddress) / PageSize; - - newNode = _blocks.AddAfter(node, new KMemoryBlock( - baseAddress, - newPagesCount, - newState, - newPermission, - newAttribute)); - } - else - { - currBlock.PagesCount = (baseAddress - currBaseAddr) / PageSize; - - ulong nextPagesCount = (currEndAddr - endAddr) / PageSize; - - newNode = _blocks.AddAfter(node, new KMemoryBlock( - baseAddress, - pagesCount, - newState, - newPermission, - newAttribute)); - - _blocks.AddAfter(newNode, new KMemoryBlock( - endAddr, - nextPagesCount, - currBlock.State, - currBlock.Permission, - currBlock.Attribute)); - - nextNode = null; - } + newNode.Value.SetState(newPermission, newState, newAttribute); MergeEqualStateNeighbours(newNode); } + if (currEndAddr - 1 >= endAddr - 1) + { + break; + } + node = nextNode; } @@ -1859,13 +2474,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory { //Inserts new block at the list, replacing and spliting //existing blocks as needed. - KMemoryBlock block = new KMemoryBlock(baseAddress, pagesCount, state, permission, attribute); - int oldCount = _blocks.Count; - ulong endAddr = pagesCount * PageSize + baseAddress; - - LinkedListNode newNode = null; + ulong endAddr = baseAddress + pagesCount * PageSize; LinkedListNode node = _blocks.First; @@ -1880,68 +2491,98 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory if (baseAddress < currEndAddr && currBaseAddr < endAddr) { - if (baseAddress >= currBaseAddr && endAddr <= currEndAddr) + LinkedListNode newNode = node; + + if (baseAddress > currBaseAddr) { - block.Attribute |= currBlock.Attribute & MemoryAttribute.IpcAndDeviceMapped; + _blocks.AddBefore(node, currBlock.SplitRightAtAddress(baseAddress)); } - if (baseAddress > currBaseAddr && endAddr < currEndAddr) + if (endAddr < currEndAddr) { - currBlock.PagesCount = (baseAddress - currBaseAddr) / PageSize; - - ulong nextPagesCount = (currEndAddr - endAddr) / PageSize; - - newNode = _blocks.AddAfter(node, block); - - _blocks.AddAfter(newNode, new KMemoryBlock( - endAddr, - nextPagesCount, - currBlock.State, - currBlock.Permission, - currBlock.Attribute)); - - break; + newNode = _blocks.AddBefore(node, currBlock.SplitRightAtAddress(endAddr)); } - else if (baseAddress <= currBaseAddr && endAddr < currEndAddr) - { - currBlock.BaseAddress = endAddr; - currBlock.PagesCount = (currEndAddr - endAddr) / PageSize; + newNode.Value.SetState(permission, state, attribute); - if (newNode == null) - { - newNode = _blocks.AddBefore(node, block); - } - } - else if (baseAddress > currBaseAddr && endAddr >= currEndAddr) - { - currBlock.PagesCount = (baseAddress - currBaseAddr) / PageSize; + MergeEqualStateNeighbours(newNode); + } - if (newNode == null) - { - newNode = _blocks.AddAfter(node, block); - } - } - else - { - if (newNode == null) - { - newNode = _blocks.AddBefore(node, block); - } - - _blocks.Remove(node); - } + if (currEndAddr - 1 >= endAddr - 1) + { + break; } node = nextNode; } - if (newNode == null) - { - newNode = _blocks.AddFirst(block); - } + _blockAllocator.Count += _blocks.Count - oldCount; + } - MergeEqualStateNeighbours(newNode); + private static void SetIpcMappingPermissions(KMemoryBlock block, MemoryPermission permission) + { + block.SetIpcMappingPermission(permission); + } + + private static void RestoreIpcMappingPermissions(KMemoryBlock block, MemoryPermission permission) + { + block.RestoreIpcMappingPermission(); + } + + private delegate void BlockMutator(KMemoryBlock block, MemoryPermission newPerm); + + private void InsertBlock( + ulong baseAddress, + ulong pagesCount, + BlockMutator blockMutate, + MemoryPermission permission = MemoryPermission.None) + { + //Inserts new block at the list, replacing and spliting + //existing blocks as needed, then calling the callback + //function on the new block. + int oldCount = _blocks.Count; + + ulong endAddr = baseAddress + pagesCount * PageSize; + + LinkedListNode node = _blocks.First; + + while (node != null) + { + KMemoryBlock currBlock = node.Value; + + LinkedListNode nextNode = node.Next; + + ulong currBaseAddr = currBlock.BaseAddress; + ulong currEndAddr = currBlock.PagesCount * PageSize + currBaseAddr; + + if (baseAddress < currEndAddr && currBaseAddr < endAddr) + { + LinkedListNode newNode = node; + + if (baseAddress > currBaseAddr) + { + _blocks.AddBefore(node, currBlock.SplitRightAtAddress(baseAddress)); + } + + if (endAddr < currEndAddr) + { + newNode = _blocks.AddBefore(node, currBlock.SplitRightAtAddress(endAddr)); + } + + KMemoryBlock newBlock = newNode.Value; + + blockMutate(newBlock, permission); + + MergeEqualStateNeighbours(newNode); + } + + if (currEndAddr - 1 >= endAddr - 1) + { + break; + } + + node = nextNode; + } _blockAllocator.Count += _blocks.Count - oldCount; } @@ -1950,42 +2591,117 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory { KMemoryBlock block = node.Value; - ulong endAddr = block.PagesCount * PageSize + block.BaseAddress; - if (node.Previous != null) { - KMemoryBlock previous = node.Previous.Value; + KMemoryBlock previousBlock = node.Previous.Value; - if (BlockStateEquals(block, previous)) + if (BlockStateEquals(block, previousBlock)) { - _blocks.Remove(node.Previous); + LinkedListNode previousNode = node.Previous; - block.BaseAddress = previous.BaseAddress; + _blocks.Remove(node); + + previousBlock.AddPages(block.PagesCount); + + node = previousNode; + block = previousBlock; } } if (node.Next != null) { - KMemoryBlock next = node.Next.Value; + KMemoryBlock nextBlock = node.Next.Value; - if (BlockStateEquals(block, next)) + if (BlockStateEquals(block, nextBlock)) { _blocks.Remove(node.Next); - endAddr = next.BaseAddress + next.PagesCount * PageSize; + block.AddPages(nextBlock.PagesCount); } } - - block.PagesCount = (endAddr - block.BaseAddress) / 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; + return lhs.State == rhs.State && + lhs.Permission == rhs.Permission && + lhs.Attribute == rhs.Attribute && + lhs.SourcePermission == rhs.SourcePermission && + lhs.DeviceRefCount == rhs.DeviceRefCount && + lhs.IpcRefCount == rhs.IpcRefCount; + } + + private ulong AllocateVa( + ulong regionStart, + ulong regionPagesCount, + ulong neededPagesCount, + int alignment) + { + ulong address = 0; + + ulong regionEndAddr = regionStart + regionPagesCount * PageSize; + + ulong reservedPagesCount = _isKernel ? 1UL : 4UL; + + if (_aslrEnabled) + { + ulong totalNeededSize = (reservedPagesCount + neededPagesCount) * PageSize; + + ulong remainingPages = regionPagesCount - neededPagesCount; + + ulong aslrMaxOffset = ((remainingPages + reservedPagesCount) * PageSize) / (ulong)alignment; + + for (int attempt = 0; attempt < 8; attempt++) + { + address = BitUtils.AlignDown(regionStart + GetRandomValue(0, aslrMaxOffset) * (ulong)alignment, alignment); + + ulong endAddr = address + totalNeededSize; + + KMemoryInfo info = FindBlock(address).GetInfo(); + + if (info.State != MemoryState.Unmapped) + { + continue; + } + + ulong currBaseAddr = info.Address + reservedPagesCount * PageSize; + ulong currEndAddr = info.Address + info.Size; + + if (address >= regionStart && + address >= currBaseAddr && + endAddr - 1 <= regionEndAddr - 1 && + endAddr - 1 <= currEndAddr - 1) + { + break; + } + } + + if (address == 0) + { + ulong aslrPage = GetRandomValue(0, aslrMaxOffset); + + address = FindFirstFit( + regionStart + aslrPage * PageSize, + regionPagesCount - aslrPage, + neededPagesCount, + alignment, + 0, + reservedPagesCount); + } + } + + if (address == 0) + { + address = FindFirstFit( + regionStart, + regionPagesCount, + neededPagesCount, + alignment, + 0, + reservedPagesCount); + } + + return address; } private ulong FindFirstFit( @@ -2397,11 +3113,21 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory return KernelResult.Success; } - public KernelResult ConvertVaToPa(ulong va, out ulong pa) + public ulong GetDramAddressFromVa(ulong va) + { + return (ulong)_cpuMemory.GetPhysicalAddress((long)va); + } + + public bool ConvertVaToPa(ulong va, out ulong pa) { pa = DramMemoryMap.DramBase + (ulong)_cpuMemory.GetPhysicalAddress((long)va); - return KernelResult.Success; + return true; + } + + public static ulong GetDramAddressFromPa(ulong pa) + { + return pa - DramMemoryMap.DramBase; } public long GetMmUsedPages() diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryRegionManager.cs b/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryRegionManager.cs index 777e9aa9..92cef559 100644 --- a/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryRegionManager.cs +++ b/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryRegionManager.cs @@ -94,6 +94,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory } } + public ulong AllocatePagesContiguous(ulong pagesCount, bool backwards) + { + lock (_blocks) + { + return AllocatePagesContiguousImpl(pagesCount, backwards); + } + } + private KernelResult AllocatePagesImpl(ulong pagesCount, bool backwards, out KPageList pageList) { pageList = new KPageList(); @@ -122,113 +130,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory //If so, try allocating as much requested pages as possible. while (blockPagesCount <= pagesCount) { - ulong address = 0; - - for (int currBlockIndex = blockIndex; - currBlockIndex < _blockOrdersCount && address == 0; - currBlockIndex++) - { - block = _blocks[currBlockIndex]; - - int index = 0; - - bool zeroMask = false; - - for (int level = 0; level < block.MaxLevel; level++) - { - long mask = block.Masks[level][index]; - - if (mask == 0) - { - zeroMask = true; - - break; - } - - if (backwards) - { - index = (index * 64 + 63) - BitUtils.CountLeadingZeros64(mask); - } - else - { - index = index * 64 + BitUtils.CountLeadingZeros64(BitUtils.ReverseBits64(mask)); - } - } - - if (block.SizeInBlocksTruncated <= (ulong)index || zeroMask) - { - continue; - } - - block.FreeCount--; - - int tempIdx = index; - - for (int level = block.MaxLevel - 1; level >= 0; level--, tempIdx /= 64) - { - block.Masks[level][tempIdx / 64] &= ~(1L << (tempIdx & 63)); - - if (block.Masks[level][tempIdx / 64] != 0) - { - break; - } - } - - address = block.StartAligned + ((ulong)index << block.Order); - } - - for (int currBlockIndex = blockIndex; - currBlockIndex < _blockOrdersCount && address == 0; - currBlockIndex++) - { - block = _blocks[currBlockIndex]; - - int index = 0; - - bool zeroMask = false; - - for (int level = 0; level < block.MaxLevel; level++) - { - long mask = block.Masks[level][index]; - - if (mask == 0) - { - zeroMask = true; - - break; - } - - if (backwards) - { - index = index * 64 + BitUtils.CountLeadingZeros64(BitUtils.ReverseBits64(mask)); - } - else - { - index = (index * 64 + 63) - BitUtils.CountLeadingZeros64(mask); - } - } - - if (block.SizeInBlocksTruncated <= (ulong)index || zeroMask) - { - continue; - } - - block.FreeCount--; - - int tempIdx = index; - - for (int level = block.MaxLevel - 1; level >= 0; level--, tempIdx /= 64) - { - block.Masks[level][tempIdx / 64] &= ~(1L << (tempIdx & 63)); - - if (block.Masks[level][tempIdx / 64] != 0) - { - break; - } - } - - address = block.StartAligned + ((ulong)index << block.Order); - } + ulong address = AllocatePagesForOrder(blockIndex, backwards, bestFitBlockSize); //The address being zero means that no free space was found on that order, //just give up and try with the next one. @@ -237,15 +139,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory break; } - //If we are using a larger order than best fit, then we should - //split it into smaller blocks. - ulong firstFreeBlockSize = 1UL << block.Order; - - if (firstFreeBlockSize > bestFitBlockSize) - { - FreePages(address + bestFitBlockSize, (firstFreeBlockSize - bestFitBlockSize) / KMemoryManager.PageSize); - } - //Add new allocated page(s) to the pages list. //If an error occurs, then free all allocated pages and fail. KernelResult result = pageList.AddRange(address, blockPagesCount); @@ -283,6 +176,172 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory return KernelResult.OutOfMemory; } + private ulong AllocatePagesContiguousImpl(ulong pagesCount, bool backwards) + { + if (pagesCount == 0 || _blocks.Length < 1) + { + return 0; + } + + int blockIndex = 0; + + while ((1UL << _blocks[blockIndex].Order) / KMemoryManager.PageSize < pagesCount) + { + if (++blockIndex >= _blocks.Length) + { + return 0; + } + } + + ulong tightestFitBlockSize = 1UL << _blocks[blockIndex].Order; + + ulong address = AllocatePagesForOrder(blockIndex, backwards, tightestFitBlockSize); + + ulong requiredSize = pagesCount * KMemoryManager.PageSize; + + if (address != 0 && tightestFitBlockSize > requiredSize) + { + FreePages(address + requiredSize, (tightestFitBlockSize - requiredSize) / KMemoryManager.PageSize); + } + + return address; + } + + private ulong AllocatePagesForOrder(int blockIndex, bool backwards, ulong bestFitBlockSize) + { + ulong address = 0; + + KMemoryRegionBlock block = null; + + for (int currBlockIndex = blockIndex; + currBlockIndex < _blockOrdersCount && address == 0; + currBlockIndex++) + { + block = _blocks[currBlockIndex]; + + int index = 0; + + bool zeroMask = false; + + for (int level = 0; level < block.MaxLevel; level++) + { + long mask = block.Masks[level][index]; + + if (mask == 0) + { + zeroMask = true; + + break; + } + + if (backwards) + { + index = (index * 64 + 63) - BitUtils.CountLeadingZeros64(mask); + } + else + { + index = index * 64 + BitUtils.CountLeadingZeros64(BitUtils.ReverseBits64(mask)); + } + } + + if (block.SizeInBlocksTruncated <= (ulong)index || zeroMask) + { + continue; + } + + block.FreeCount--; + + int tempIdx = index; + + for (int level = block.MaxLevel - 1; level >= 0; level--, tempIdx /= 64) + { + block.Masks[level][tempIdx / 64] &= ~(1L << (tempIdx & 63)); + + if (block.Masks[level][tempIdx / 64] != 0) + { + break; + } + } + + address = block.StartAligned + ((ulong)index << block.Order); + } + + for (int currBlockIndex = blockIndex; + currBlockIndex < _blockOrdersCount && address == 0; + currBlockIndex++) + { + block = _blocks[currBlockIndex]; + + int index = 0; + + bool zeroMask = false; + + for (int level = 0; level < block.MaxLevel; level++) + { + long mask = block.Masks[level][index]; + + if (mask == 0) + { + zeroMask = true; + + break; + } + + if (backwards) + { + index = index * 64 + BitUtils.CountLeadingZeros64(BitUtils.ReverseBits64(mask)); + } + else + { + index = (index * 64 + 63) - BitUtils.CountLeadingZeros64(mask); + } + } + + if (block.SizeInBlocksTruncated <= (ulong)index || zeroMask) + { + continue; + } + + block.FreeCount--; + + int tempIdx = index; + + for (int level = block.MaxLevel - 1; level >= 0; level--, tempIdx /= 64) + { + block.Masks[level][tempIdx / 64] &= ~(1L << (tempIdx & 63)); + + if (block.Masks[level][tempIdx / 64] != 0) + { + break; + } + } + + address = block.StartAligned + ((ulong)index << block.Order); + } + + if (address != 0) + { + //If we are using a larger order than best fit, then we should + //split it into smaller blocks. + ulong firstFreeBlockSize = 1UL << block.Order; + + if (firstFreeBlockSize > bestFitBlockSize) + { + FreePages(address + bestFitBlockSize, (firstFreeBlockSize - bestFitBlockSize) / KMemoryManager.PageSize); + } + } + + return address; + } + + public void FreePage(ulong address) + { + lock (_blocks) + { + FreePages(address, 1); + } + } + public void FreePages(KPageList pageList) { lock (_blocks) diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/KSharedMemory.cs b/Ryujinx.HLE/HOS/Kernel/Memory/KSharedMemory.cs index f2a05bda..6b92ed30 100644 --- a/Ryujinx.HLE/HOS/Kernel/Memory/KSharedMemory.cs +++ b/Ryujinx.HLE/HOS/Kernel/Memory/KSharedMemory.cs @@ -4,7 +4,7 @@ using Ryujinx.HLE.HOS.Kernel.Process; namespace Ryujinx.HLE.HOS.Kernel.Memory { - class KSharedMemory + class KSharedMemory : KAutoObject { private KPageList _pageList; @@ -14,10 +14,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory private MemoryPermission _userPermission; public KSharedMemory( + Horizon system, KPageList pageList, long ownerPid, MemoryPermission ownerPermission, - MemoryPermission userPermission) + MemoryPermission userPermission) : base(system) { _pageList = pageList; _ownerPid = ownerPid; diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/KTransferMemory.cs b/Ryujinx.HLE/HOS/Kernel/Memory/KTransferMemory.cs index 02367e89..a0929eca 100644 --- a/Ryujinx.HLE/HOS/Kernel/Memory/KTransferMemory.cs +++ b/Ryujinx.HLE/HOS/Kernel/Memory/KTransferMemory.cs @@ -1,11 +1,13 @@ +using Ryujinx.HLE.HOS.Kernel.Common; + namespace Ryujinx.HLE.HOS.Kernel.Memory { - class KTransferMemory + class KTransferMemory : KAutoObject { public ulong Address { get; private set; } public ulong Size { get; private set; } - public KTransferMemory(ulong address, ulong size) + public KTransferMemory(Horizon system, ulong address, ulong size) : base(system) { Address = address; Size = size; diff --git a/Ryujinx.HLE/HOS/Kernel/Process/KHandleEntry.cs b/Ryujinx.HLE/HOS/Kernel/Process/KHandleEntry.cs index 87137d0f..b5ca9b5e 100644 --- a/Ryujinx.HLE/HOS/Kernel/Process/KHandleEntry.cs +++ b/Ryujinx.HLE/HOS/Kernel/Process/KHandleEntry.cs @@ -1,3 +1,5 @@ +using Ryujinx.HLE.HOS.Kernel.Common; + namespace Ryujinx.HLE.HOS.Kernel.Process { class KHandleEntry @@ -6,8 +8,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process public int Index { get; private set; } - public ushort HandleId { get; set; } - public object Obj { get; set; } + public ushort HandleId { get; set; } + public KAutoObject Obj { get; set; } public KHandleEntry(int index) { diff --git a/Ryujinx.HLE/HOS/Kernel/Process/KHandleTable.cs b/Ryujinx.HLE/HOS/Kernel/Process/KHandleTable.cs index 413edf94..88c2e690 100644 --- a/Ryujinx.HLE/HOS/Kernel/Process/KHandleTable.cs +++ b/Ryujinx.HLE/HOS/Kernel/Process/KHandleTable.cs @@ -6,8 +6,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process { class KHandleTable { - private const int SelfThreadHandle = (0x1ffff << 15) | 0; - private const int SelfProcessHandle = (0x1ffff << 15) | 1; + public const int SelfThreadHandle = (0x1ffff << 15) | 0; + public const int SelfProcessHandle = (0x1ffff << 15) | 1; private Horizon _system; @@ -65,7 +65,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process return KernelResult.Success; } - public KernelResult GenerateHandle(object obj, out int handle) + public KernelResult GenerateHandle(KAutoObject obj, out int handle) { handle = 0; @@ -85,7 +85,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Process _activeSlotsCount++; - handle = (int)((_idCounter << 15) & 0xffff8000) | entry.Index; + handle = (_idCounter << 15) | entry.Index; + + obj.IncrementReferenceCount(); if ((short)(_idCounter + 1) >= 0) { @@ -100,6 +102,72 @@ namespace Ryujinx.HLE.HOS.Kernel.Process return KernelResult.Success; } + public KernelResult ReserveHandle(out int handle) + { + handle = 0; + + lock (_table) + { + if (_activeSlotsCount >= _size) + { + return KernelResult.HandleTableFull; + } + + KHandleEntry entry = _nextFreeEntry; + + _nextFreeEntry = entry.Next; + + _activeSlotsCount++; + + handle = (_idCounter << 15) | entry.Index; + + if ((short)(_idCounter + 1) >= 0) + { + _idCounter++; + } + else + { + _idCounter = 1; + } + } + + return KernelResult.Success; + } + + public void CancelHandleReservation(int handle) + { + int index = (handle >> 0) & 0x7fff; + int handleId = (handle >> 15); + + lock (_table) + { + KHandleEntry entry = _table[index]; + + entry.Obj = null; + entry.Next = _nextFreeEntry; + + _nextFreeEntry = entry; + + _activeSlotsCount--; + } + } + + public void SetReservedHandleObj(int handle, KAutoObject obj) + { + int index = (handle >> 0) & 0x7fff; + int handleId = (handle >> 15); + + lock (_table) + { + KHandleEntry entry = _table[index]; + + entry.Obj = obj; + entry.HandleId = (ushort)(handle >> 15); + + obj.IncrementReferenceCount(); + } + } + public bool CloseHandle(int handle) { if ((handle >> 30) != 0 || @@ -112,6 +180,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process int index = (handle >> 0) & 0x7fff; int handleId = (handle >> 15); + KAutoObject obj = null; + bool result = false; lock (_table) @@ -120,7 +190,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process { KHandleEntry entry = _table[index]; - if (entry.Obj != null && entry.HandleId == handleId) + if ((obj = entry.Obj) != null && entry.HandleId == handleId) { entry.Obj = null; entry.Next = _nextFreeEntry; @@ -134,17 +204,22 @@ namespace Ryujinx.HLE.HOS.Kernel.Process } } + if (result) + { + obj.DecrementReferenceCount(); + } + return result; } - public T GetObject(int handle) + public T GetObject(int handle) where T : KAutoObject { int index = (handle >> 0) & 0x7fff; int handleId = (handle >> 15); lock (_table) { - if ((handle >> 30) == 0 && handleId != 0) + if ((handle >> 30) == 0 && handleId != 0 && index < _size) { KHandleEntry entry = _table[index]; diff --git a/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs b/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs index 0d77a495..855f3a18 100644 --- a/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs +++ b/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs @@ -30,7 +30,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process private SortedDictionary _fullTlsPages; private SortedDictionary _freeTlsPages; - public int DefaultCpuCore { get; private set; } + public int DefaultCpuCore { get; set; } public bool Debug { get; private set; } @@ -557,14 +557,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Process private KernelResult FreeTlsPage(KTlsPageInfo pageInfo) { - KernelResult result = MemoryManager.ConvertVaToPa(pageInfo.PageAddr, out ulong tlsPagePa); - - if (result != KernelResult.Success) + if (!MemoryManager.ConvertVaToPa(pageInfo.PageAddr, out ulong tlsPagePa)) { throw new InvalidOperationException("Unexpected failure translating virtual address to physical."); } - result = MemoryManager.UnmapForKernel(pageInfo.PageAddr, 1, MemoryState.ThreadLocal); + KernelResult result = MemoryManager.UnmapForKernel(pageInfo.PageAddr, 1, MemoryState.ThreadLocal); if (result == KernelResult.Success) { @@ -636,9 +634,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Process void CleanUpForError() { - mainThread?.Terminate(); HandleTable.Destroy(); + mainThread?.DecrementReferenceCount(); + if (_mainThreadStackSize != 0) { ulong stackBottom = stackTop - _mainThreadStackSize; @@ -646,6 +645,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process ulong stackPagesCount = _mainThreadStackSize / KMemoryManager.PageSize; MemoryManager.UnmapForKernel(stackBottom, stackPagesCount, MemoryState.Stack); + + _mainThreadStackSize = 0; } memoryResourceLimit?.Release(LimitableResource.Memory, stackSizeRounded); @@ -756,6 +757,13 @@ namespace Ryujinx.HLE.HOS.Kernel.Process mainThread.Reschedule(ThreadSchedState.Running); + if (result == KernelResult.Success) + { + mainThread.IncrementReferenceCount(); + } + + mainThread.DecrementReferenceCount(); + return result; } } diff --git a/Ryujinx.HLE/HOS/Kernel/Process/KProcessCapabilities.cs b/Ryujinx.HLE/HOS/Kernel/Process/KProcessCapabilities.cs index 033f0a2c..964762bb 100644 --- a/Ryujinx.HLE/HOS/Kernel/Process/KProcessCapabilities.cs +++ b/Ryujinx.HLE/HOS/Kernel/Process/KProcessCapabilities.cs @@ -306,6 +306,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Process { int range = max - min + 1; + if (range == 64) + { + return -1L; + } + long mask = (1L << range) - 1; return mask << min; diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcHandler.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcHandler.cs index 08340b06..071b3c20 100644 --- a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcHandler.cs +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcHandler.cs @@ -1,10 +1,7 @@ using ChocolArm64.Events; using ChocolArm64.Memory; using ChocolArm64.State; -using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.HOS.Kernel.Ipc; using Ryujinx.HLE.HOS.Kernel.Process; -using Ryujinx.HLE.HOS.Kernel.Threading; using System; namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall @@ -16,26 +13,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall private Horizon _system; private MemoryManager _memory; - private struct HleIpcMessage - { - public KThread Thread { get; private set; } - public KSession Session { get; private set; } - public IpcMessage Message { get; private set; } - public long MessagePtr { get; private set; } - - public HleIpcMessage( - KThread thread, - KSession session, - IpcMessage message, - long messagePtr) - { - Thread = thread; - Session = session; - Message = message; - MessagePtr = messagePtr; - } - } - public SvcHandler(Switch device, KProcess process) { _device = device; diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcIpc.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcIpc.cs new file mode 100644 index 00000000..54939418 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcIpc.cs @@ -0,0 +1,532 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Ipc; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Kernel.Threading; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall +{ + partial class SvcHandler + { + private struct HleIpcMessage + { + public KThread Thread { get; private set; } + public KClientSession Session { get; private set; } + public IpcMessage Message { get; private set; } + public long MessagePtr { get; private set; } + + public HleIpcMessage( + KThread thread, + KClientSession session, + IpcMessage message, + long messagePtr) + { + Thread = thread; + Session = session; + Message = message; + MessagePtr = messagePtr; + } + } + + public KernelResult ConnectToNamedPort64(ulong namePtr, out int handle) + { + return ConnectToNamedPort(namePtr, out handle); + } + + private KernelResult ConnectToNamedPort(ulong namePtr, out int handle) + { + handle = 0; + + if (!KernelTransfer.UserToKernelString(_system, namePtr, 12, out string name)) + { + return KernelResult.UserCopyFailed; + } + + if (name.Length > 11) + { + return KernelResult.MaximumExceeded; + } + + KAutoObject autoObj = KAutoObject.FindNamedObject(_system, name); + + if (!(autoObj is KClientPort clientPort)) + { + return KernelResult.NotFound; + } + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + KernelResult result = currentProcess.HandleTable.ReserveHandle(out handle); + + if (result != KernelResult.Success) + { + return result; + } + + result = clientPort.Connect(out KClientSession clientSession); + + if (result != KernelResult.Success) + { + currentProcess.HandleTable.CancelHandleReservation(handle); + + return result; + } + + currentProcess.HandleTable.SetReservedHandleObj(handle, clientSession); + + clientSession.DecrementReferenceCount(); + + return result; + } + + public KernelResult SendSyncRequest64(int handle) + { + return SendSyncRequest((ulong)_system.Scheduler.GetCurrentThread().Context.ThreadState.Tpidr, 0x100, handle); + } + + public KernelResult SendSyncRequestWithUserBuffer64(ulong messagePtr, ulong size, int handle) + { + return SendSyncRequest(messagePtr, size, handle); + } + + private KernelResult SendSyncRequest(ulong messagePtr, ulong size, int handle) + { + byte[] messageData = _memory.ReadBytes((long)messagePtr, (long)size); + + KClientSession clientSession = _process.HandleTable.GetObject(handle); + + if (clientSession == null || clientSession.Service == null) + { + return SendSyncRequest_(handle); + } + + if (clientSession != null) + { + _system.CriticalSection.Enter(); + + KThread currentThread = _system.Scheduler.GetCurrentThread(); + + currentThread.SignaledObj = null; + currentThread.ObjSyncResult = KernelResult.Success; + + currentThread.Reschedule(ThreadSchedState.Paused); + + IpcMessage message = new IpcMessage(messageData, (long)messagePtr); + + ThreadPool.QueueUserWorkItem(ProcessIpcRequest, new HleIpcMessage( + currentThread, + clientSession, + message, + (long)messagePtr)); + + _system.ThreadCounter.AddCount(); + + _system.CriticalSection.Leave(); + + return currentThread.ObjSyncResult; + } + else + { + Logger.PrintWarning(LogClass.KernelSvc, $"Invalid session handle 0x{handle:x8}!"); + + return KernelResult.InvalidHandle; + } + } + + private void ProcessIpcRequest(object state) + { + HleIpcMessage ipcMessage = (HleIpcMessage)state; + + ipcMessage.Thread.ObjSyncResult = IpcHandler.IpcCall( + _device, + _process, + _memory, + ipcMessage.Session, + ipcMessage.Message, + ipcMessage.MessagePtr); + + _system.ThreadCounter.Signal(); + + ipcMessage.Thread.Reschedule(ThreadSchedState.Running); + } + + private KernelResult SendSyncRequest_(int handle) + { + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + KClientSession session = currentProcess.HandleTable.GetObject(handle); + + if (session == null) + { + return KernelResult.InvalidHandle; + } + + return session.SendSyncRequest(); + } + + public KernelResult CreateSession64( + bool isLight, + ulong namePtr, + out int serverSessionHandle, + out int clientSessionHandle) + { + return CreateSession(isLight, namePtr, out serverSessionHandle, out clientSessionHandle); + } + + private KernelResult CreateSession( + bool isLight, + ulong namePtr, + out int serverSessionHandle, + out int clientSessionHandle) + { + serverSessionHandle = 0; + clientSessionHandle = 0; + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + KResourceLimit resourceLimit = currentProcess.ResourceLimit; + + KernelResult result = KernelResult.Success; + + if (resourceLimit != null && !resourceLimit.Reserve(LimitableResource.Session, 1)) + { + return KernelResult.ResLimitExceeded; + } + + if (isLight) + { + KLightSession session = new KLightSession(_system); + + result = currentProcess.HandleTable.GenerateHandle(session.ServerSession, out serverSessionHandle); + + if (result == KernelResult.Success) + { + result = currentProcess.HandleTable.GenerateHandle(session.ClientSession, out clientSessionHandle); + + if (result != KernelResult.Success) + { + currentProcess.HandleTable.CloseHandle(serverSessionHandle); + + serverSessionHandle = 0; + } + } + + session.ServerSession.DecrementReferenceCount(); + session.ClientSession.DecrementReferenceCount(); + } + else + { + KSession session = new KSession(_system); + + result = currentProcess.HandleTable.GenerateHandle(session.ServerSession, out serverSessionHandle); + + if (result == KernelResult.Success) + { + result = currentProcess.HandleTable.GenerateHandle(session.ClientSession, out clientSessionHandle); + + if (result != KernelResult.Success) + { + currentProcess.HandleTable.CloseHandle(serverSessionHandle); + + serverSessionHandle = 0; + } + } + + session.ServerSession.DecrementReferenceCount(); + session.ClientSession.DecrementReferenceCount(); + } + + return result; + } + + public KernelResult AcceptSession64(int portHandle, out int sessionHandle) + { + return AcceptSession(portHandle, out sessionHandle); + } + + private KernelResult AcceptSession(int portHandle, out int sessionHandle) + { + sessionHandle = 0; + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + KServerPort serverPort = currentProcess.HandleTable.GetObject(portHandle); + + if (serverPort == null) + { + return KernelResult.InvalidHandle; + } + + KernelResult result = currentProcess.HandleTable.ReserveHandle(out int handle); + + if (result != KernelResult.Success) + { + return result; + } + + KAutoObject session; + + if (serverPort.IsLight) + { + session = serverPort.AcceptIncomingLightConnection(); + } + else + { + session = serverPort.AcceptIncomingConnection(); + } + + if (session != null) + { + currentProcess.HandleTable.SetReservedHandleObj(handle, session); + + session.DecrementReferenceCount(); + + sessionHandle = handle; + + result = KernelResult.Success; + } + else + { + currentProcess.HandleTable.CancelHandleReservation(handle); + + result = KernelResult.NotFound; + } + + return result; + } + + public KernelResult ReplyAndReceive64( + ulong handlesPtr, + int handlesCount, + int replyTargetHandle, + long timeout, + out int handleIndex) + { + handleIndex = 0; + + if ((uint)handlesCount > 0x40) + { + return KernelResult.MaximumExceeded; + } + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + ulong copySize = (ulong)((long)handlesCount * 4); + + if (!currentProcess.MemoryManager.InsideAddrSpace(handlesPtr, copySize)) + { + return KernelResult.UserCopyFailed; + } + + if (handlesPtr + copySize < handlesPtr) + { + return KernelResult.UserCopyFailed; + } + + int[] handles = new int[handlesCount]; + + if (!KernelTransfer.UserToKernelInt32Array(_system, handlesPtr, handles)) + { + return KernelResult.UserCopyFailed; + } + + KSynchronizationObject[] syncObjs = new KSynchronizationObject[handlesCount]; + + for (int index = 0; index < handlesCount; index++) + { + KSynchronizationObject obj = currentProcess.HandleTable.GetObject(handles[index]); + + if (obj == null) + { + return KernelResult.InvalidHandle; + } + + syncObjs[index] = obj; + } + + KernelResult result; + + if (replyTargetHandle != 0) + { + KServerSession replyTarget = currentProcess.HandleTable.GetObject(replyTargetHandle); + + if (replyTarget == null) + { + return KernelResult.InvalidHandle; + } + + result = replyTarget.Reply(); + + if (result != KernelResult.Success) + { + return result; + } + } + + while ((result = _system.Synchronization.WaitFor(syncObjs, timeout, out handleIndex)) == KernelResult.Success) + { + KServerSession session = currentProcess.HandleTable.GetObject(handles[handleIndex]); + + if (session == null) + { + break; + } + + if ((result = session.Receive()) != KernelResult.NotFound) + { + break; + } + } + + return result; + } + + public KernelResult CreatePort64( + int maxSessions, + bool isLight, + ulong namePtr, + out int serverPortHandle, + out int clientPortHandle) + { + return CreatePort(maxSessions, isLight, namePtr, out serverPortHandle, out clientPortHandle); + } + + private KernelResult CreatePort( + int maxSessions, + bool isLight, + ulong namePtr, + out int serverPortHandle, + out int clientPortHandle) + { + serverPortHandle = clientPortHandle = 0; + + if (maxSessions < 1) + { + return KernelResult.MaximumExceeded; + } + + KPort port = new KPort(_system, maxSessions, isLight, (long)namePtr); + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + KernelResult result = currentProcess.HandleTable.GenerateHandle(port.ClientPort, out clientPortHandle); + + if (result != KernelResult.Success) + { + return result; + } + + result = currentProcess.HandleTable.GenerateHandle(port.ServerPort, out serverPortHandle); + + if (result != KernelResult.Success) + { + currentProcess.HandleTable.CloseHandle(clientPortHandle); + } + + return result; + } + + public KernelResult ManageNamedPort64(ulong namePtr, int maxSessions, out int handle) + { + return ManageNamedPort(namePtr, maxSessions, out handle); + } + + private KernelResult ManageNamedPort(ulong namePtr, int maxSessions, out int handle) + { + handle = 0; + + if (!KernelTransfer.UserToKernelString(_system, namePtr, 12, out string name)) + { + return KernelResult.UserCopyFailed; + } + + if (maxSessions < 0 || name.Length > 11) + { + return KernelResult.MaximumExceeded; + } + + if (maxSessions == 0) + { + return KClientPort.RemoveName(_system, name); + } + + KPort port = new KPort(_system, maxSessions, false, 0); + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + KernelResult result = currentProcess.HandleTable.GenerateHandle(port.ServerPort, out handle); + + if (result != KernelResult.Success) + { + return result; + } + + result = port.ClientPort.SetName(name); + + if (result != KernelResult.Success) + { + currentProcess.HandleTable.CloseHandle(handle); + } + + return result; + } + + public KernelResult ConnectToPort64(int clientPortHandle, out int clientSessionHandle) + { + return ConnectToPort(clientPortHandle, out clientSessionHandle); + } + + private KernelResult ConnectToPort(int clientPortHandle, out int clientSessionHandle) + { + clientSessionHandle = 0; + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + KClientPort clientPort = currentProcess.HandleTable.GetObject(clientPortHandle); + + if (clientPort == null) + { + return KernelResult.InvalidHandle; + } + + KernelResult result = currentProcess.HandleTable.ReserveHandle(out int handle); + + if (result != KernelResult.Success) + { + return result; + } + + KAutoObject session; + + if (clientPort.IsLight) + { + result = clientPort.ConnectLight(out KLightClientSession clientSession); + + session = clientSession; + } + else + { + result = clientPort.Connect(out KClientSession clientSession); + + session = clientSession; + } + + if (result != KernelResult.Success) + { + currentProcess.HandleTable.CancelHandleReservation(handle); + + return result; + } + + currentProcess.HandleTable.SetReservedHandleObj(handle, session); + + session.DecrementReferenceCount(); + + clientSessionHandle = handle; + + return result; + } + } +} diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcMemory.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcMemory.cs index e6590522..388dcc21 100644 --- a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcMemory.cs +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcMemory.cs @@ -305,7 +305,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return result; } - KTransferMemory transferMemory = new KTransferMemory(address, size); + KTransferMemory transferMemory = new KTransferMemory(_system, address, size); return _process.HandleTable.GenerateHandle(transferMemory, out handle); } @@ -350,7 +350,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall public KernelResult UnmapPhysicalMemory64(ulong address, ulong size) { - return MapPhysicalMemory(address, size); + return UnmapPhysicalMemory(address, size); } private KernelResult UnmapPhysicalMemory(ulong address, ulong size) diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcSystem.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcSystem.cs index b0563356..be136ff0 100644 --- a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcSystem.cs +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcSystem.cs @@ -2,14 +2,11 @@ using ChocolArm64.Memory; using Ryujinx.Common; using Ryujinx.Common.Logging; using Ryujinx.HLE.Exceptions; -using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Ipc; using Ryujinx.HLE.HOS.Kernel.Memory; using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.HOS.Kernel.Threading; -using Ryujinx.HLE.HOS.Services; -using System.Threading; namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall { @@ -82,7 +79,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall private KernelResult CloseHandle(int handle) { - object obj = _process.HandleTable.GetObject(handle); + KAutoObject obj = _process.HandleTable.GetObject(handle); _process.HandleTable.CloseHandle(handle); @@ -144,88 +141,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return _system.Scheduler.GetCurrentThread().Context.ThreadState.CntpctEl0; } - public KernelResult ConnectToNamedPort64(ulong namePtr, out int handle) - { - return ConnectToNamedPort(namePtr, out handle); - } - - private KernelResult ConnectToNamedPort(ulong namePtr, out int handle) - { - string name = MemoryHelper.ReadAsciiString(_memory, (long)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(_system, name), name); - - return _process.HandleTable.GenerateHandle(session, out handle); - } - - public KernelResult SendSyncRequest64(int handle) - { - return SendSyncRequest((ulong)_system.Scheduler.GetCurrentThread().Context.ThreadState.Tpidr, 0x100, handle); - } - - public KernelResult SendSyncRequestWithUserBuffer64(ulong messagePtr, ulong size, int handle) - { - return SendSyncRequest(messagePtr, size, handle); - } - - private KernelResult SendSyncRequest(ulong messagePtr, ulong size, int handle) - { - byte[] messageData = _memory.ReadBytes((long)messagePtr, (long)size); - - KSession session = _process.HandleTable.GetObject(handle); - - if (session != null) - { - _system.CriticalSection.Enter(); - - KThread currentThread = _system.Scheduler.GetCurrentThread(); - - currentThread.SignaledObj = null; - currentThread.ObjSyncResult = KernelResult.Success; - - currentThread.Reschedule(ThreadSchedState.Paused); - - IpcMessage message = new IpcMessage(messageData, (long)messagePtr); - - ThreadPool.QueueUserWorkItem(ProcessIpcRequest, new HleIpcMessage( - currentThread, - session, - message, - (long)messagePtr)); - - _system.ThreadCounter.AddCount(); - - _system.CriticalSection.Leave(); - - return currentThread.ObjSyncResult; - } - else - { - Logger.PrintWarning(LogClass.KernelSvc, $"Invalid session handle 0x{handle:x8}!"); - - return KernelResult.InvalidHandle; - } - } - - private void ProcessIpcRequest(object state) - { - HleIpcMessage ipcMessage = (HleIpcMessage)state; - - ipcMessage.Thread.ObjSyncResult = IpcHandler.IpcCall( - _device, - _process, - _memory, - ipcMessage.Session, - ipcMessage.Message, - ipcMessage.MessagePtr); - - _system.ThreadCounter.Signal(); - - ipcMessage.Thread.Reschedule(ThreadSchedState.Running); - } - public KernelResult GetProcessId64(int handle, out long pid) { return GetProcessId(handle, out pid); @@ -664,99 +579,5 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.Success; } - - public KernelResult CreatePort64( - int maxSessions, - bool isLight, - ulong namePtr, - out int serverPortHandle, - out int clientPortHandle) - { - return CreatePort(maxSessions, isLight, namePtr, out serverPortHandle, out clientPortHandle); - } - - private KernelResult CreatePort( - int maxSessions, - bool isLight, - ulong namePtr, - out int serverPortHandle, - out int clientPortHandle) - { - serverPortHandle = clientPortHandle = 0; - - if (maxSessions < 1) - { - return KernelResult.MaximumExceeded; - } - - KPort port = new KPort(_system); - - port.Initialize(maxSessions, isLight, (long)namePtr); - - KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); - - KernelResult result = currentProcess.HandleTable.GenerateHandle(port.ClientPort, out clientPortHandle); - - if (result != KernelResult.Success) - { - return result; - } - - result = currentProcess.HandleTable.GenerateHandle(port.ServerPort, out serverPortHandle); - - if (result != KernelResult.Success) - { - currentProcess.HandleTable.CloseHandle(clientPortHandle); - } - - return result; - } - - public KernelResult ManageNamedPort64(ulong namePtr, int maxSessions, out int handle) - { - return ManageNamedPort(namePtr, maxSessions, out handle); - } - - private KernelResult ManageNamedPort(ulong namePtr, int maxSessions, out int handle) - { - handle = 0; - - if (!KernelTransfer.UserToKernelString(_system, namePtr, 12, out string name)) - { - return KernelResult.UserCopyFailed; - } - - if (maxSessions < 0 || name.Length > 11) - { - return KernelResult.MaximumExceeded; - } - - if (maxSessions == 0) - { - return KClientPort.RemoveName(_system, name); - } - - KPort port = new KPort(_system); - - KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); - - KernelResult result = currentProcess.HandleTable.GenerateHandle(port.ServerPort, out handle); - - if (result != KernelResult.Success) - { - return result; - } - - port.Initialize(maxSessions, false, 0); - - result = port.SetName(name); - - if (result != KernelResult.Success) - { - currentProcess.HandleTable.CloseHandle(handle); - } - - return result; - } } } diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcTable.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcTable.cs index a6111777..cbcb712f 100644 --- a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcTable.cs +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcTable.cs @@ -63,11 +63,15 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall { 0x33, nameof(SvcHandler.GetThreadContext364) }, { 0x34, nameof(SvcHandler.WaitForAddress64) }, { 0x35, nameof(SvcHandler.SignalToAddress64) }, + { 0x40, nameof(SvcHandler.CreateSession64) }, + { 0x41, nameof(SvcHandler.AcceptSession64) }, + { 0x43, nameof(SvcHandler.ReplyAndReceive64) }, { 0x45, nameof(SvcHandler.CreateEvent64) }, { 0x65, nameof(SvcHandler.GetProcessList64) }, { 0x6f, nameof(SvcHandler.GetSystemInfo64) }, { 0x70, nameof(SvcHandler.CreatePort64) }, - { 0x71, nameof(SvcHandler.ManageNamedPort64) } + { 0x71, nameof(SvcHandler.ManageNamedPort64) }, + { 0x72, nameof(SvcHandler.ConnectToPort64) } }; _svcTable64 = new Action[0x80]; diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcThread.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcThread.cs index 1e1927fe..64268ff2 100644 --- a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcThread.cs +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcThread.cs @@ -62,21 +62,16 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall priority, cpuCore); - if (result != KernelResult.Success) + if (result == KernelResult.Success) + { + result = _process.HandleTable.GenerateHandle(thread, out handle); + } + else { currentProcess.ResourceLimit?.Release(LimitableResource.Thread, 1); - - return result; } - result = _process.HandleTable.GenerateHandle(thread, out handle); - - if (result != KernelResult.Success) - { - thread.Terminate(); - - currentProcess.ResourceLimit?.Release(LimitableResource.Thread, 1); - } + thread.DecrementReferenceCount(); return result; } @@ -88,11 +83,22 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall private KernelResult StartThread(int handle) { - KThread thread = _process.HandleTable.GetObject(handle); + KThread thread = _process.HandleTable.GetKThread(handle); if (thread != null) { - return thread.Start(); + thread.IncrementReferenceCount(); + + KernelResult result = thread.Start(); + + if (result == KernelResult.Success) + { + thread.IncrementReferenceCount(); + } + + thread.DecrementReferenceCount(); + + return result; } else { diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KEvent.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KEvent.cs index 5bdb9c1d..dd982291 100644 --- a/Ryujinx.HLE/HOS/Kernel/Threading/KEvent.cs +++ b/Ryujinx.HLE/HOS/Kernel/Threading/KEvent.cs @@ -8,7 +8,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading public KEvent(Horizon system) { ReadableEvent = new KReadableEvent(system, this); - WritableEvent = new KWritableEvent(this); + WritableEvent = new KWritableEvent(system, this); } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs index 60e15efa..c9686df3 100644 --- a/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs +++ b/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs @@ -210,9 +210,29 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading } } + return GetDummyThread(); + throw new InvalidOperationException("Current thread is not scheduled!"); } + private KThread _dummyThread; + + private KThread GetDummyThread() + { + if (_dummyThread != null) + { + return _dummyThread; + } + + KProcess dummyProcess = new KProcess(_system); + + KThread dummyThread = new KThread(_system); + + dummyThread.Initialize(0, 0, 0, 44, 0, dummyProcess, ThreadType.Dummy); + + return _dummyThread = dummyThread; + } + public KProcess GetCurrentProcess() { return GetCurrentThread().Owner; diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs index 450155ce..327b0418 100644 --- a/Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs +++ b/Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs @@ -32,7 +32,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading _system.CriticalSection.Leave(); - return 0; + return KernelResult.Success; } if (timeout == 0) diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs index 3ad64024..302e8f41 100644 --- a/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs +++ b/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs @@ -30,6 +30,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading private ulong _tlsAddress; + public ulong TlsAddress => _tlsAddress; + public ulong TlsDramAddress { get; private set; } + public long LastScheduledTime { get; set; } public LinkedListNode[] SiblingsPerCore { get; private set; } @@ -67,6 +70,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading public bool WaitingSync { get; set; } private bool _hasExited; + private bool _hasBeenInitialized; + private bool _hasBeenReleased; public bool WaitingInArbitration { get; set; } @@ -124,6 +129,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading return KernelResult.OutOfMemory; } + TlsDramAddress = owner.MemoryManager.GetDramAddressFromVa(_tlsAddress); + MemoryHelper.FillWithZeros(owner.CpuMemory, (long)_tlsAddress, KTlsPageInfo.TlsEntrySize); } @@ -133,6 +140,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading { Owner = owner; + owner.IncrementReferenceCount(); owner.IncrementThreadCount(); is64Bits = (owner.MmuFlags & 1) != 0; @@ -156,6 +164,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading ThreadUid = System.GetThreadUid(); + _hasBeenInitialized = true; + if (owner != null) { owner.AddThread(this); @@ -252,6 +262,15 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading public void Exit() { + //TODO: Debug event. + + if (Owner != null) + { + Owner.ResourceLimit?.Release(LimitableResource.Thread, 0, 1); + + _hasBeenReleased = true; + } + System.CriticalSection.Enter(); _forcePauseFlags &= ~ThreadSchedState.ForcePauseMask; @@ -259,6 +278,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading ExitImpl(); System.CriticalSection.Leave(); + + DecrementReferenceCount(); } private void ExitImpl() @@ -930,7 +951,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading return; } - //Remove from old queues. + //Remove thread from the old priority queues. for (int core = 0; core < KScheduler.CpuCoresCount; core++) { if (((oldAffinityMask >> core) & 1) != 0) @@ -946,7 +967,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading } } - //Insert on new queues. + //Add thread to the new priority queues. for (int core = 0; core < KScheduler.CpuCoresCount; core++) { if (((AffinityMask >> core) & 1) != 0) @@ -965,11 +986,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading _scheduler.ThreadReselectionRequested = true; } - public override bool IsSignaled() - { - return _hasExited; - } - public void SetEntryArguments(long argsPtr, int threadHandle) { Context.ThreadState.X0 = (ulong)argsPtr; @@ -994,13 +1010,36 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading private void ThreadFinishedHandler(object sender, EventArgs e) { System.Scheduler.ExitThread(this); - - Terminate(); - System.Scheduler.RemoveThread(this); } - public void Terminate() + public override bool IsSignaled() + { + return _hasExited; + } + + protected override void Destroy() + { + if (_hasBeenInitialized) + { + FreeResources(); + + bool released = Owner != null || _hasBeenReleased; + + if (Owner != null) + { + Owner.ResourceLimit?.Release(LimitableResource.Thread, 1, released ? 0 : 1); + + Owner.DecrementReferenceCount(); + } + else + { + System.ResourceLimit.Release(LimitableResource.Thread, 1, released ? 0 : 1); + } + } + } + + private void FreeResources() { Owner?.RemoveThread(this); @@ -1011,8 +1050,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading System.CriticalSection.Enter(); - //Wake up all threads that may be waiting for a mutex being held - //by this thread. + //Wake up all threads that may be waiting for a mutex being held by this thread. foreach (KThread thread in _mutexWaiters) { thread.MutexOwner = null; diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KWritableEvent.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KWritableEvent.cs index c9b2f40d..1db88995 100644 --- a/Ryujinx.HLE/HOS/Kernel/Threading/KWritableEvent.cs +++ b/Ryujinx.HLE/HOS/Kernel/Threading/KWritableEvent.cs @@ -2,11 +2,11 @@ using Ryujinx.HLE.HOS.Kernel.Common; namespace Ryujinx.HLE.HOS.Kernel.Threading { - class KWritableEvent + class KWritableEvent : KAutoObject { private KEvent _parent; - public KWritableEvent(KEvent parent) + public KWritableEvent(Horizon system, KEvent parent) : base(system) { _parent = parent; } diff --git a/Ryujinx.HLE/HOS/ProgramLoader.cs b/Ryujinx.HLE/HOS/ProgramLoader.cs index bb09db6e..568c56ef 100644 --- a/Ryujinx.HLE/HOS/ProgramLoader.cs +++ b/Ryujinx.HLE/HOS/ProgramLoader.cs @@ -63,11 +63,11 @@ namespace Ryujinx.HLE.HOS 0, 0); - MemoryRegion memRegion = kip.IsService + MemoryRegion memoryRegion = kip.IsService ? MemoryRegion.Service : MemoryRegion.Application; - KMemoryRegionManager region = system.MemoryRegions[(int)memRegion]; + KMemoryRegionManager region = system.MemoryRegions[(int)memoryRegion]; KernelResult result = region.AllocatePages((ulong)codePagesCount, false, out KPageList pageList); @@ -85,7 +85,7 @@ namespace Ryujinx.HLE.HOS kip.Capabilities, pageList, system.ResourceLimit, - memRegion); + memoryRegion); if (result != KernelResult.Success) { @@ -103,6 +103,8 @@ namespace Ryujinx.HLE.HOS return false; } + process.DefaultCpuCore = kip.DefaultProcessorId; + result = process.Start(kip.MainThreadPriority, (ulong)kip.MainThreadStackSize); if (result != KernelResult.Success) @@ -201,11 +203,20 @@ namespace Ryujinx.HLE.HOS KProcess process = new KProcess(system); + MemoryRegion memoryRegion = (MemoryRegion)((metaData.Acid.Flags >> 2) & 0xf); + + if (memoryRegion > MemoryRegion.NvServices) + { + Logger.PrintError(LogClass.Loader, $"Process initialization failed due to invalid ACID flags."); + + return false; + } + result = process.Initialize( creationInfo, metaData.Aci0.KernelAccessControl.Capabilities, resourceLimit, - MemoryRegion.Application); + memoryRegion); if (result != KernelResult.Success) { @@ -228,6 +239,8 @@ namespace Ryujinx.HLE.HOS } } + process.DefaultCpuCore = metaData.DefaultCpuId; + result = process.Start(metaData.MainThreadPriority, (ulong)metaData.MainThreadStackSize); if (result != KernelResult.Success) diff --git a/Ryujinx.HLE/HOS/ServiceCtx.cs b/Ryujinx.HLE/HOS/ServiceCtx.cs index 005d16f3..af42d417 100644 --- a/Ryujinx.HLE/HOS/ServiceCtx.cs +++ b/Ryujinx.HLE/HOS/ServiceCtx.cs @@ -8,24 +8,24 @@ namespace Ryujinx.HLE.HOS { class ServiceCtx { - public Switch Device { get; private set; } - public KProcess Process { get; private set; } - public MemoryManager Memory { get; private set; } - public KSession Session { get; private set; } - public IpcMessage Request { get; private set; } - public IpcMessage Response { get; private set; } - public BinaryReader RequestData { get; private set; } - public BinaryWriter ResponseData { get; private set; } + public Switch Device { get; } + public KProcess Process { get; } + public MemoryManager Memory { get; } + public KClientSession Session { get; } + public IpcMessage Request { get; } + public IpcMessage Response { get; } + public BinaryReader RequestData { get; } + public BinaryWriter ResponseData { get; } public ServiceCtx( - Switch device, - KProcess process, - MemoryManager memory, - KSession session, - IpcMessage request, - IpcMessage response, - BinaryReader requestData, - BinaryWriter responseData) + Switch device, + KProcess process, + MemoryManager memory, + KClientSession session, + IpcMessage request, + IpcMessage response, + BinaryReader requestData, + BinaryWriter responseData) { Device = device; Process = process; diff --git a/Ryujinx.HLE/HOS/Services/IpcService.cs b/Ryujinx.HLE/HOS/Services/IpcService.cs index 5c7fa18d..71683ce3 100644 --- a/Ryujinx.HLE/HOS/Services/IpcService.cs +++ b/Ryujinx.HLE/HOS/Services/IpcService.cs @@ -116,7 +116,7 @@ namespace Ryujinx.HLE.HOS.Services } else { - string dbgMessage = $"{context.Session.ServiceName} {service.GetType().Name}: {commandId}"; + string dbgMessage = $"{service.GetType().FullName}: {commandId}"; throw new ServiceNotImplementedException(context, dbgMessage); } @@ -132,9 +132,11 @@ namespace Ryujinx.HLE.HOS.Services } else { - KSession session = new KSession(obj, context.Session.ServiceName); + KSession session = new KSession(context.Device.System); - if (context.Process.HandleTable.GenerateHandle(session, out int handle) != KernelResult.Success) + session.ClientSession.Service = obj; + + if (context.Process.HandleTable.GenerateHandle(session.ClientSession, out int handle) != KernelResult.Success) { throw new InvalidOperationException("Out of handles!"); } @@ -151,7 +153,7 @@ namespace Ryujinx.HLE.HOS.Services { int handle = context.Request.HandleDesc.ToMove[index]; - KSession session = context.Process.HandleTable.GetObject(handle); + KClientSession session = context.Process.HandleTable.GetObject(handle); return session?.Service is T ? (T)session.Service : null; } diff --git a/Ryujinx.HLE/HOS/Services/Psm/IPsmSession.cs b/Ryujinx.HLE/HOS/Services/Psm/IPsmSession.cs index aeeeb052..3db3bd27 100644 --- a/Ryujinx.HLE/HOS/Services/Psm/IPsmSession.cs +++ b/Ryujinx.HLE/HOS/Services/Psm/IPsmSession.cs @@ -35,7 +35,7 @@ namespace Ryujinx.HLE.HOS.Services.Psm { if (_stateChangeEventHandle == -1) { - KernelResult resultCode = context.Process.HandleTable.GenerateHandle(_stateChangeEvent, out int stateChangeEventHandle); + KernelResult resultCode = context.Process.HandleTable.GenerateHandle(_stateChangeEvent.ReadableEvent, out int stateChangeEventHandle); if (resultCode != KernelResult.Success) { diff --git a/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs b/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs index df551a41..6940bfc8 100644 --- a/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs +++ b/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs @@ -1,8 +1,11 @@ +using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Ipc; using System; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.IO; namespace Ryujinx.HLE.HOS.Services.Sm { @@ -12,18 +15,30 @@ namespace Ryujinx.HLE.HOS.Services.Sm public override IReadOnlyDictionary Commands => _commands; + private ConcurrentDictionary _registeredServices; + private bool _isInitialized; public IUserInterface() { _commands = new Dictionary { - { 0, Initialize }, - { 1, GetService } + { 0, Initialize }, + { 1, GetService }, + { 2, RegisterService } }; + + _registeredServices = new ConcurrentDictionary(); } - private const int SmNotInitialized = 0x415; + public static void InitializePort(Horizon system) + { + KPort port = new KPort(system, 256, false, 0); + + port.ClientPort.SetName("sm:"); + + port.ClientPort.Service = new IUserInterface(); + } public long Initialize(ServiceCtx context) { @@ -34,12 +49,87 @@ namespace Ryujinx.HLE.HOS.Services.Sm public long GetService(ServiceCtx context) { - //Only for kernel version > 3.0.0. if (!_isInitialized) { - //return SmNotInitialized; + return ErrorCode.MakeError(ErrorModule.Sm, SmErr.NotInitialized); } + string name = ReadName(context); + + if (name == string.Empty) + { + return ErrorCode.MakeError(ErrorModule.Sm, SmErr.InvalidName); + } + + KSession session = new KSession(context.Device.System); + + if (_registeredServices.TryGetValue(name, out KPort port)) + { + KernelResult result = port.EnqueueIncomingSession(session.ServerSession); + + if (result != KernelResult.Success) + { + throw new InvalidOperationException($"Session enqueue on port returned error \"{result}\"."); + } + } + else + { + session.ClientSession.Service = ServiceFactory.MakeService(context.Device.System, name); + } + + if (context.Process.HandleTable.GenerateHandle(session.ClientSession, out int handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeMove(handle); + + return 0; + } + + public long RegisterService(ServiceCtx context) + { + if (!_isInitialized) + { + return ErrorCode.MakeError(ErrorModule.Sm, SmErr.NotInitialized); + } + + long namePosition = context.RequestData.BaseStream.Position; + + string name = ReadName(context); + + context.RequestData.BaseStream.Seek(namePosition + 8, SeekOrigin.Begin); + + bool isLight = (context.RequestData.ReadInt32() & 1) != 0; + + int maxSessions = context.RequestData.ReadInt32(); + + if (name == string.Empty) + { + return ErrorCode.MakeError(ErrorModule.Sm, SmErr.InvalidName); + } + + Logger.PrintInfo(LogClass.ServiceSm, $"Register \"{name}\"."); + + KPort port = new KPort(context.Device.System, maxSessions, isLight, 0); + + if (!_registeredServices.TryAdd(name, port)) + { + return ErrorCode.MakeError(ErrorModule.Sm, SmErr.AlreadyRegistered); + } + + if (context.Process.HandleTable.GenerateHandle(port.ServerPort, out int handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeMove(handle); + + return 0; + } + + private static string ReadName(ServiceCtx context) + { string name = string.Empty; for (int index = 0; index < 8 && @@ -54,21 +144,7 @@ namespace Ryujinx.HLE.HOS.Services.Sm } } - if (name == string.Empty) - { - return 0; - } - - KSession session = new KSession(ServiceFactory.MakeService(context.Device.System, name), name); - - if (context.Process.HandleTable.GenerateHandle(session, out int handle) != KernelResult.Success) - { - throw new InvalidOperationException("Out of handles!"); - } - - context.Response.HandleDesc = IpcHandleDesc.MakeMove(handle); - - return 0; + return name; } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sm/SmErr.cs b/Ryujinx.HLE/HOS/Services/Sm/SmErr.cs new file mode 100644 index 00000000..5b5a66dc --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sm/SmErr.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Sm +{ + static class SmErr + { + public const int NotInitialized = 2; + public const int AlreadyRegistered = 4; + public const int InvalidName = 6; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/Compression/BackwardsLz.cs b/Ryujinx.HLE/Loaders/Compression/BackwardsLz.cs index 166ae60a..0a76dc95 100644 --- a/Ryujinx.HLE/Loaders/Compression/BackwardsLz.cs +++ b/Ryujinx.HLE/Loaders/Compression/BackwardsLz.cs @@ -1,5 +1,4 @@ using System; -using System.IO; namespace Ryujinx.HLE.Loaders.Compression { @@ -7,22 +6,26 @@ namespace Ryujinx.HLE.Loaders.Compression { private class BackwardsReader { - private Stream _baseStream; + private byte[] _data; - public BackwardsReader(Stream baseStream) + private int _position; + + public int Position => _position; + + public BackwardsReader(byte[] data, int end) { - _baseStream = baseStream; + _data = data; + _position = end; + } + + public void SeekCurrent(int offset) + { + _position += offset; } public byte ReadByte() { - _baseStream.Seek(-1, SeekOrigin.Current); - - byte value = (byte)_baseStream.ReadByte(); - - _baseStream.Seek(-1, SeekOrigin.Current); - - return value; + return _data[--_position]; } public short ReadInt16() @@ -39,30 +42,24 @@ namespace Ryujinx.HLE.Loaders.Compression } } - public static byte[] Decompress(Stream input, int decompressedLength) + public static void DecompressInPlace(byte[] buffer, int headerEnd) { - long end = input.Position; - - BackwardsReader reader = new BackwardsReader(input); + BackwardsReader reader = new BackwardsReader(buffer, headerEnd); int additionalDecLength = reader.ReadInt32(); int startOffset = reader.ReadInt32(); int compressedLength = reader.ReadInt32(); - input.Seek(12 - startOffset, SeekOrigin.Current); + reader.SeekCurrent(12 - startOffset); - byte[] dec = new byte[decompressedLength]; + int decBase = headerEnd - compressedLength; - int decompressedLengthUnpadded = compressedLength + additionalDecLength; - - int decompressionStart = decompressedLength - decompressedLengthUnpadded; - - int decPos = dec.Length; + int decPos = compressedLength + additionalDecLength; byte mask = 0; byte header = 0; - while (decPos > decompressionStart) + while (decPos > 0) { if ((mask >>= 1) == 0) { @@ -72,7 +69,7 @@ namespace Ryujinx.HLE.Loaders.Compression if ((header & mask) == 0) { - dec[--decPos] = reader.ReadByte(); + buffer[decBase + --decPos] = reader.ReadByte(); } else { @@ -81,25 +78,30 @@ namespace Ryujinx.HLE.Loaders.Compression int length = (pair >> 12) + 3; int position = (pair & 0xfff) + 3; + if (length > decPos) + { + length = decPos; + } + decPos -= length; + int dstPos = decBase + decPos; + if (length <= position) { - int srcPos = decPos + position; + int srcPos = dstPos + position; - Buffer.BlockCopy(dec, srcPos, dec, decPos, length); + Buffer.BlockCopy(buffer, srcPos, buffer, dstPos, length); } else { for (int offset = 0; offset < length; offset++) { - dec[decPos + offset] = dec[decPos + position + offset]; + buffer[dstPos + offset] = buffer[dstPos + position + offset]; } } } } - - return dec; } } } \ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/Executables/KernelInitialProcess.cs b/Ryujinx.HLE/Loaders/Executables/KernelInitialProcess.cs index 8b09bfcd..af57cf2d 100644 --- a/Ryujinx.HLE/Loaders/Executables/KernelInitialProcess.cs +++ b/Ryujinx.HLE/Loaders/Executables/KernelInitialProcess.cs @@ -98,7 +98,7 @@ namespace Ryujinx.HLE.Loaders.Executables MainThreadStackSize = segments[1].Attribute; - Capabilities = new int[8]; + Capabilities = new int[32]; for (int index = 0; index < Capabilities.Length; index++) { @@ -114,13 +114,11 @@ namespace Ryujinx.HLE.Loaders.Executables private byte[] ReadSegment(SegmentHeader header, Stream input) { - long end = input.Position + header.CompressedSize; + byte[] data = new byte[header.DecompressedSize]; - input.Seek(end, SeekOrigin.Begin); + input.Read(data, 0, header.CompressedSize); - byte[] data = BackwardsLz.Decompress(input, header.DecompressedSize); - - input.Seek(end, SeekOrigin.Begin); + BackwardsLz.DecompressInPlace(data, header.CompressedSize); return data; } diff --git a/Ryujinx.HLE/Loaders/Npdm/ServiceAccessControl.cs b/Ryujinx.HLE/Loaders/Npdm/ServiceAccessControl.cs index 368dbae7..def780a2 100644 --- a/Ryujinx.HLE/Loaders/Npdm/ServiceAccessControl.cs +++ b/Ryujinx.HLE/Loaders/Npdm/ServiceAccessControl.cs @@ -31,7 +31,7 @@ namespace Ryujinx.HLE.Loaders.Npdm int length = (controlByte & 0x07) + 1; bool registerAllowed = (controlByte & 0x80) != 0; - services.Add(Encoding.ASCII.GetString(reader.ReadBytes(length), 0, length), registerAllowed); + services[Encoding.ASCII.GetString(reader.ReadBytes(length))] = registerAllowed; byteReaded += length + 1; }