mirror of
https://github.com/Ryujinx/Ryujinx.git
synced 2024-12-23 03:42:01 +00:00
666e05f5cb
* use ArrayPool, avoid 6000-7000 allocs/sec of runtime * use ArrayPool, avoid ~7k allocs/second during game execution * use ArrayPool, avoid ~3000 allocs/sec during game execution * use MemoryPool, reduce 0.5 MB/sec of new allocations during game execution * avoid over-allocation by setting List<> Capacity when known * remove LINQ in KTimeManager.UnscheduleFutureInvocation * KTimeManager - avoid spinning one more time when the time has arrived * KTimeManager - let SpinWait decide when to Thread.Yield(), and don't SpinOnce() immediately after Thread.Yield() * use MemoryPool, reduce ~175k bytes/sec allocation during game execution * IpcService - call commands via dynamic methods instead of reflection .Invoke(). Faster to call and with fewer allocations because parameters can be passed directly instead of as an array * Make ButtonMappingEntry a record struct to avoid allocations. Set the List<ButtonMappingEntry> capacity according to use. * add MemoryBuffer type for working with MemoryPool<byte> * update changes to use MemoryBuffer * make parameter ReadOnlySpan instead of Span * whitespace fix * Revert "IpcService - call commands via dynamic methods instead of reflection .Invoke(). Faster to call and with fewer allocations because parameters can be passed directly instead of as an array" This reverts commit f2c698bdf65f049e8481c9f2ec7138d9b9a8261d. * tweak KTimeManager spin behavior * replace MemoryBuffer with ByteMemoryPool modeled after System.Buffers.ArrayMemoryPool<T> * make ByteMemoryPoolBuffer responsible for renting memory
423 lines
15 KiB
C#
423 lines
15 KiB
C#
using Ryujinx.Common;
|
|
using Ryujinx.Common.Memory;
|
|
using Ryujinx.HLE.HOS.Ipc;
|
|
using Ryujinx.HLE.HOS.Kernel;
|
|
using Ryujinx.HLE.HOS.Kernel.Ipc;
|
|
using Ryujinx.HLE.HOS.Kernel.Process;
|
|
using Ryujinx.HLE.HOS.Kernel.Threading;
|
|
using Ryujinx.Horizon.Common;
|
|
using System;
|
|
using System.Buffers;
|
|
using System.Buffers.Binary;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Threading;
|
|
|
|
namespace Ryujinx.HLE.HOS.Services
|
|
{
|
|
class ServerBase : IDisposable
|
|
{
|
|
// Must be the maximum value used by services (highest one know is the one used by nvservices = 0x8000).
|
|
// Having a size that is too low will cause failures as data copy will fail if the receiving buffer is
|
|
// not large enough.
|
|
private const int PointerBufferSize = 0x8000;
|
|
|
|
private readonly static uint[] DefaultCapabilities = new uint[]
|
|
{
|
|
0x030363F7,
|
|
0x1FFFFFCF,
|
|
0x207FFFEF,
|
|
0x47E0060F,
|
|
0x0048BFFF,
|
|
0x01007FFF
|
|
};
|
|
|
|
private readonly object _handleLock = new();
|
|
|
|
private readonly KernelContext _context;
|
|
private KProcess _selfProcess;
|
|
|
|
private readonly List<int> _sessionHandles = new List<int>();
|
|
private readonly List<int> _portHandles = new List<int>();
|
|
private readonly Dictionary<int, IpcService> _sessions = new Dictionary<int, IpcService>();
|
|
private readonly Dictionary<int, Func<IpcService>> _ports = new Dictionary<int, Func<IpcService>>();
|
|
|
|
private readonly MemoryStream _requestDataStream;
|
|
private readonly BinaryReader _requestDataReader;
|
|
|
|
private readonly MemoryStream _responseDataStream;
|
|
private readonly BinaryWriter _responseDataWriter;
|
|
|
|
public ManualResetEvent InitDone { get; }
|
|
public string Name { get; }
|
|
public Func<IpcService> SmObjectFactory { get; }
|
|
|
|
public ServerBase(KernelContext context, string name, Func<IpcService> smObjectFactory = null)
|
|
{
|
|
_context = context;
|
|
|
|
_requestDataStream = MemoryStreamManager.Shared.GetStream();
|
|
_requestDataReader = new BinaryReader(_requestDataStream);
|
|
|
|
_responseDataStream = MemoryStreamManager.Shared.GetStream();
|
|
_responseDataWriter = new BinaryWriter(_responseDataStream);
|
|
|
|
InitDone = new ManualResetEvent(false);
|
|
Name = name;
|
|
SmObjectFactory = smObjectFactory;
|
|
|
|
const ProcessCreationFlags flags =
|
|
ProcessCreationFlags.EnableAslr |
|
|
ProcessCreationFlags.AddressSpace64Bit |
|
|
ProcessCreationFlags.Is64Bit |
|
|
ProcessCreationFlags.PoolPartitionSystem;
|
|
|
|
ProcessCreationInfo creationInfo = new ProcessCreationInfo("Service", 1, 0, 0x8000000, 1, flags, 0, 0);
|
|
|
|
KernelStatic.StartInitialProcess(context, creationInfo, DefaultCapabilities, 44, Main);
|
|
}
|
|
|
|
private void AddPort(int serverPortHandle, Func<IpcService> objectFactory)
|
|
{
|
|
lock (_handleLock)
|
|
{
|
|
_portHandles.Add(serverPortHandle);
|
|
}
|
|
_ports.Add(serverPortHandle, objectFactory);
|
|
}
|
|
|
|
public void AddSessionObj(KServerSession serverSession, IpcService obj)
|
|
{
|
|
// Ensure that the sever loop is running.
|
|
InitDone.WaitOne();
|
|
|
|
_selfProcess.HandleTable.GenerateHandle(serverSession, out int serverSessionHandle);
|
|
AddSessionObj(serverSessionHandle, obj);
|
|
}
|
|
|
|
public void AddSessionObj(int serverSessionHandle, IpcService obj)
|
|
{
|
|
lock (_handleLock)
|
|
{
|
|
_sessionHandles.Add(serverSessionHandle);
|
|
}
|
|
_sessions.Add(serverSessionHandle, obj);
|
|
}
|
|
|
|
private void Main()
|
|
{
|
|
ServerLoop();
|
|
}
|
|
|
|
private void ServerLoop()
|
|
{
|
|
_selfProcess = KernelStatic.GetCurrentProcess();
|
|
|
|
if (SmObjectFactory != null)
|
|
{
|
|
_context.Syscall.ManageNamedPort(out int serverPortHandle, "sm:", 50);
|
|
|
|
AddPort(serverPortHandle, SmObjectFactory);
|
|
}
|
|
|
|
InitDone.Set();
|
|
|
|
KThread thread = KernelStatic.GetCurrentThread();
|
|
ulong messagePtr = thread.TlsAddress;
|
|
_context.Syscall.SetHeapSize(out ulong heapAddr, 0x200000);
|
|
|
|
_selfProcess.CpuMemory.Write(messagePtr + 0x0, 0);
|
|
_selfProcess.CpuMemory.Write(messagePtr + 0x4, 2 << 10);
|
|
_selfProcess.CpuMemory.Write(messagePtr + 0x8, heapAddr | ((ulong)PointerBufferSize << 48));
|
|
|
|
int replyTargetHandle = 0;
|
|
|
|
while (true)
|
|
{
|
|
int handleCount;
|
|
int portHandleCount;
|
|
int[] handles;
|
|
|
|
lock (_handleLock)
|
|
{
|
|
portHandleCount = _portHandles.Count;
|
|
handleCount = portHandleCount + _sessionHandles.Count;
|
|
|
|
handles = ArrayPool<int>.Shared.Rent(handleCount);
|
|
|
|
_portHandles.CopyTo(handles, 0);
|
|
_sessionHandles.CopyTo(handles, portHandleCount);
|
|
}
|
|
|
|
// We still need a timeout here to allow the service to pick up and listen new sessions...
|
|
var rc = _context.Syscall.ReplyAndReceive(out int signaledIndex, handles.AsSpan(0, handleCount), replyTargetHandle, 1000000L);
|
|
|
|
thread.HandlePostSyscall();
|
|
|
|
if (!thread.Context.Running)
|
|
{
|
|
break;
|
|
}
|
|
|
|
replyTargetHandle = 0;
|
|
|
|
if (rc == Result.Success && signaledIndex >= portHandleCount)
|
|
{
|
|
// We got a IPC request, process it, pass to the appropriate service if needed.
|
|
int signaledHandle = handles[signaledIndex];
|
|
|
|
if (Process(signaledHandle, heapAddr))
|
|
{
|
|
replyTargetHandle = signaledHandle;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (rc == Result.Success)
|
|
{
|
|
// We got a new connection, accept the session to allow servicing future requests.
|
|
if (_context.Syscall.AcceptSession(out int serverSessionHandle, handles[signaledIndex]) == Result.Success)
|
|
{
|
|
IpcService obj = _ports[handles[signaledIndex]].Invoke();
|
|
|
|
AddSessionObj(serverSessionHandle, obj);
|
|
}
|
|
}
|
|
|
|
_selfProcess.CpuMemory.Write(messagePtr + 0x0, 0);
|
|
_selfProcess.CpuMemory.Write(messagePtr + 0x4, 2 << 10);
|
|
_selfProcess.CpuMemory.Write(messagePtr + 0x8, heapAddr | ((ulong)PointerBufferSize << 48));
|
|
}
|
|
|
|
ArrayPool<int>.Shared.Return(handles);
|
|
}
|
|
|
|
Dispose();
|
|
}
|
|
|
|
private bool Process(int serverSessionHandle, ulong recvListAddr)
|
|
{
|
|
KProcess process = KernelStatic.GetCurrentProcess();
|
|
KThread thread = KernelStatic.GetCurrentThread();
|
|
ulong messagePtr = thread.TlsAddress;
|
|
|
|
IpcMessage request = ReadRequest(process, messagePtr);
|
|
|
|
IpcMessage response = new IpcMessage();
|
|
|
|
ulong tempAddr = recvListAddr;
|
|
int sizesOffset = request.RawData.Length - ((request.RecvListBuff.Count * 2 + 3) & ~3);
|
|
|
|
bool noReceive = true;
|
|
|
|
for (int i = 0; i < request.ReceiveBuff.Count; i++)
|
|
{
|
|
noReceive &= (request.ReceiveBuff[i].Position == 0);
|
|
}
|
|
|
|
if (noReceive)
|
|
{
|
|
response.PtrBuff.EnsureCapacity(request.RecvListBuff.Count);
|
|
|
|
for (int i = 0; i < request.RecvListBuff.Count; i++)
|
|
{
|
|
ulong size = (ulong)BinaryPrimitives.ReadInt16LittleEndian(request.RawData.AsSpan(sizesOffset + i * 2, 2));
|
|
|
|
response.PtrBuff.Add(new IpcPtrBuffDesc(tempAddr, (uint)i, size));
|
|
|
|
request.RecvListBuff[i] = new IpcRecvListBuffDesc(tempAddr, size);
|
|
|
|
tempAddr += size;
|
|
}
|
|
}
|
|
|
|
bool shouldReply = true;
|
|
bool isTipcCommunication = false;
|
|
|
|
_requestDataStream.SetLength(0);
|
|
_requestDataStream.Write(request.RawData);
|
|
_requestDataStream.Position = 0;
|
|
|
|
if (request.Type == IpcMessageType.CmifRequest ||
|
|
request.Type == IpcMessageType.CmifRequestWithContext)
|
|
{
|
|
response.Type = IpcMessageType.CmifResponse;
|
|
|
|
_responseDataStream.SetLength(0);
|
|
|
|
ServiceCtx context = new ServiceCtx(
|
|
_context.Device,
|
|
process,
|
|
process.CpuMemory,
|
|
thread,
|
|
request,
|
|
response,
|
|
_requestDataReader,
|
|
_responseDataWriter);
|
|
|
|
_sessions[serverSessionHandle].CallCmifMethod(context);
|
|
|
|
response.RawData = _responseDataStream.ToArray();
|
|
}
|
|
else if (request.Type == IpcMessageType.CmifControl ||
|
|
request.Type == IpcMessageType.CmifControlWithContext)
|
|
{
|
|
uint magic = (uint)_requestDataReader.ReadUInt64();
|
|
uint cmdId = (uint)_requestDataReader.ReadUInt64();
|
|
|
|
switch (cmdId)
|
|
{
|
|
case 0:
|
|
FillHipcResponse(response, 0, _sessions[serverSessionHandle].ConvertToDomain());
|
|
break;
|
|
|
|
case 3:
|
|
FillHipcResponse(response, 0, PointerBufferSize);
|
|
break;
|
|
|
|
// TODO: Whats the difference between IpcDuplicateSession/Ex?
|
|
case 2:
|
|
case 4:
|
|
int unknown = _requestDataReader.ReadInt32();
|
|
|
|
_context.Syscall.CreateSession(out int dupServerSessionHandle, out int dupClientSessionHandle, false, 0);
|
|
|
|
AddSessionObj(dupServerSessionHandle, _sessions[serverSessionHandle]);
|
|
|
|
response.HandleDesc = IpcHandleDesc.MakeMove(dupClientSessionHandle);
|
|
|
|
FillHipcResponse(response, 0);
|
|
|
|
break;
|
|
|
|
default: throw new NotImplementedException(cmdId.ToString());
|
|
}
|
|
}
|
|
else if (request.Type == IpcMessageType.CmifCloseSession || request.Type == IpcMessageType.TipcCloseSession)
|
|
{
|
|
_context.Syscall.CloseHandle(serverSessionHandle);
|
|
lock (_handleLock)
|
|
{
|
|
_sessionHandles.Remove(serverSessionHandle);
|
|
}
|
|
IpcService service = _sessions[serverSessionHandle];
|
|
(service as IDisposable)?.Dispose();
|
|
_sessions.Remove(serverSessionHandle);
|
|
shouldReply = false;
|
|
}
|
|
// If the type is past 0xF, we are using TIPC
|
|
else if (request.Type > IpcMessageType.TipcCloseSession)
|
|
{
|
|
isTipcCommunication = true;
|
|
|
|
// Response type is always the same as request on TIPC.
|
|
response.Type = request.Type;
|
|
|
|
_responseDataStream.SetLength(0);
|
|
|
|
ServiceCtx context = new ServiceCtx(
|
|
_context.Device,
|
|
process,
|
|
process.CpuMemory,
|
|
thread,
|
|
request,
|
|
response,
|
|
_requestDataReader,
|
|
_responseDataWriter);
|
|
|
|
_sessions[serverSessionHandle].CallTipcMethod(context);
|
|
|
|
response.RawData = _responseDataStream.ToArray();
|
|
|
|
using var responseStream = response.GetStreamTipc();
|
|
process.CpuMemory.Write(messagePtr, responseStream.GetReadOnlySequence());
|
|
}
|
|
else
|
|
{
|
|
throw new NotImplementedException(request.Type.ToString());
|
|
}
|
|
|
|
if (!isTipcCommunication)
|
|
{
|
|
using var responseStream = response.GetStream((long)messagePtr, recvListAddr | ((ulong)PointerBufferSize << 48));
|
|
process.CpuMemory.Write(messagePtr, responseStream.GetReadOnlySequence());
|
|
}
|
|
|
|
return shouldReply;
|
|
}
|
|
|
|
private static IpcMessage ReadRequest(KProcess process, ulong messagePtr)
|
|
{
|
|
const int messageSize = 0x100;
|
|
|
|
byte[] reqData = ArrayPool<byte>.Shared.Rent(messageSize);
|
|
|
|
Span<byte> reqDataSpan = reqData.AsSpan(0, messageSize);
|
|
reqDataSpan.Clear();
|
|
|
|
process.CpuMemory.Read(messagePtr, reqDataSpan);
|
|
|
|
IpcMessage request = new IpcMessage(reqDataSpan, (long)messagePtr);
|
|
|
|
ArrayPool<byte>.Shared.Return(reqData);
|
|
|
|
return request;
|
|
}
|
|
|
|
private void FillHipcResponse(IpcMessage response, long result)
|
|
{
|
|
FillHipcResponse(response, result, ReadOnlySpan<byte>.Empty);
|
|
}
|
|
|
|
private void FillHipcResponse(IpcMessage response, long result, int value)
|
|
{
|
|
Span<byte> span = stackalloc byte[sizeof(int)];
|
|
BinaryPrimitives.WriteInt32LittleEndian(span, value);
|
|
FillHipcResponse(response, result, span);
|
|
}
|
|
|
|
private void FillHipcResponse(IpcMessage response, long result, ReadOnlySpan<byte> data)
|
|
{
|
|
response.Type = IpcMessageType.CmifResponse;
|
|
|
|
_responseDataStream.SetLength(0);
|
|
|
|
_responseDataStream.Write(IpcMagic.Sfco);
|
|
_responseDataStream.Write(result);
|
|
|
|
_responseDataStream.Write(data);
|
|
|
|
response.RawData = _responseDataStream.ToArray();
|
|
}
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
if (disposing)
|
|
{
|
|
foreach (IpcService service in _sessions.Values)
|
|
{
|
|
if (service is IDisposable disposableObj)
|
|
{
|
|
disposableObj.Dispose();
|
|
}
|
|
|
|
service.DestroyAtExit();
|
|
}
|
|
|
|
_sessions.Clear();
|
|
|
|
_requestDataReader.Dispose();
|
|
_requestDataStream.Dispose();
|
|
_responseDataWriter.Dispose();
|
|
_responseDataStream.Dispose();
|
|
|
|
InitDone.Dispose();
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
}
|
|
}
|
|
}
|