From a2237def5a48560ca698a9421ebad0bb265f4aa1 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Wed, 26 Dec 2018 16:58:05 -0300 Subject: [PATCH] Implement some IPC related kernel SVCs properly --- Ryujinx.HLE/DeviceMemory.cs | 34 + Ryujinx.HLE/HOS/Horizon.cs | 12 +- Ryujinx.HLE/HOS/Kernel/Common/KAutoObject.cs | 21 + .../HOS/Kernel/Common/KResourceLimit.cs | 14 +- Ryujinx.HLE/HOS/Kernel/Common/KernelResult.cs | 57 +- .../HOS/Kernel/Common/KernelTransfer.cs | 20 + .../HOS/Kernel/Ipc/KBufferDescriptor.cs | 20 + .../HOS/Kernel/Ipc/KBufferDescriptorTable.cs | 216 +++ Ryujinx.HLE/HOS/Kernel/Ipc/KClientPort.cs | 56 +- Ryujinx.HLE/HOS/Kernel/Ipc/KClientSession.cs | 52 + Ryujinx.HLE/HOS/Kernel/Ipc/KPort.cs | 34 +- Ryujinx.HLE/HOS/Kernel/Ipc/KServerPort.cs | 66 +- Ryujinx.HLE/HOS/Kernel/Ipc/KServerSession.cs | 1118 ++++++++++++++ Ryujinx.HLE/HOS/Kernel/Ipc/KSession.cs | 27 +- Ryujinx.HLE/HOS/Kernel/Ipc/KSessionRequest.cs | 28 + Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlock.cs | 106 +- Ryujinx.HLE/HOS/Kernel/Memory/KMemoryInfo.cs | 31 +- .../HOS/Kernel/Memory/KMemoryManager.cs | 1311 +++++++++++++---- .../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 | 18 +- .../HOS/Kernel/SupervisorCall/SvcMemory.cs | 2 +- .../HOS/Kernel/SupervisorCall/SvcSystem.cs | 209 ++- .../HOS/Kernel/SupervisorCall/SvcTable.cs | 2 + .../HOS/Kernel/SupervisorCall/SvcThread.cs | 32 +- Ryujinx.HLE/HOS/Kernel/Threading/KEvent.cs | 2 +- .../HOS/Kernel/Threading/KSynchronization.cs | 2 +- Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs | 64 +- .../HOS/Kernel/Threading/KWritableEvent.cs | 4 +- Ryujinx.HLE/HOS/Services/IpcService.cs | 2 +- Ryujinx.HLE/HOS/Services/Psm/IPsmSession.cs | 2 +- Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs | 2 +- .../Loaders/Npdm/ServiceAccessControl.cs | 2 +- 36 files changed, 3425 insertions(+), 538 deletions(-) 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/KServerSession.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Ipc/KSessionRequest.cs diff --git a/Ryujinx.HLE/DeviceMemory.cs b/Ryujinx.HLE/DeviceMemory.cs index 3db89c7275..e21848e7a1 100644 --- a/Ryujinx.HLE/DeviceMemory.cs +++ b/Ryujinx.HLE/DeviceMemory.cs @@ -113,6 +113,40 @@ namespace Ryujinx.HLE } } + public void Set(ulong address, byte value, ulong size) + { + 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) + { + ulong size8 = size & ~7UL; + + for (ulong offs = 0; offs < size8; offs += 8) + { + WriteUInt64((long)(dst + offs), ReadUInt64((long)(src + offs))); + + System.Console.WriteLine((dst + offs).ToString("X16") + " <- " + (src + offs).ToString("X16") + " " + ReadUInt64((long)(src + offs)).ToString("X16")); + } + + 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 5c2ca9b3af..5274605a19 100644 --- a/Ryujinx.HLE/HOS/Horizon.cs +++ b/Ryujinx.HLE/HOS/Horizon.cs @@ -156,8 +156,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); @@ -258,6 +258,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/Kernel/Common/KAutoObject.cs b/Ryujinx.HLE/HOS/Kernel/Common/KAutoObject.cs index ddb0c71f7b..3b30dd808e 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 01bba65f79..a7955d7ad6 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/KernelResult.cs b/Ryujinx.HLE/HOS/Kernel/Common/KernelResult.cs index cea24693f6..357b01ea5d 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 a29b1722cc..2b7591406f 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/KBufferDescriptor.cs b/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptor.cs new file mode 100644 index 0000000000..e28244d4ac --- /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 0000000000..ac805bee17 --- /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 ddfe20960d..2863054447 100644 --- a/Ryujinx.HLE/HOS/Kernel/Ipc/KClientPort.cs +++ b/Ryujinx.HLE/HOS/Kernel/Ipc/KClientPort.cs @@ -1,4 +1,5 @@ using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Process; namespace Ryujinx.HLE.HOS.Kernel.Ipc { @@ -10,7 +11,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc private KPort _parent; - public KClientPort(Horizon system) : base(system) { } + private object _countIncLock; + + public KClientPort(Horizon system) : base(system) + { + _countIncLock = new object(); + } public void Initialize(KPort parent, int maxSessions) { @@ -18,6 +24,54 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc _parent = parent; } + 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); + + KernelResult result = _parent.EnqueueIncomingSession(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) { KAutoObject foundObj = FindNamedObject(system, name); diff --git a/Ryujinx.HLE/HOS/Kernel/Ipc/KClientSession.cs b/Ryujinx.HLE/HOS/Kernel/Ipc/KClientSession.cs new file mode 100644 index 0000000000..8e8f874ceb --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Ipc/KClientSession.cs @@ -0,0 +1,52 @@ +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Kernel.Threading; + +namespace Ryujinx.HLE.HOS.Kernel.Ipc +{ + class KClientSession : KSynchronizationObject + { + public KProcess CreatorProcess { get; } + + private KSession _parent; + + public int ResourceStatus { get; private set; } + + public KClientSession(Horizon system, KSession parent) : base(system) + { + _parent = parent; + + ResourceStatus = 1; + } + + public KernelResult SendSyncRequest(ulong customCmdBuffAddr = 0, ulong customCmdBuffSize = 0) + { + KThread currentThread = System.Scheduler.GetCurrentThread(); + + KSessionRequest request = new KSessionRequest(currentThread, customCmdBuffAddr, customCmdBuffSize); + + currentThread.IncrementReferenceCount(); + + 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.DecrementReferenceCount(); + } + } +} \ 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 16e9a1119a..1fc0c98e3b 100644 --- a/Ryujinx.HLE/HOS/Kernel/Ipc/KPort.cs +++ b/Ryujinx.HLE/HOS/Kernel/Ipc/KPort.cs @@ -4,11 +4,13 @@ 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; + private int _resourceStatus; + + public bool IsLight { get; private set; } public KPort(Horizon system) : base(system) { @@ -21,8 +23,32 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc ServerPort.Initialize(this); ClientPort.Initialize(this, maxSessions); - _isLight = isLight; + IsLight = isLight; _nameAddress = nameAddress; + + _resourceStatus = 1; + } + + public KernelResult EnqueueIncomingSession(KServerSession session) + { + KernelResult result; + + System.CriticalSection.Enter(); + + if (_resourceStatus == 1) + { + ServerPort.EnqueueIncomingSession(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 d4d3bcd29a..2ec62871b1 100644 --- a/Ryujinx.HLE/HOS/Kernel/Ipc/KServerPort.cs +++ b/Ryujinx.HLE/HOS/Kernel/Ipc/KServerPort.cs @@ -1,16 +1,80 @@ 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 KServerPort(Horizon system) : base(system) + { + _incomingConnections = new LinkedList(); + _lightIncomingConnections = new LinkedList(); + } public void Initialize(KPort parent) { _parent = parent; } + + public void EnqueueIncomingSession(KServerSession session) + { + System.CriticalSection.Enter(); + + _incomingConnections.AddLast(session); + + if (_incomingConnections.Count == 1) + { + Signal(); + } + + System.CriticalSection.Leave(); + } + + public KServerSession AcceptIncomingConnection() + { + return AcceptIncomingConnection(_incomingConnections); + } + + public KServerSession AcceptLightIncomingConnection() + { + return AcceptIncomingConnection(_lightIncomingConnections); + } + + private KServerSession AcceptIncomingConnection(LinkedList list) + { + KServerSession session = null; + + 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 0000000000..4695498470 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Ipc/KServerSession.cs @@ -0,0 +1,1118 @@ +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; + } + } + } + + private struct MessageHeader + { + 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) + { + 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.ResourceStatus != 1) + { + return KernelResult.PortRemoteClosed; + } + + if (request.AsyncEvent == null) + { + if (request.SenderThread.ShallBeTerminated || + request.SenderThread.SchedFlags == ThreadSchedState.TerminationPending) + { + return KernelResult.ThreadTerminating; + } + + request.SenderThread.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.ResourceStatus != 1) + { + System.CriticalSection.Leave(); + + return KernelResult.PortRemoteClosed; + } + + if (_activeRequest != null || _requests.Count == 0) + { + System.CriticalSection.Leave(); + + return KernelResult.NotFound; + } + + KSessionRequest request = _requests.First.Value; + + _requests.RemoveFirst(); + + if (request.SenderThread == null) + { + System.CriticalSection.Leave(); + + return KernelResult.PortRemoteClosed; + } + + KThread clientThread = request.SenderThread; + KProcess clientProcess = clientThread.Owner; + + System.CriticalSection.Leave(); + + _activeRequest = request; + + Message clientMsg = new Message( + clientThread, + request.CustomCmdBuffAddr, + request.CustomCmdBuffSize); + + Message serverMsg = new Message( + serverThread, + customCmdBuffAddr, + customCmdBuffSize); + + 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); + + MessageHeader header = new MessageHeader(word0, word1, word2); + + KernelResult serverResult = KernelResult.NotFound; + KernelResult clientResult = KernelResult.Success; + + void CleanUpForError() + { + request.BufferDescriptorTable.UnmapServerBuffers(serverProcess.MemoryManager); + + CloseAllHandles(serverMsg, header, serverProcess); + + System.CriticalSection.Enter(); + + _activeRequest = null; + + if (_requests.Count != 0) + { + Signal(); + } + + System.CriticalSection.Leave(); + + if (request.AsyncEvent != null) + { + System.Device.Memory.WriteInt64((long)clientMsg.DramAddress + 0, 0); + System.Device.Memory.WriteInt32((long)clientMsg.DramAddress + 8, (int)clientResult); + + clientProcess.MemoryManager.UnborrowIpcBuffer(clientMsg.Address, clientMsg.Size); + + request.AsyncEvent.Signal(); + } + else + { + System.CriticalSection.Enter(); + + if ((clientThread.SchedFlags & ThreadSchedState.LowMask) == ThreadSchedState.Paused) + { + clientThread.SignaledObj = null; + clientThread.ObjSyncResult = clientResult; + + clientThread.Reschedule(ThreadSchedState.Running); + } + + System.CriticalSection.Leave(); + } + } + + if (header.ReceiveListType < 2 && header.ReceiveListOffset > clientMsg.Size) + { + CleanUpForError(); + + return KernelResult.InvalidCombination; + } + else if (header.ReceiveListType == 2 && header.ReceiveListOffset + 8 > clientMsg.Size) + { + CleanUpForError(); + + return KernelResult.InvalidCombination; + } + else if (header.ReceiveListType * 8 - 0x10 + header.ReceiveListOffset > clientMsg.Size) + { + CleanUpForError(); + + return KernelResult.InvalidCombination; + } + + if (header.ReceiveListOffsetInWords < header.MessageSizeInWords) + { + CleanUpForError(); + + return KernelResult.InvalidCombination; + } + + if (header.MessageSizeInWords * 4 > clientMsg.Size) + { + CleanUpForError(); + + return KernelResult.CmdBufferTooSmall; + } + + int recvListSize = 0; + + if (header.ReceiveListType >= 3) + { + recvListSize = (int)header.ReceiveListType - 2; + } + else if (header.ReceiveListType == 2) + { + recvListSize = 1; + } + + ulong[] receiveList = new ulong[recvListSize]; + + long recvListAddress = (long)serverMsg.Address + header.ReceiveListOffset; + + for (int index = 0; index < recvListSize; index++) + { + receiveList[index] = serverProcess.CpuMemory.ReadUInt64(recvListAddress + index * 8); + } + + serverProcess.CpuMemory.WriteUInt32((long)serverMsg.Address + 0, word0); + serverProcess.CpuMemory.WriteUInt32((long)serverMsg.Address + 4, word1); + + uint offset; + + //Copy handles. + if (header.HasHandles) + { + if (header.MoveHandlesCount != 0) + { + CleanUpForError(); + + return KernelResult.InvalidCombination; + } + + serverProcess.CpuMemory.WriteUInt32((long)serverMsg.Address + 8, word2); + + offset = 3; + + if (header.HasPid) + { + serverProcess.CpuMemory.WriteInt64((long)serverMsg.Address + offset * 4, clientProcess.Pid); + + offset += 2; + } + + for (int index = 0; index < header.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, handle, out newHandle); + } + + serverProcess.CpuMemory.WriteInt32((long)serverMsg.Address + offset * 4, newHandle); + + offset++; + } + + for (int index = 0; index < header.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(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. + for (int index = 0; index < header.PointerBuffersCount; index++) + { + ulong pointerDesc = System.Device.Memory.ReadUInt64((long)clientMsg.DramAddress + offset * 4); + + PointerBufferDesc descriptor = new PointerBufferDesc(pointerDesc); + + if (descriptor.BufferSize != 0) + { + clientResult = GetReceiveListAddress(serverMsg, header, descriptor, receiveList, 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 = + header.SendBuffersCount + + header.ReceiveBuffersCount + + header.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 < header.SendBuffersCount; + bool isExchangeDesc = index >= header.SendBuffersCount + header.ReceiveBuffersCount; + + bool notReceiveDesc = isSendDesc || isExchangeDesc; + bool isReceiveDesc = !notReceiveDesc; + + MemoryPermission permission = index >= header.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 (header.RawDataSizeInWords != 0) + { + ulong copySrc = clientMsg.Address + offset * 4; + ulong copyDst = serverMsg.Address + offset * 4; + + ulong copySize = header.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.SenderThread; + KProcess clientProcess = clientThread.Owner; + + Message clientMsg = new Message( + clientThread, + request.CustomCmdBuffAddr, + request.CustomCmdBuffSize); + + Message serverMsg = new Message( + serverThread, + customCmdBuffAddr, + customCmdBuffSize); + + uint word0 = serverProcess.CpuMemory.ReadUInt32((long)serverMsg.Address + 0); + uint word1 = serverProcess.CpuMemory.ReadUInt32((long)serverMsg.Address + 4); + uint word2 = serverProcess.CpuMemory.ReadUInt32((long)serverMsg.Address + 8); + + MessageHeader header = new MessageHeader(word0, word1, word2); + + KernelResult clientResult = KernelResult.Success; + KernelResult serverResult = KernelResult.Success; + + void SendResultToClient() + { + if (request.AsyncEvent != null) + { + if (clientResult != KernelResult.Success) + { + System.Device.Memory.WriteInt64((long)clientMsg.DramAddress + 0, 0); + System.Device.Memory.WriteInt32((long)clientMsg.DramAddress + 8, (int)clientResult); + } + + clientProcess.MemoryManager.UnborrowIpcBuffer(clientMsg.Address, clientMsg.Size); + + request.AsyncEvent.Signal(); + } + else + { + System.CriticalSection.Enter(); + + if ((clientThread.SchedFlags & ThreadSchedState.LowMask) == ThreadSchedState.Paused) + { + clientThread.SignaledObj = null; + clientThread.ObjSyncResult = clientResult; + + clientThread.Reschedule(ThreadSchedState.Running); + } + + System.CriticalSection.Leave(); + } + } + + void CleanUpForError() + { + CloseAllHandles(clientMsg, header, serverProcess); + + if (request.BufferDescriptorTable.UnmapServerBuffers(serverProcess.MemoryManager) == KernelResult.Success) + { + request.BufferDescriptorTable.RestoreClientBuffers(serverProcess.MemoryManager); + } + + SendResultToClient(); + } + + if (header.ReceiveListType < 2 && header.ReceiveListOffset > clientMsg.Size) + { + CleanUpForError(); + + return KernelResult.InvalidCombination; + } + else if (header.ReceiveListType == 2 && header.ReceiveListOffset + 8 > clientMsg.Size) + { + CleanUpForError(); + + return KernelResult.InvalidCombination; + } + else if (header.ReceiveListType * 8 - 0x10 + header.ReceiveListOffset > clientMsg.Size) + { + CleanUpForError(); + + return KernelResult.InvalidCombination; + } + + if (header.ReceiveListOffsetInWords < header.MessageSizeInWords) + { + CleanUpForError(); + + return KernelResult.InvalidCombination; + } + + if (header.MessageSizeInWords * 4 > clientMsg.Size) + { + CleanUpForError(); + + return KernelResult.CmdBufferTooSmall; + } + + //Close move handles. + uint pidSizeInWords = header.HasPid ? 2u : 0u; + + ulong moveHandlesAddr = serverMsg.Address + (3u + pidSizeInWords + header.CopyHandlesCount) * 4; + + for (int index = 0; index < header.MoveHandlesCount; index++) + { + int handle = serverProcess.CpuMemory.ReadInt32((long)moveHandlesAddr + index * 4); + + serverProcess.HandleTable.CloseHandle(handle); + } + + //Read receive list. + ulong[] receiveList = null; + + if (header.ReceiveListType >= 2) + { + uint count = header.ReceiveListType == 2 ? 1 : header.ReceiveListType - 2; + + receiveList = new ulong[count]; + + ulong receiveListAddress = clientMsg.DramAddress + header.ReceiveListOffset; + + for (uint index = 0; index < count; index++) + { + ulong dword = System.Device.Memory.ReadUInt64((long)receiveListAddress + index * 8); + + receiveList[index] = dword; + } + } + + //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, word0); + System.Device.Memory.WriteUInt32((long)clientMsg.DramAddress + 4, word1); + + //Copy handles. + uint offset; + + if (header.HasHandles) + { + offset = 3; + + System.Device.Memory.WriteUInt32((long)clientMsg.DramAddress + 8, word2); + + if (header.HasPid) + { + System.Device.Memory.WriteInt64((long)clientMsg.DramAddress + offset * 4, serverProcess.Pid); + + offset += 2; + } + + for (int index = 0; index < header.CopyHandlesCount; index++) + { + int newHandle = 0; + + int handle = serverProcess.CpuMemory.ReadInt32((long)serverMsg.Address + offset * 4); + + if (handle != 0) + { + GetCopyObjectHandle(clientThread, handle, out newHandle); + } + + serverProcess.CpuMemory.WriteInt32((long)serverMsg.Address + offset * 4, newHandle); + + offset++; + } + + for (int index = 0; index < header.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, handle, out newHandle); + } + else + { + serverProcess.HandleTable.CloseHandle(handle); + } + } + + serverProcess.CpuMemory.WriteInt32((long)serverMsg.Address + offset * 4, newHandle); + + offset++; + } + } + else + { + offset = 2; + } + + //Copy pointer/receive list buffers. + for (int index = 0; index < header.PointerBuffersCount; index++) + { + ulong pointerDesc = serverProcess.CpuMemory.ReadUInt64((long)serverMsg.Address + offset * 4); + + PointerBufferDesc descriptor = new PointerBufferDesc(pointerDesc); + + if (descriptor.BufferSize != 0) + { + clientResult = GetReceiveListAddress(serverMsg, header, descriptor, receiveList, 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 = + header.SendBuffersCount + + header.ReceiveBuffersCount + + header.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 (header.RawDataSizeInWords != 0) + { + ulong copyDst = clientMsg.Address + offset * 4; + ulong copySrc = serverMsg.Address + offset * 4; + + ulong copySize = header.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; + } + + //Wake client thread. + SendResultToClient(); + + return serverResult; + } + + private KernelResult GetCopyObjectHandle(KThread thread, int srcHandle, out int dstHandle) + { + dstHandle = 0; + + KProcess process = thread.Owner; + + KAutoObject obj; + + if (srcHandle == KHandleTable.SelfProcessHandle) + { + obj = process; + } + else if (srcHandle == KHandleTable.SelfThreadHandle) + { + obj = thread; + } + else + { + obj = process.HandleTable.GetObject(srcHandle); + } + + if (obj != null) + { + return process.HandleTable.GenerateHandle(obj, out dstHandle); + } + else + { + return KernelResult.InvalidHandle; + } + } + + private KernelResult GetMoveObjectHandle(KProcess srcProcess, int srcHandle, out int dstHandle) + { + dstHandle = 0; + + KAutoObject obj = srcProcess.HandleTable.GetObject(srcHandle); + + if (obj != null) + { + KernelResult result = srcProcess.HandleTable.GenerateHandle(obj, out dstHandle); + + srcProcess.HandleTable.CloseHandle(srcHandle); + + return result; + } + else + { + return KernelResult.InvalidHandle; + } + } + + private KernelResult GetReceiveListAddress( + Message message, + MessageHeader header, + PointerBufferDesc descriptor, + ulong[] receiveList, + out ulong address) + { + ulong recvListBufferAddress = address = 0; + + if (header.ReceiveListType == 0) + { + return KernelResult.OutOfResource; + } + else if (header.ReceiveListType == 1 || header.ReceiveListType == 2) + { + ulong recvListBaseAddr; + ulong recvListEndAddr; + + if (header.ReceiveListType == 1) + { + recvListBaseAddr = message.Address + header.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 + descriptor.ReceiveIndex, 0x10); + + 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 transferSize = (uint)(packed >> 48); + + if (recvListBufferAddress == 0 || transferSize == 0 || transferSize < 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.ResourceStatus != 1) + { + return true; + } + + return _requests.Count != 0 && _activeRequest == null; + } + + protected override void Destroy() + { + _parent.DecrementReferenceCount(); + } + } +} \ 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 f2b3049339..1b9bb337d5 100644 --- a/Ryujinx.HLE/HOS/Kernel/Ipc/KSession.cs +++ b/Ryujinx.HLE/HOS/Kernel/Ipc/KSession.cs @@ -1,15 +1,28 @@ +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.HOS.Services; using System; namespace Ryujinx.HLE.HOS.Kernel.Ipc { - class KSession : IDisposable + class KSession : KAutoObject, IDisposable { + public KServerSession ServerSession { get; } + public KClientSession ClientSession { get; } + + private bool _hasBeenInitialized; + public IpcService Service { get; private set; } public string ServiceName { get; private set; } - public KSession(IpcService service, string serviceName) + public KSession(Horizon system) : base(system) + { + ServerSession = new KServerSession(system, this); + ClientSession = new KClientSession(system, this); + } + + public KSession(Horizon system, IpcService service, string serviceName) : base(system) { Service = service; ServiceName = serviceName; @@ -27,5 +40,15 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc disposableService.Dispose(); } } + + protected override void Destroy() + { + if (_hasBeenInitialized) + { + KProcess creatorProcess = ClientSession.CreatorProcess; + + creatorProcess.ResourceLimit?.Release(LimitableResource.Session, 1); + } + } } } \ 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 0000000000..05be2ecb8b --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Ipc/KSessionRequest.cs @@ -0,0 +1,28 @@ +using Ryujinx.HLE.HOS.Kernel.Threading; + +namespace Ryujinx.HLE.HOS.Kernel.Ipc +{ + class KSessionRequest + { + public KBufferDescriptorTable BufferDescriptorTable { get; } + + public KThread SenderThread { get; } + + public KWritableEvent AsyncEvent { get; } + + public ulong CustomCmdBuffAddr { get; } + public ulong CustomCmdBuffSize { get; } + + public KSessionRequest( + KThread senderThread, + ulong customCmdBuffAddr, + ulong customCmdBuffSize) + { + SenderThread = senderThread; + 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 89a194980a..b7c2b309d3 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 226ce77c52..21e9e49407 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 fb5dec0433..a75a6cab81 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,774 @@ 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, size, 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; + } + else + { + unusedSizeAfter = PageSize; + } + + _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) + { + if (MapPages(va, pageList, permission) != KernelResult.Success) + { + throw new InvalidOperationException("Unexpected failure while trying to map pages."); + } + } + else + { + 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 +2283,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 +2314,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 +2336,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 +2362,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 +2402,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 +2434,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 +2471,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 +2488,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 +2588,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 +3110,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 777e9aa941..92cef5598c 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 f2a05bda0b..6b92ed30a7 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 02367e896f..a0929eca00 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 87137d0fc1..b5ca9b5e28 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 413edf94b9..88c2e69032 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 0d77a4959a..919a877dd8 100644 --- a/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs +++ b/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs @@ -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/SupervisorCall/SvcMemory.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcMemory.cs index e6590522e7..0717d30586 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); } diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcSystem.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcSystem.cs index b05633567f..431376a06b 100644 --- a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcSystem.cs +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcSystem.cs @@ -15,6 +15,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall { partial class SvcHandler { + private const bool UseLegacyIpc = true; + public void ExitProcess64() { ExitProcess(); @@ -82,7 +84,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); @@ -146,6 +148,11 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall public KernelResult ConnectToNamedPort64(ulong namePtr, out int handle) { + if (!UseLegacyIpc) + { + return ConnectToNamedPort_(namePtr, out handle); + } + return ConnectToNamedPort(namePtr, out handle); } @@ -155,13 +162,62 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall //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); + KSession session = new KSession(_system, ServiceFactory.MakeService(_system, name), name); return _process.HandleTable.GenerateHandle(session, 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 session); + + if (result != KernelResult.Success) + { + currentProcess.HandleTable.CancelHandleReservation(handle); + + return result; + } + + currentProcess.HandleTable.SetReservedHandleObj(handle, session); + + return result; + } + public KernelResult SendSyncRequest64(int handle) { + if (!UseLegacyIpc) + { + return SendSyncRequest_(handle); + } + return SendSyncRequest((ulong)_system.Scheduler.GetCurrentThread().Context.ThreadState.Tpidr, 0x100, handle); } @@ -226,6 +282,20 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall 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 GetProcessId64(int handle, out long pid) { return GetProcessId(handle, out pid); @@ -522,6 +592,139 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.Success; } + 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; + } + + KServerSession session = serverPort.IsLight + ? serverPort.AcceptLightIncomingConnection() + : serverPort.AcceptIncomingConnection(); + + if (session != null) + { + currentProcess.HandleTable.SetReservedHandleObj(handle, session); + + 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 CreateEvent64(out int wEventHandle, out int rEventHandle) { return CreateEvent(out wEventHandle, out rEventHandle); @@ -749,7 +952,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall port.Initialize(maxSessions, false, 0); - result = port.SetName(name); + result = port.ClientPort.SetName(name); if (result != KernelResult.Success) { diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcTable.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcTable.cs index a6111777da..b04f476022 100644 --- a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcTable.cs +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcTable.cs @@ -63,6 +63,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall { 0x33, nameof(SvcHandler.GetThreadContext364) }, { 0x34, nameof(SvcHandler.WaitForAddress64) }, { 0x35, nameof(SvcHandler.SignalToAddress64) }, + { 0x41, nameof(SvcHandler.AcceptSession64) }, + { 0x43, nameof(SvcHandler.ReplyAndReceive64) }, { 0x45, nameof(SvcHandler.CreateEvent64) }, { 0x65, nameof(SvcHandler.GetProcessList64) }, { 0x6f, nameof(SvcHandler.GetSystemInfo64) }, diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcThread.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcThread.cs index 1e1927fec1..64268ff23d 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 5bdb9c1d6e..dd9822918c 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/KSynchronization.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs index 450155ce0e..327b04181e 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 3ad6402457..302e8f4150 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 c9b2f40db2..1db88995d8 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/Services/IpcService.cs b/Ryujinx.HLE/HOS/Services/IpcService.cs index 8d2036ea05..ac503e4f89 100644 --- a/Ryujinx.HLE/HOS/Services/IpcService.cs +++ b/Ryujinx.HLE/HOS/Services/IpcService.cs @@ -131,7 +131,7 @@ namespace Ryujinx.HLE.HOS.Services } else { - KSession session = new KSession(obj, context.Session.ServiceName); + KSession session = new KSession(context.Device.System, obj, context.Session.ServiceName); if (context.Process.HandleTable.GenerateHandle(session, out int handle) != KernelResult.Success) { diff --git a/Ryujinx.HLE/HOS/Services/Psm/IPsmSession.cs b/Ryujinx.HLE/HOS/Services/Psm/IPsmSession.cs index 0c304b4177..3b042d5511 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 df551a41b9..3bcd608876 100644 --- a/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs +++ b/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs @@ -59,7 +59,7 @@ namespace Ryujinx.HLE.HOS.Services.Sm return 0; } - KSession session = new KSession(ServiceFactory.MakeService(context.Device.System, name), name); + KSession session = new KSession(context.Device.System, ServiceFactory.MakeService(context.Device.System, name), name); if (context.Process.HandleTable.GenerateHandle(session, out int handle) != KernelResult.Success) { diff --git a/Ryujinx.HLE/Loaders/Npdm/ServiceAccessControl.cs b/Ryujinx.HLE/Loaders/Npdm/ServiceAccessControl.cs index 368dbae757..def780a256 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; }