Improve kernel IPC implementation (#550)

* Implement some IPC related kernel SVCs properly

* Fix BLZ decompression when the segment also has a uncompressed chunck

* Set default cpu core on process start from ProgramLoader, remove debug message

* Load process capabilities properly on KIPs

* Fix a copy/paste error in UnmapPhysicalMemory64

* Implement smarter switching between old and new IPC system to support the old HLE services implementation without the manual switch

* Implement RegisterService on sm and AcceptSession (partial)

* Misc fixes and improvements on new IPC methods

* Move IPC related SVCs into a separate file, and logging on RegisterService (sm)

* Some small fixes related to receive list buffers and error cases

* Load NSOs using the correct pool partition

* Fix corner case on GetMaskFromMinMax where range is 64, doesn't happen in pratice however

* Fix send static buffer copy

* Session release, implement closing requests on client disconnect

* Implement ConnectToPort SVC

* KLightSession init
This commit is contained in:
gdkchan 2019-01-18 20:26:39 -02:00 committed by GitHub
parent 3731d0ce84
commit 22bacc6188
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
51 changed files with 4310 additions and 840 deletions

View file

@ -0,0 +1,10 @@
namespace Ryujinx.HLE.HOS.Kernel.Ipc
{
enum ChannelState
{
NotInitialized,
Open,
ClientDisconnected,
ServerDisconnected
}
}

View file

@ -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;
}
}
}

View file

@ -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<KBufferDescriptor> _sendBufferDescriptors;
private List<KBufferDescriptor> _receiveBufferDescriptors;
private List<KBufferDescriptor> _exchangeBufferDescriptors;
public KBufferDescriptorTable()
{
_sendBufferDescriptors = new List<KBufferDescriptor>(MaxInternalBuffersCount);
_receiveBufferDescriptors = new List<KBufferDescriptor>(MaxInternalBuffersCount);
_exchangeBufferDescriptors = new List<KBufferDescriptor>(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<KBufferDescriptor> 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<KBufferDescriptor> 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<KBufferDescriptor> 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<KBufferDescriptor> 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;
}
}
}

View file

@ -1,4 +1,6 @@
using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.HOS.Services;
namespace Ryujinx.HLE.HOS.Kernel.Ipc
{
@ -10,12 +12,116 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
private KPort _parent;
public KClientPort(Horizon system) : base(system) { }
public bool IsLight => _parent.IsLight;
public void Initialize(KPort parent, int maxSessions)
private object _countIncLock;
//TODO: Remove that, we need it for now to allow HLE
//SM implementation to work with the new IPC system.
public IpcService Service { get; set; }
public KClientPort(Horizon system, KPort parent, int maxSessions) : base(system)
{
_maxSessions = maxSessions;
_parent = parent;
_countIncLock = new object();
}
public KernelResult Connect(out KClientSession clientSession)
{
clientSession = null;
KProcess currentProcess = System.Scheduler.GetCurrentProcess();
if (currentProcess.ResourceLimit != null &&
!currentProcess.ResourceLimit.Reserve(LimitableResource.Session, 1))
{
return KernelResult.ResLimitExceeded;
}
lock (_countIncLock)
{
if (_sessionsCount < _maxSessions)
{
_sessionsCount++;
}
else
{
currentProcess.ResourceLimit?.Release(LimitableResource.Session, 1);
return KernelResult.SessionCountExceeded;
}
if (_currentCapacity < _sessionsCount)
{
_currentCapacity = _sessionsCount;
}
}
KSession session = new KSession(System);
if (Service != null)
{
session.ClientSession.Service = Service;
}
KernelResult result = _parent.EnqueueIncomingSession(session.ServerSession);
if (result != KernelResult.Success)
{
session.ClientSession.DecrementReferenceCount();
session.ServerSession.DecrementReferenceCount();
return result;
}
clientSession = session.ClientSession;
return result;
}
public KernelResult ConnectLight(out KLightClientSession clientSession)
{
clientSession = null;
KProcess currentProcess = System.Scheduler.GetCurrentProcess();
if (currentProcess.ResourceLimit != null &&
!currentProcess.ResourceLimit.Reserve(LimitableResource.Session, 1))
{
return KernelResult.ResLimitExceeded;
}
lock (_countIncLock)
{
if (_sessionsCount < _maxSessions)
{
_sessionsCount++;
}
else
{
currentProcess.ResourceLimit?.Release(LimitableResource.Session, 1);
return KernelResult.SessionCountExceeded;
}
}
KLightSession session = new KLightSession(System);
KernelResult result = _parent.EnqueueIncomingLightSession(session.ServerSession);
if (result != KernelResult.Success)
{
session.ClientSession.DecrementReferenceCount();
session.ServerSession.DecrementReferenceCount();
return result;
}
clientSession = session.ClientSession;
return result;
}
public new static KernelResult RemoveName(Horizon system, string name)

View file

@ -0,0 +1,60 @@
using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services;
namespace Ryujinx.HLE.HOS.Kernel.Ipc
{
class KClientSession : KSynchronizationObject
{
public KProcess CreatorProcess { get; }
private KSession _parent;
public ChannelState State { get; set; }
//TODO: Remove that, we need it for now to allow HLE
//services implementation to work with the new IPC system.
public IpcService Service { get; set; }
public KClientSession(Horizon system, KSession parent) : base(system)
{
_parent = parent;
State = ChannelState.Open;
CreatorProcess = system.Scheduler.GetCurrentProcess();
CreatorProcess.IncrementReferenceCount();
}
public KernelResult SendSyncRequest(ulong customCmdBuffAddr = 0, ulong customCmdBuffSize = 0)
{
KThread currentThread = System.Scheduler.GetCurrentThread();
KSessionRequest request = new KSessionRequest(currentThread, customCmdBuffAddr, customCmdBuffSize);
System.CriticalSection.Enter();
currentThread.SignaledObj = null;
currentThread.ObjSyncResult = KernelResult.Success;
KernelResult result = _parent.ServerSession.EnqueueRequest(request);
System.CriticalSection.Leave();
if (result == KernelResult.Success)
{
result = currentThread.ObjSyncResult;
}
return result;
}
protected override void Destroy()
{
_parent.DisconnectClient();
_parent.DecrementReferenceCount();
}
}
}

View file

@ -0,0 +1,14 @@
using Ryujinx.HLE.HOS.Kernel.Common;
namespace Ryujinx.HLE.HOS.Kernel.Ipc
{
class KLightClientSession : KAutoObject
{
private KLightSession _parent;
public KLightClientSession(Horizon system, KLightSession parent) : base(system)
{
_parent = parent;
}
}
}

View file

@ -0,0 +1,14 @@
using Ryujinx.HLE.HOS.Kernel.Common;
namespace Ryujinx.HLE.HOS.Kernel.Ipc
{
class KLightServerSession : KAutoObject
{
private KLightSession _parent;
public KLightServerSession(Horizon system, KLightSession parent) : base(system)
{
_parent = parent;
}
}
}

View file

@ -0,0 +1,20 @@
using Ryujinx.HLE.HOS.Kernel.Common;
namespace Ryujinx.HLE.HOS.Kernel.Ipc
{
class KLightSession : KAutoObject
{
public KLightServerSession ServerSession { get; }
public KLightClientSession ClientSession { get; }
private bool _hasBeenInitialized;
public KLightSession(Horizon system) : base(system)
{
ServerSession = new KLightServerSession(system, this);
ClientSession = new KLightClientSession(system, this);
_hasBeenInitialized = true;
}
}
}

View file

@ -4,25 +4,68 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
{
class KPort : KAutoObject
{
public KServerPort ServerPort { get; private set; }
public KClientPort ClientPort { get; private set; }
public KServerPort ServerPort { get; }
public KClientPort ClientPort { get; }
private long _nameAddress;
private bool _isLight;
public KPort(Horizon system) : base(system)
private ChannelState _state;
public bool IsLight { get; private set; }
public KPort(Horizon system, int maxSessions, bool isLight, long nameAddress) : base(system)
{
ServerPort = new KServerPort(system);
ClientPort = new KClientPort(system);
ServerPort = new KServerPort(system, this);
ClientPort = new KClientPort(system, this, maxSessions);
IsLight = isLight;
_nameAddress = nameAddress;
_state = ChannelState.Open;
}
public void Initialize(int maxSessions, bool isLight, long nameAddress)
public KernelResult EnqueueIncomingSession(KServerSession session)
{
ServerPort.Initialize(this);
ClientPort.Initialize(this, maxSessions);
KernelResult result;
_isLight = isLight;
_nameAddress = nameAddress;
System.CriticalSection.Enter();
if (_state == ChannelState.Open)
{
ServerPort.EnqueueIncomingSession(session);
result = KernelResult.Success;
}
else
{
result = KernelResult.PortClosed;
}
System.CriticalSection.Leave();
return result;
}
public KernelResult EnqueueIncomingLightSession(KLightServerSession session)
{
KernelResult result;
System.CriticalSection.Enter();
if (_state == ChannelState.Open)
{
ServerPort.EnqueueIncomingLightSession(session);
result = KernelResult.Success;
}
else
{
result = KernelResult.PortClosed;
}
System.CriticalSection.Leave();
return result;
}
}
}

View file

@ -1,16 +1,87 @@
using Ryujinx.HLE.HOS.Kernel.Common;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.Kernel.Ipc
{
class KServerPort : KSynchronizationObject
{
private LinkedList<KServerSession> _incomingConnections;
private LinkedList<KLightServerSession> _lightIncomingConnections;
private KPort _parent;
public KServerPort(Horizon system) : base(system) { }
public bool IsLight => _parent.IsLight;
public void Initialize(KPort parent)
public KServerPort(Horizon system, KPort parent) : base(system)
{
_parent = parent;
_incomingConnections = new LinkedList<KServerSession>();
_lightIncomingConnections = new LinkedList<KLightServerSession>();
}
public void EnqueueIncomingSession(KServerSession session)
{
AcceptIncomingConnection(_incomingConnections, session);
}
public void EnqueueIncomingLightSession(KLightServerSession session)
{
AcceptIncomingConnection(_lightIncomingConnections, session);
}
private void AcceptIncomingConnection<T>(LinkedList<T> list, T session)
{
System.CriticalSection.Enter();
list.AddLast(session);
if (list.Count == 1)
{
Signal();
}
System.CriticalSection.Leave();
}
public KServerSession AcceptIncomingConnection()
{
return AcceptIncomingConnection(_incomingConnections);
}
public KLightServerSession AcceptIncomingLightConnection()
{
return AcceptIncomingConnection(_lightIncomingConnections);
}
private T AcceptIncomingConnection<T>(LinkedList<T> list)
{
T session = default(T);
System.CriticalSection.Enter();
if (list.Count != 0)
{
session = list.First.Value;
list.RemoveFirst();
}
System.CriticalSection.Leave();
return session;
}
public override bool IsSignaled()
{
if (_parent.IsLight)
{
return _lightIncomingConnections.Count != 0;
}
else
{
return _incomingConnections.Count != 0;
}
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,18 +1,40 @@
using Ryujinx.HLE.HOS.Services;
using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Process;
using System;
namespace Ryujinx.HLE.HOS.Kernel.Ipc
{
class KSession : IDisposable
class KSession : KAutoObject, IDisposable
{
public IpcService Service { get; private set; }
public KServerSession ServerSession { get; }
public KClientSession ClientSession { get; }
public string ServiceName { get; private set; }
private bool _hasBeenInitialized;
public KSession(IpcService service, string serviceName)
public KSession(Horizon system) : base(system)
{
Service = service;
ServiceName = serviceName;
ServerSession = new KServerSession(system, this);
ClientSession = new KClientSession(system, this);
_hasBeenInitialized = true;
}
public void DisconnectClient()
{
if (ClientSession.State == ChannelState.Open)
{
ClientSession.State = ChannelState.ClientDisconnected;
ServerSession.CancelAllRequestsClientDisconnected();
}
}
public void DisconnectServer()
{
if (ClientSession.State == ChannelState.Open)
{
ClientSession.State = ChannelState.ServerDisconnected;
}
}
public void Dispose()
@ -22,10 +44,22 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
protected virtual void Dispose(bool disposing)
{
if (disposing && Service is IDisposable disposableService)
if (disposing && ClientSession.Service is IDisposable disposableService)
{
disposableService.Dispose();
}
}
protected override void Destroy()
{
if (_hasBeenInitialized)
{
KProcess creatorProcess = ClientSession.CreatorProcess;
creatorProcess.ResourceLimit?.Release(LimitableResource.Session, 1);
creatorProcess.DecrementReferenceCount();
}
}
}
}

View file

@ -0,0 +1,31 @@
using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.HOS.Kernel.Threading;
namespace Ryujinx.HLE.HOS.Kernel.Ipc
{
class KSessionRequest
{
public KBufferDescriptorTable BufferDescriptorTable { get; }
public KThread ClientThread { get; }
public KProcess ServerProcess { get; set; }
public KWritableEvent AsyncEvent { get; }
public ulong CustomCmdBuffAddr { get; }
public ulong CustomCmdBuffSize { get; }
public KSessionRequest(
KThread clientThread,
ulong customCmdBuffAddr,
ulong customCmdBuffSize)
{
ClientThread = clientThread;
CustomCmdBuffAddr = customCmdBuffAddr;
CustomCmdBuffSize = customCmdBuffSize;
BufferDescriptorTable = new KBufferDescriptorTable();
}
}
}