IPC refactor part 1: Use explicit separate threads to process requests (#1447)

* Changes to allow explicit management of service threads

* Remove now unused code

* Remove ThreadCounter, its no longer needed

* Allow and use separate server per service, also fix exit issues

* New policy change: PTC version now uses PR number
This commit is contained in:
gdkchan 2020-09-22 01:50:40 -03:00 committed by GitHub
parent 5dd6f41ff4
commit 6c9565693f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 138 additions and 135 deletions

View file

@ -14,7 +14,7 @@ namespace Ryujinx.HLE.HOS.Services.Audio
private const int DefaultSampleRate = 48000;
private const int DefaultChannelsCount = 2;
public IAudioOutManager(ServiceCtx context) { }
public IAudioOutManager(ServiceCtx context) : base(new ServerBase("AudioOutServer")) { }
[Command(0)]
// ListAudioOuts() -> (u32 count, buffer<bytes, 6>)

View file

@ -3,7 +3,7 @@
namespace Ryujinx.HLE.HOS.Services
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class CommandAttribute : Attribute
class CommandAttribute : Attribute
{
public readonly int Id;

View file

@ -15,13 +15,13 @@ namespace Ryujinx.HLE.HOS.Services
{
public IReadOnlyDictionary<int, MethodInfo> Commands { get; }
public ServerBase Server { get; private set; }
private IdDictionary _domainObjects;
private int _selfId;
private bool _isDomain;
public IpcService()
public IpcService(ServerBase server = null)
{
Commands = Assembly.GetExecutingAssembly().GetTypes()
.Where(type => type == GetType())
@ -30,8 +30,9 @@ namespace Ryujinx.HLE.HOS.Services
.Select(command => (((CommandAttribute)command).Id, methodInfo)))
.ToDictionary(command => command.Id, command => command.methodInfo);
_domainObjects = new IdDictionary();
Server = server;
_domainObjects = new IdDictionary();
_selfId = -1;
}
@ -152,6 +153,8 @@ namespace Ryujinx.HLE.HOS.Services
{
IpcService service = context.Session.Service;
obj.TrySetServer(service.Server);
if (service._isDomain)
{
context.Response.ObjectIds.Add(service.Add(obj));
@ -194,6 +197,18 @@ namespace Ryujinx.HLE.HOS.Services
return obj is T ? (T)obj : null;
}
public bool TrySetServer(ServerBase newServer)
{
if (Server == null)
{
Server = newServer;
return true;
}
return false;
}
private int Add(IIpcService obj)
{
return _domainObjects.Add(obj);

View file

@ -45,7 +45,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv
private bool _transferMemInitialized = false;
public INvDrvServices(ServiceCtx context)
public INvDrvServices(ServiceCtx context) : base(new ServerBase("NvservicesServer"))
{
_owner = null;
}

View file

@ -0,0 +1,177 @@
using Ryujinx.Common;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Ipc;
using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.HOS.Kernel.Threading;
using System;
using System.IO;
namespace Ryujinx.HLE.HOS.Services
{
class ServerBase
{
private struct IpcRequest
{
public Switch Device { get; }
public KProcess Process => Thread?.Owner;
public KThread Thread { get; }
public KClientSession Session { get; }
public ulong MessagePtr { get; }
public ulong MessageSize { get; }
public IpcRequest(Switch device, KThread thread, KClientSession session, ulong messagePtr, ulong messageSize)
{
Device = device;
Thread = thread;
Session = session;
MessagePtr = messagePtr;
MessageSize = messageSize;
}
public void SignalDone(KernelResult result)
{
Thread.ObjSyncResult = result;
Thread.Reschedule(ThreadSchedState.Running);
}
}
private readonly AsyncWorkQueue<IpcRequest> _ipcProcessor;
public ServerBase(string name)
{
_ipcProcessor = new AsyncWorkQueue<IpcRequest>(Process, name);
}
public void PushMessage(Switch device, KThread thread, KClientSession session, ulong messagePtr, ulong messageSize)
{
_ipcProcessor.Add(new IpcRequest(device, thread, session, messagePtr, messageSize));
}
private void Process(IpcRequest message)
{
byte[] reqData = new byte[message.MessageSize];
message.Process.CpuMemory.Read(message.MessagePtr, reqData);
IpcMessage request = new IpcMessage(reqData, (long)message.MessagePtr);
IpcMessage response = new IpcMessage();
using (MemoryStream raw = new MemoryStream(request.RawData))
{
BinaryReader reqReader = new BinaryReader(raw);
if (request.Type == IpcMessageType.Request ||
request.Type == IpcMessageType.RequestWithContext)
{
response.Type = IpcMessageType.Response;
using (MemoryStream resMs = new MemoryStream())
{
BinaryWriter resWriter = new BinaryWriter(resMs);
ServiceCtx context = new ServiceCtx(
message.Device,
message.Process,
message.Process.CpuMemory,
message.Thread,
message.Session,
request,
response,
reqReader,
resWriter);
message.Session.Service.CallMethod(context);
response.RawData = resMs.ToArray();
}
}
else if (request.Type == IpcMessageType.Control ||
request.Type == IpcMessageType.ControlWithContext)
{
uint magic = (uint)reqReader.ReadUInt64();
uint cmdId = (uint)reqReader.ReadUInt64();
switch (cmdId)
{
case 0:
request = FillResponse(response, 0, message.Session.Service.ConvertToDomain());
break;
case 3:
request = FillResponse(response, 0, 0x1000);
break;
// TODO: Whats the difference between IpcDuplicateSession/Ex?
case 2:
case 4:
int unknown = reqReader.ReadInt32();
if (message.Process.HandleTable.GenerateHandle(message.Session, out int handle) != KernelResult.Success)
{
throw new InvalidOperationException("Out of handles!");
}
response.HandleDesc = IpcHandleDesc.MakeMove(handle);
request = FillResponse(response, 0);
break;
default: throw new NotImplementedException(cmdId.ToString());
}
}
else if (request.Type == IpcMessageType.CloseSession)
{
message.SignalDone(KernelResult.PortRemoteClosed);
return;
}
else
{
throw new NotImplementedException(request.Type.ToString());
}
message.Process.CpuMemory.Write(message.MessagePtr, response.GetBytes((long)message.MessagePtr));
}
message.SignalDone(KernelResult.Success);
}
private static IpcMessage FillResponse(IpcMessage response, long result, params int[] values)
{
using (MemoryStream ms = new MemoryStream())
{
BinaryWriter writer = new BinaryWriter(ms);
foreach (int value in values)
{
writer.Write(value);
}
return FillResponse(response, result, ms.ToArray());
}
}
private static IpcMessage FillResponse(IpcMessage response, long result, byte[] data = null)
{
response.Type = IpcMessageType.Response;
using (MemoryStream ms = new MemoryStream())
{
BinaryWriter writer = new BinaryWriter(ms);
writer.Write(IpcMagic.Sfco);
writer.Write(result);
if (data != null)
{
writer.Write(data);
}
response.RawData = ms.ToArray();
}
return response;
}
}
}

View file

@ -3,7 +3,7 @@
namespace Ryujinx.HLE.HOS.Services
{
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class ServiceAttribute : Attribute
class ServiceAttribute : Attribute
{
public readonly string Name;
public readonly object Parameter;

View file

@ -18,9 +18,11 @@ namespace Ryujinx.HLE.HOS.Services.Sm
private ConcurrentDictionary<string, KPort> _registeredServices;
private readonly ServerBase _commonServer;
private bool _isInitialized;
public IUserInterface(ServiceCtx context = null)
public IUserInterface(ServiceCtx context = null) : base(new ServerBase("SmServer"))
{
_registeredServices = new ConcurrentDictionary<string, KPort>();
@ -28,6 +30,8 @@ namespace Ryujinx.HLE.HOS.Services.Sm
.SelectMany(type => type.GetCustomAttributes(typeof(ServiceAttribute), true)
.Select(service => (((ServiceAttribute)service).Name, type)))
.ToDictionary(service => service.Name, service => service.type);
_commonServer = new ServerBase("CommonServer");
}
public static void InitializePort(Horizon system)
@ -36,7 +40,9 @@ namespace Ryujinx.HLE.HOS.Services.Sm
port.ClientPort.SetName("sm:");
port.ClientPort.Service = new IUserInterface();
IUserInterface smService = new IUserInterface();
port.ClientPort.Service = smService;
}
[Command(0)]
@ -81,8 +87,13 @@ namespace Ryujinx.HLE.HOS.Services.Sm
{
ServiceAttribute serviceAttribute = (ServiceAttribute)type.GetCustomAttributes(typeof(ServiceAttribute)).First(service => ((ServiceAttribute)service).Name == name);
session.ClientSession.Service = serviceAttribute.Parameter != null ? (IpcService)Activator.CreateInstance(type, context, serviceAttribute.Parameter)
: (IpcService)Activator.CreateInstance(type, context);
IpcService service = serviceAttribute.Parameter != null
? (IpcService)Activator.CreateInstance(type, context, serviceAttribute.Parameter)
: (IpcService)Activator.CreateInstance(type, context);
service.TrySetServer(_commonServer);
session.ClientSession.Service = service;
}
else
{

View file

@ -102,7 +102,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
private List<BsdSocket> _sockets = new List<BsdSocket>();
public IClient(ServiceCtx context, bool isPrivileged)
public IClient(ServiceCtx context, bool isPrivileged) : base(new ServerBase("BsdServer"))
{
_isPrivileged = isPrivileged;
}

View file

@ -5,7 +5,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi
[Service("vi:u")]
class IApplicationRootService : IpcService
{
public IApplicationRootService(ServiceCtx context) { }
public IApplicationRootService(ServiceCtx context) : base(new ServerBase("ViServer")) { }
[Command(0)]
// GetDisplayService(u32) -> object<nn::visrv::sf::IApplicationDisplayService>