IPC refactor part 3+4: New server HIPC message processor (#4188)

* IPC refactor part 3 + 4: New server HIPC message processor with source generator based serialization

* Make types match on calls to AlignUp/AlignDown

* Formatting

* Address some PR feedback

* Move BitfieldExtensions to Ryujinx.Common.Utilities and consolidate implementations

* Rename Reader/Writer to SpanReader/SpanWriter and move to Ryujinx.Common.Memory

* Implement EventType

* Address more PR feedback

* Log request processing errors since they are not normal

* Rename waitable to multiwait and add missing lock

* PR feedback

* Ac_K PR feedback
This commit is contained in:
gdkchan 2023-01-04 19:15:45 -03:00 committed by GitHub
parent c6a139a6e7
commit 08831eecf7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
213 changed files with 9762 additions and 1010 deletions

View file

@ -0,0 +1,12 @@
namespace Ryujinx.Horizon.Sdk.Sf.Cmif
{
struct CmifDomainInHeader
{
public CmifDomainRequestType Type;
public byte ObjectsCount;
public ushort DataSize;
public int ObjectId;
public uint Padding;
public uint Token;
}
}

View file

@ -0,0 +1,12 @@
namespace Ryujinx.Horizon.Sdk.Sf.Cmif
{
struct CmifDomainOutHeader
{
#pragma warning disable CS0649
public uint ObjectsCount;
public uint Padding;
public uint Padding2;
public uint Padding3;
#pragma warning restore CS0649
}
}

View file

@ -0,0 +1,9 @@
namespace Ryujinx.Horizon.Sdk.Sf.Cmif
{
enum CmifDomainRequestType : byte
{
Invalid = 0,
SendMessage = 1,
Close = 2
}
}

View file

@ -0,0 +1,10 @@
namespace Ryujinx.Horizon.Sdk.Sf.Cmif
{
struct CmifInHeader
{
public uint Magic;
public uint Version;
public uint CommandId;
public uint Token;
}
}

View file

@ -0,0 +1,128 @@
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Sdk.Sf.Hipc;
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Sf.Cmif
{
static class CmifMessage
{
public const uint CmifInHeaderMagic = 0x49434653; // SFCI
public const uint CmifOutHeaderMagic = 0x4f434653; // SFCO
public static CmifRequest CreateRequest(Span<byte> output, CmifRequestFormat format)
{
int totalSize = 16;
if (format.ObjectId != 0)
{
totalSize += Unsafe.SizeOf<CmifDomainInHeader>() + format.ObjectsCount * sizeof(int);
}
totalSize += Unsafe.SizeOf<CmifInHeader>() + format.DataSize;
totalSize = (totalSize + 1) & ~1;
int outPointerSizeTableOffset = totalSize;
int outPointerSizeTableSize = format.OutAutoBuffersCount + format.OutPointersCount;
totalSize += sizeof(ushort) * outPointerSizeTableSize;
int rawDataSizeInWords = (totalSize + sizeof(uint) - 1) / sizeof(uint);
CmifRequest request = new CmifRequest();
request.Hipc = HipcMessage.WriteMessage(output, new HipcMetadata()
{
Type = format.Context != 0 ? (int)CommandType.RequestWithContext : (int)CommandType.Request,
SendStaticsCount = format.InAutoBuffersCount + format.InPointersCount,
SendBuffersCount = format.InAutoBuffersCount + format.InBuffersCount,
ReceiveBuffersCount = format.OutAutoBuffersCount + format.OutBuffersCount,
ExchangeBuffersCount = format.InOutBuffersCount,
DataWordsCount = rawDataSizeInWords,
ReceiveStaticsCount = outPointerSizeTableSize + format.OutFixedPointersCount,
SendPid = format.SendPid,
CopyHandlesCount = format.HandlesCount,
MoveHandlesCount = 0
});
Span<uint> data = request.Hipc.DataWords;
if (format.ObjectId != 0)
{
ref CmifDomainInHeader domainHeader = ref MemoryMarshal.Cast<uint, CmifDomainInHeader>(data)[0];
int payloadSize = Unsafe.SizeOf<CmifInHeader>() + format.DataSize;
domainHeader = new CmifDomainInHeader()
{
Type = CmifDomainRequestType.SendMessage,
ObjectsCount = (byte)format.ObjectsCount,
DataSize = (ushort)payloadSize,
ObjectId = format.ObjectId,
Padding = 0,
Token = format.Context
};
data = data.Slice(Unsafe.SizeOf<CmifDomainInHeader>() / sizeof(uint));
request.Objects = data.Slice((payloadSize + sizeof(uint) - 1) / sizeof(uint));
}
ref CmifInHeader header = ref MemoryMarshal.Cast<uint, CmifInHeader>(data)[0];
header = new CmifInHeader()
{
Magic = CmifInHeaderMagic,
Version = format.Context != 0 ? 1u : 0u,
CommandId = format.RequestId,
Token = format.ObjectId != 0 ? 0u : format.Context
};
request.Data = MemoryMarshal.Cast<uint, byte>(data).Slice(Unsafe.SizeOf<CmifInHeader>());
int paddingSizeBefore = (rawDataSizeInWords - request.Hipc.DataWords.Length) * sizeof(uint);
Span<byte> outPointerTable = MemoryMarshal.Cast<uint, byte>(request.Hipc.DataWords).Slice(outPointerSizeTableOffset - paddingSizeBefore);
request.OutPointerSizes = MemoryMarshal.Cast<byte, ushort>(outPointerTable);
request.ServerPointerSize = format.ServerPointerSize;
return request;
}
public static Result ParseResponse(out CmifResponse response, Span<byte> input, bool isDomain, int size)
{
HipcMessage responseMessage = new HipcMessage(input);
Span<byte> data = MemoryMarshal.Cast<uint, byte>(responseMessage.Data.DataWords);
Span<uint> objects = Span<uint>.Empty;
if (isDomain)
{
data = data.Slice(Unsafe.SizeOf<CmifDomainOutHeader>());
objects = MemoryMarshal.Cast<byte, uint>(data.Slice(Unsafe.SizeOf<CmifOutHeader>() + size));
}
CmifOutHeader header = MemoryMarshal.Cast<byte, CmifOutHeader>(data)[0];
if (header.Magic != CmifOutHeaderMagic)
{
response = default;
return SfResult.InvalidOutHeader;
}
if (header.Result.IsFailure)
{
response = default;
return header.Result;
}
response = new CmifResponse()
{
Data = data.Slice(Unsafe.SizeOf<CmifOutHeader>()),
Objects = objects,
CopyHandles = responseMessage.Data.CopyHandles,
MoveHandles = responseMessage.Data.MoveHandles
};
return Result.Success;
}
}
}

View file

@ -0,0 +1,14 @@
using Ryujinx.Horizon.Common;
namespace Ryujinx.Horizon.Sdk.Sf.Cmif
{
struct CmifOutHeader
{
#pragma warning disable CS0649
public uint Magic;
public uint Version;
public Result Result;
public uint Token;
#pragma warning restore CS0649
}
}

View file

@ -0,0 +1,14 @@
using Ryujinx.Horizon.Sdk.Sf.Hipc;
using System;
namespace Ryujinx.Horizon.Sdk.Sf.Cmif
{
ref struct CmifRequest
{
public HipcMessageData Hipc;
public Span<byte> Data;
public Span<ushort> OutPointerSizes;
public Span<uint> Objects;
public int ServerPointerSize;
}
}

View file

@ -0,0 +1,24 @@
namespace Ryujinx.Horizon.Sdk.Sf.Cmif
{
struct CmifRequestFormat
{
#pragma warning disable CS0649
public int ObjectId;
public uint RequestId;
public uint Context;
public int DataSize;
public int ServerPointerSize;
public int InAutoBuffersCount;
public int OutAutoBuffersCount;
public int InBuffersCount;
public int OutBuffersCount;
public int InOutBuffersCount;
public int InPointersCount;
public int OutPointersCount;
public int OutFixedPointersCount;
public int ObjectsCount;
public int HandlesCount;
public bool SendPid;
#pragma warning restore CS0649
}
}

View file

@ -0,0 +1,12 @@
using System;
namespace Ryujinx.Horizon.Sdk.Sf.Cmif
{
ref struct CmifResponse
{
public ReadOnlySpan<byte> Data;
public ReadOnlySpan<uint> Objects;
public ReadOnlySpan<int> CopyHandles;
public ReadOnlySpan<int> MoveHandles;
}
}

View file

@ -0,0 +1,14 @@
namespace Ryujinx.Horizon.Sdk.Sf.Cmif
{
enum CommandType
{
Invalid = 0,
LegacyRequest = 1,
Close = 2,
LegacyControl = 3,
Request = 4,
Control = 5,
RequestWithContext = 6,
ControlWithContext = 7
}
}

View file

@ -0,0 +1,7 @@
namespace Ryujinx.Horizon.Sdk.Sf.Cmif
{
abstract partial class DomainServiceObject : ServerDomainBase, IServiceObject
{
public abstract ServerDomainBase GetServerDomain();
}
}

View file

@ -0,0 +1,75 @@
using Ryujinx.Horizon.Common;
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Sf.Cmif
{
class DomainServiceObjectDispatchTable : ServiceDispatchTableBase
{
public override Result ProcessMessage(ref ServiceDispatchContext context, ReadOnlySpan<byte> inRawData)
{
return ProcessMessageImpl(ref context, ((DomainServiceObject)context.ServiceObject).GetServerDomain(), inRawData);
}
private Result ProcessMessageImpl(ref ServiceDispatchContext context, ServerDomainBase domain, ReadOnlySpan<byte> inRawData)
{
if (inRawData.Length < Unsafe.SizeOf<CmifDomainInHeader>())
{
return SfResult.InvalidHeaderSize;
}
var inHeader = MemoryMarshal.Cast<byte, CmifDomainInHeader>(inRawData)[0];
ReadOnlySpan<byte> inDomainRawData = inRawData.Slice(Unsafe.SizeOf<CmifDomainInHeader>());
int targetObjectId = inHeader.ObjectId;
switch (inHeader.Type)
{
case CmifDomainRequestType.SendMessage:
var targetObject = domain.GetObject(targetObjectId);
if (targetObject == null)
{
return SfResult.TargetNotFound;
}
if (inHeader.DataSize + inHeader.ObjectsCount * sizeof(int) > inDomainRawData.Length)
{
return SfResult.InvalidHeaderSize;
}
ReadOnlySpan<byte> inMessageRawData = inDomainRawData.Slice(0, inHeader.DataSize);
if (inHeader.ObjectsCount > DomainServiceObjectProcessor.MaximumObjects)
{
return SfResult.InvalidInObjectsCount;
}
int[] inObjectIds = new int[inHeader.ObjectsCount];
var domainProcessor = new DomainServiceObjectProcessor(domain, inObjectIds);
if (context.Processor == null)
{
context.Processor = domainProcessor;
}
else
{
context.Processor.SetImplementationProcessor(domainProcessor);
}
context.ServiceObject = targetObject.ServiceObject;
return targetObject.ProcessMessage(ref context, inMessageRawData);
case CmifDomainRequestType.Close:
domain.UnregisterObject(targetObjectId);
return Result.Success;
default:
return SfResult.InvalidInHeader;
}
}
}
}

View file

@ -0,0 +1,140 @@
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Sdk.Sf.Hipc;
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Sf.Cmif
{
class DomainServiceObjectProcessor : ServerMessageProcessor
{
public const int MaximumObjects = 8;
private ServerMessageProcessor _implProcessor;
private readonly ServerDomainBase _domain;
private int _outObjectIdsOffset;
private readonly int[] _inObjectIds;
private readonly int[] _reservedObjectIds;
private ServerMessageRuntimeMetadata _implMetadata;
private int InObjectsCount => _inObjectIds.Length;
private int OutObjectsCount => _implMetadata.OutObjectsCount;
private int ImplOutHeadersSize => _implMetadata.OutHeadersSize;
private int ImplOutDataTotalSize => _implMetadata.OutDataSize + _implMetadata.OutHeadersSize;
public DomainServiceObjectProcessor(ServerDomainBase domain, int[] inObjectIds)
{
_domain = domain;
_inObjectIds = inObjectIds;
_reservedObjectIds = new int[MaximumObjects];
}
public override void SetImplementationProcessor(ServerMessageProcessor impl)
{
if (_implProcessor == null)
{
_implProcessor = impl;
}
else
{
_implProcessor.SetImplementationProcessor(impl);
}
_implMetadata = _implProcessor.GetRuntimeMetadata();
}
public override ServerMessageRuntimeMetadata GetRuntimeMetadata()
{
var runtimeMetadata = _implProcessor.GetRuntimeMetadata();
return new ServerMessageRuntimeMetadata(
(ushort)(runtimeMetadata.InDataSize + runtimeMetadata.InObjectsCount * sizeof(int)),
(ushort)(runtimeMetadata.OutDataSize + runtimeMetadata.OutObjectsCount * sizeof(int)),
(byte)(runtimeMetadata.InHeadersSize + Unsafe.SizeOf<CmifDomainInHeader>()),
(byte)(runtimeMetadata.OutHeadersSize + Unsafe.SizeOf<CmifDomainOutHeader>()),
0,
0);
}
public override Result PrepareForProcess(ref ServiceDispatchContext context, ServerMessageRuntimeMetadata runtimeMetadata)
{
if (_implMetadata.InObjectsCount != InObjectsCount)
{
return SfResult.InvalidInObjectsCount;
}
Result result = _domain.ReserveIds(new Span<int>(_reservedObjectIds).Slice(0, OutObjectsCount));
if (result.IsFailure)
{
return result;
}
return _implProcessor.PrepareForProcess(ref context, runtimeMetadata);
}
public override Result GetInObjects(Span<ServiceObjectHolder> inObjects)
{
for (int i = 0; i < InObjectsCount; i++)
{
inObjects[i] = _domain.GetObject(_inObjectIds[i]);
}
return Result.Success;
}
public override HipcMessageData PrepareForReply(scoped ref ServiceDispatchContext context, out Span<byte> outRawData, ServerMessageRuntimeMetadata runtimeMetadata)
{
var response = _implProcessor.PrepareForReply(ref context, out outRawData, runtimeMetadata);
int outHeaderSize = Unsafe.SizeOf<CmifDomainOutHeader>();
int implOutDataTotalSize = ImplOutDataTotalSize;
DebugUtil.Assert(outHeaderSize + implOutDataTotalSize + OutObjectsCount * sizeof(int) <= outRawData.Length);
outRawData = outRawData.Slice(outHeaderSize);
_outObjectIdsOffset = (response.DataWords.Length * sizeof(uint) - outRawData.Length) + implOutDataTotalSize;
return response;
}
public override void PrepareForErrorReply(scoped ref ServiceDispatchContext context, out Span<byte> outRawData, ServerMessageRuntimeMetadata runtimeMetadata)
{
_implProcessor.PrepareForErrorReply(ref context, out outRawData, runtimeMetadata);
int outHeaderSize = Unsafe.SizeOf<CmifDomainOutHeader>();
int implOutDataTotalSize = ImplOutDataTotalSize;
DebugUtil.Assert(outHeaderSize + implOutDataTotalSize <= outRawData.Length);
outRawData = outRawData.Slice(outHeaderSize);
_domain.UnreserveIds(new Span<int>(_reservedObjectIds).Slice(0, OutObjectsCount));
}
public override void SetOutObjects(scoped ref ServiceDispatchContext context, HipcMessageData response, Span<ServiceObjectHolder> outObjects)
{
int outObjectsCount = OutObjectsCount;
Span<int> objectIds = _reservedObjectIds;
for (int i = 0; i < outObjectsCount; i++)
{
if (outObjects[i] == null)
{
_domain.UnreserveIds(objectIds.Slice(i, 1));
objectIds[i] = 0;
continue;
}
_domain.RegisterObject(objectIds[i], outObjects[i]);
}
Span<int> outObjectIds = MemoryMarshal.Cast<byte, int>(MemoryMarshal.Cast<uint, byte>(response.DataWords).Slice(_outObjectIdsOffset));
for (int i = 0; i < outObjectsCount; i++)
{
outObjectIds[i] = objectIds[i];
}
}
}
}

View file

@ -0,0 +1,52 @@
using System;
namespace Ryujinx.Horizon.Sdk.Sf.Cmif
{
struct HandlesToClose
{
private int _handle0;
private int _handle1;
private int _handle2;
private int _handle3;
private int _handle4;
private int _handle5;
private int _handle6;
private int _handle7;
public int Count;
public int this[int index]
{
get
{
return index switch
{
0 => _handle0,
1 => _handle1,
2 => _handle2,
3 => _handle3,
4 => _handle4,
5 => _handle5,
6 => _handle6,
7 => _handle7,
_ => throw new IndexOutOfRangeException()
};
}
set
{
switch (index)
{
case 0: _handle0 = value; break;
case 1: _handle1 = value; break;
case 2: _handle2 = value; break;
case 3: _handle3 = value; break;
case 4: _handle4 = value; break;
case 5: _handle5 = value; break;
case 6: _handle6 = value; break;
case 7: _handle7 = value; break;
default: throw new IndexOutOfRangeException();
}
}
}
}
}

View file

@ -0,0 +1,11 @@
namespace Ryujinx.Horizon.Sdk.Sf.Cmif
{
class InlineContext
{
public static int Set(int newContext)
{
// TODO: Implement (will require FS changes???)
return newContext;
}
}
}

View file

@ -0,0 +1,17 @@
namespace Ryujinx.Horizon.Sdk.Sf.Cmif
{
struct PointerAndSize
{
public static PointerAndSize Empty => new PointerAndSize(0UL, 0UL);
public ulong Address { get; }
public ulong Size { get; }
public bool IsEmpty => Size == 0UL;
public PointerAndSize(ulong address, ulong size)
{
Address = address;
Size = size;
}
}
}

View file

@ -0,0 +1,19 @@
using System;
namespace Ryujinx.Horizon.Sdk.Sf.Cmif
{
struct ScopedInlineContextChange : IDisposable
{
private readonly int _previousContext;
public ScopedInlineContextChange(int newContext)
{
_previousContext = InlineContext.Set(newContext);
}
public void Dispose()
{
InlineContext.Set(_previousContext);
}
}
}

View file

@ -0,0 +1,15 @@
using Ryujinx.Horizon.Common;
using System;
namespace Ryujinx.Horizon.Sdk.Sf.Cmif
{
abstract class ServerDomainBase
{
public abstract Result ReserveIds(Span<int> outIds);
public abstract void UnreserveIds(ReadOnlySpan<int> ids);
public abstract void RegisterObject(int id, ServiceObjectHolder obj);
public abstract ServiceObjectHolder UnregisterObject(int id);
public abstract ServiceObjectHolder GetObject(int id);
}
}

View file

@ -0,0 +1,246 @@
using Ryujinx.Horizon.Common;
using System;
using System.Collections.Generic;
namespace Ryujinx.Horizon.Sdk.Sf.Cmif
{
class ServerDomainManager
{
private class EntryManager
{
public class Entry
{
public int Id { get; }
public Domain Owner { get; set; }
public ServiceObjectHolder Obj { get; set; }
public LinkedListNode<Entry> Node { get; set; }
public Entry(int id)
{
Id = id;
}
}
private readonly LinkedList<Entry> _freeList;
private readonly Entry[] _entries;
public EntryManager(int count)
{
_freeList = new LinkedList<Entry>();
_entries = new Entry[count];
for (int i = 0; i < count; i++)
{
_freeList.AddLast(_entries[i] = new Entry(i + 1));
}
}
public Entry AllocateEntry()
{
lock (_freeList)
{
if (_freeList.Count == 0)
{
return null;
}
var entry = _freeList.First.Value;
_freeList.RemoveFirst();
return entry;
}
}
public void FreeEntry(Entry entry)
{
lock (_freeList)
{
DebugUtil.Assert(entry.Owner == null);
DebugUtil.Assert(entry.Obj == null);
_freeList.AddFirst(entry);
}
}
public Entry GetEntry(int id)
{
if (id == 0)
{
return null;
}
int index = id - 1;
if ((uint)index >= (uint)_entries.Length)
{
return null;
}
return _entries[index];
}
}
private class Domain : DomainServiceObject, IDisposable
{
private readonly ServerDomainManager _manager;
private readonly LinkedList<EntryManager.Entry> _entries;
public Domain(ServerDomainManager manager)
{
_manager = manager;
_entries = new LinkedList<EntryManager.Entry>();
}
public override ServiceObjectHolder GetObject(int id)
{
var entry = _manager._entryManager.GetEntry(id);
if (entry == null)
{
return null;
}
lock (_manager._entryOwnerLock)
{
if (entry.Owner != this)
{
return null;
}
}
return entry.Obj.Clone();
}
public override ServerDomainBase GetServerDomain()
{
return this;
}
public override void RegisterObject(int id, ServiceObjectHolder obj)
{
var entry = _manager._entryManager.GetEntry(id);
DebugUtil.Assert(entry != null);
lock (_manager._entryOwnerLock)
{
DebugUtil.Assert(entry.Owner == null);
entry.Owner = this;
entry.Node = _entries.AddLast(entry);
}
entry.Obj = obj;
}
public override Result ReserveIds(Span<int> outIds)
{
for (int i = 0; i < outIds.Length; i++)
{
var entry = _manager._entryManager.AllocateEntry();
if (entry == null)
{
return SfResult.OutOfDomainEntries;
}
DebugUtil.Assert(entry.Owner == null);
outIds[i] = entry.Id;
}
return Result.Success;
}
public override ServiceObjectHolder UnregisterObject(int id)
{
var entry = _manager._entryManager.GetEntry(id);
if (entry == null)
{
return null;
}
ServiceObjectHolder obj;
lock (_manager._entryOwnerLock)
{
if (entry.Owner != this)
{
return null;
}
entry.Owner = null;
obj = entry.Obj;
entry.Obj = null;
_entries.Remove(entry.Node);
entry.Node = null;
}
_manager._entryManager.FreeEntry(entry);
return obj;
}
public override void UnreserveIds(ReadOnlySpan<int> ids)
{
for (int i = 0; i < ids.Length; i++)
{
var entry = _manager._entryManager.GetEntry(ids[i]);
DebugUtil.Assert(entry != null);
DebugUtil.Assert(entry.Owner == null);
_manager._entryManager.FreeEntry(entry);
}
}
public void Dispose()
{
foreach (var entry in _entries)
{
if (entry.Obj.ServiceObject is IDisposable disposableObj)
{
disposableObj.Dispose();
}
}
_manager.FreeDomain(this);
}
}
private readonly EntryManager _entryManager;
private readonly object _entryOwnerLock;
private readonly HashSet<Domain> _domains;
private int _maxDomains;
public ServerDomainManager(int entryCount, int maxDomains)
{
_entryManager = new EntryManager(entryCount);
_entryOwnerLock = new object();
_domains = new HashSet<Domain>();
_maxDomains = maxDomains;
}
public DomainServiceObject AllocateDomainServiceObject()
{
lock (_domains)
{
if (_domains.Count == _maxDomains)
{
return null;
}
var domain = new Domain(this);
_domains.Add(domain);
return domain;
}
}
public static void DestroyDomainServiceObject(DomainServiceObject obj)
{
((Domain)obj).Dispose();
}
private void FreeDomain(Domain domain)
{
lock (_domains)
{
_domains.Remove(domain);
}
}
}
}

View file

@ -0,0 +1,18 @@
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Sdk.Sf.Hipc;
using System;
namespace Ryujinx.Horizon.Sdk.Sf.Cmif
{
abstract class ServerMessageProcessor
{
public abstract void SetImplementationProcessor(ServerMessageProcessor impl);
public abstract ServerMessageRuntimeMetadata GetRuntimeMetadata();
public abstract Result PrepareForProcess(scoped ref ServiceDispatchContext context, ServerMessageRuntimeMetadata runtimeMetadata);
public abstract Result GetInObjects(Span<ServiceObjectHolder> inObjects);
public abstract HipcMessageData PrepareForReply(scoped ref ServiceDispatchContext context, out Span<byte> outRawData, ServerMessageRuntimeMetadata runtimeMetadata);
public abstract void PrepareForErrorReply(scoped ref ServiceDispatchContext context, out Span<byte> outRawData, ServerMessageRuntimeMetadata runtimeMetadata);
public abstract void SetOutObjects(scoped ref ServiceDispatchContext context, HipcMessageData response, Span<ServiceObjectHolder> outObjects);
}
}

View file

@ -0,0 +1,29 @@
namespace Ryujinx.Horizon.Sdk.Sf.Cmif
{
struct ServerMessageRuntimeMetadata
{
public ushort InDataSize { get; }
public ushort OutDataSize { get; }
public byte InHeadersSize { get; }
public byte OutHeadersSize { get; }
public byte InObjectsCount { get; }
public byte OutObjectsCount { get; }
public int UnfixedOutPointerSizeOffset => InDataSize + InHeadersSize + 0x10;
public ServerMessageRuntimeMetadata(
ushort inDataSize,
ushort outDataSize,
byte inHeadersSize,
byte outHeadersSize,
byte inObjectsCount,
byte outObjectsCount)
{
InDataSize = inDataSize;
OutDataSize = outDataSize;
InHeadersSize = inHeadersSize;
OutHeadersSize = outHeadersSize;
InObjectsCount = inObjectsCount;
OutObjectsCount = outObjectsCount;
}
}
}

View file

@ -0,0 +1,18 @@
using Ryujinx.Horizon.Sdk.Sf.Hipc;
using System;
namespace Ryujinx.Horizon.Sdk.Sf.Cmif
{
ref struct ServiceDispatchContext
{
public IServiceObject ServiceObject;
public ServerSessionManager Manager;
public ServerSession Session;
public ServerMessageProcessor Processor;
public HandlesToClose HandlesToClose;
public PointerAndSize PointerBuffer;
public ReadOnlySpan<byte> InMessageBuffer;
public Span<byte> OutMessageBuffer;
public HipcMessage Request;
}
}

View file

@ -0,0 +1,12 @@
namespace Ryujinx.Horizon.Sdk.Sf.Cmif
{
struct ServiceDispatchMeta
{
public ServiceDispatchTableBase DispatchTable { get; }
public ServiceDispatchMeta(ServiceDispatchTableBase dispatchTable)
{
DispatchTable = dispatchTable;
}
}
}

View file

@ -0,0 +1,33 @@
using Ryujinx.Horizon.Common;
using System;
using System.Collections.Generic;
namespace Ryujinx.Horizon.Sdk.Sf.Cmif
{
class ServiceDispatchTable : ServiceDispatchTableBase
{
private readonly string _objectName;
private readonly IReadOnlyDictionary<int, CommandHandler> _entries;
public ServiceDispatchTable(string objectName, IReadOnlyDictionary<int, CommandHandler> entries)
{
_objectName = objectName;
_entries = entries;
}
public override Result ProcessMessage(ref ServiceDispatchContext context, ReadOnlySpan<byte> inRawData)
{
return ProcessMessageImpl(ref context, inRawData, _entries, _objectName);
}
public static ServiceDispatchTableBase Create(IServiceObject instance)
{
if (instance is DomainServiceObject)
{
return new DomainServiceObjectDispatchTable();
}
return new ServiceDispatchTable(instance.GetType().Name, instance.GetCommandHandlers());
}
}
}

View file

@ -0,0 +1,90 @@
using Ryujinx.Common.Logging;
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Sdk.Sf.Hipc;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Sf.Cmif
{
abstract class ServiceDispatchTableBase
{
private const uint MaxCmifVersion = 1;
public abstract Result ProcessMessage(ref ServiceDispatchContext context, ReadOnlySpan<byte> inRawData);
protected Result ProcessMessageImpl(ref ServiceDispatchContext context, ReadOnlySpan<byte> inRawData, IReadOnlyDictionary<int, CommandHandler> entries, string objectName)
{
if (inRawData.Length < Unsafe.SizeOf<CmifInHeader>())
{
Logger.Warning?.Print(LogClass.KernelIpc, $"Request message size 0x{inRawData.Length:X} is invalid");
return SfResult.InvalidHeaderSize;
}
CmifInHeader inHeader = MemoryMarshal.Cast<byte, CmifInHeader>(inRawData)[0];
if (inHeader.Magic != CmifMessage.CmifInHeaderMagic || inHeader.Version > MaxCmifVersion)
{
Logger.Warning?.Print(LogClass.KernelIpc, $"Request message header magic value 0x{inHeader.Magic:X} is invalid");
return SfResult.InvalidInHeader;
}
ReadOnlySpan<byte> inMessageRawData = inRawData[Unsafe.SizeOf<CmifInHeader>()..];
uint commandId = inHeader.CommandId;
var outHeader = Span<CmifOutHeader>.Empty;
if (!entries.TryGetValue((int)commandId, out var commandHandler))
{
Logger.Warning?.Print(LogClass.KernelIpc, $"{objectName} command ID 0x{commandId:X} is not implemented");
if (HorizonStatic.Options.IgnoreMissingServices)
{
// If ignore missing services is enabled, just pretend that everything is fine.
var response = PrepareForStubReply(ref context, out Span<byte> outRawData);
CommandHandler.GetCmifOutHeaderPointer(ref outHeader, ref outRawData);
outHeader[0] = new CmifOutHeader() { Magic = CmifMessage.CmifOutHeaderMagic, Result = Result.Success };
return Result.Success;
}
return SfResult.UnknownCommandId;
}
Logger.Trace?.Print(LogClass.KernelIpc, $"{objectName}.{commandHandler.MethodName} called");
Result commandResult = commandHandler.Invoke(ref outHeader, ref context, inMessageRawData);
if (commandResult.Module == SfResult.ModuleId ||
commandResult.Module == HipcResult.ModuleId)
{
Logger.Warning?.Print(LogClass.KernelIpc, $"{commandHandler.MethodName} returned error {commandResult}");
}
if (SfResult.RequestContextChanged(commandResult))
{
return commandResult;
}
if (outHeader.IsEmpty)
{
commandResult.AbortOnSuccess();
return commandResult;
}
outHeader[0] = new CmifOutHeader() { Magic = CmifMessage.CmifOutHeaderMagic, Result = commandResult };
return Result.Success;
}
private static HipcMessageData PrepareForStubReply(scoped ref ServiceDispatchContext context, out Span<byte> outRawData)
{
var response = HipcMessage.WriteResponse(context.OutMessageBuffer, 0, 0x20 / sizeof(uint), 0, 0);
outRawData = MemoryMarshal.Cast<uint, byte>(response.DataWords);
return response;
}
}
}

View file

@ -0,0 +1,34 @@
using Ryujinx.Horizon.Common;
using System;
namespace Ryujinx.Horizon.Sdk.Sf.Cmif
{
class ServiceObjectHolder
{
public IServiceObject ServiceObject { get; }
private readonly ServiceDispatchMeta _dispatchMeta;
public ServiceObjectHolder(ServiceObjectHolder objectHolder)
{
ServiceObject = objectHolder.ServiceObject;
_dispatchMeta = objectHolder._dispatchMeta;
}
public ServiceObjectHolder(IServiceObject serviceImpl)
{
ServiceObject = serviceImpl;
_dispatchMeta = new ServiceDispatchMeta(ServiceDispatchTable.Create(serviceImpl));
}
public ServiceObjectHolder Clone()
{
return new ServiceObjectHolder(this);
}
public Result ProcessMessage(ref ServiceDispatchContext context, ReadOnlySpan<byte> inRawData)
{
return _dispatchMeta.DispatchTable.ProcessMessage(ref context, inRawData);
}
}
}