diff options
Diffstat (limited to 'src/Ryujinx.Horizon/Sdk/Sf/Cmif')
26 files changed, 1077 insertions, 0 deletions
diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainInHeader.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainInHeader.cs new file mode 100644 index 00000000..beaff613 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainInHeader.cs @@ -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; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainOutHeader.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainOutHeader.cs new file mode 100644 index 00000000..2086d24c --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainOutHeader.cs @@ -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 + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainRequestType.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainRequestType.cs new file mode 100644 index 00000000..1a02e082 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainRequestType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Horizon.Sdk.Sf.Cmif +{ + enum CmifDomainRequestType : byte + { + Invalid = 0, + SendMessage = 1, + Close = 2 + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifInHeader.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifInHeader.cs new file mode 100644 index 00000000..55b859fc --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifInHeader.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Horizon.Sdk.Sf.Cmif +{ + struct CmifInHeader + { + public uint Magic; + public uint Version; + public uint CommandId; + public uint Token; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifMessage.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifMessage.cs new file mode 100644 index 00000000..0d23d33b --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifMessage.cs @@ -0,0 +1,135 @@ +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() + { + 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[(Unsafe.SizeOf<CmifDomainInHeader>() / sizeof(uint))..]; + + request.Objects = data[((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)[Unsafe.SizeOf<CmifInHeader>()..]; + + int paddingSizeBefore = (rawDataSizeInWords - request.Hipc.DataWords.Length) * sizeof(uint); + + Span<byte> outPointerTable = MemoryMarshal.Cast<uint, byte>(request.Hipc.DataWords)[(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(input); + + Span<byte> data = MemoryMarshal.Cast<uint, byte>(responseMessage.Data.DataWords); + Span<uint> objects = Span<uint>.Empty; + + if (isDomain) + { + data = data[Unsafe.SizeOf<CmifDomainOutHeader>()..]; + objects = MemoryMarshal.Cast<byte, uint>(data[(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[Unsafe.SizeOf<CmifOutHeader>()..], + Objects = objects, + CopyHandles = responseMessage.Data.CopyHandles, + MoveHandles = responseMessage.Data.MoveHandles + }; + + return Result.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifOutHeader.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifOutHeader.cs new file mode 100644 index 00000000..00b9d2bd --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifOutHeader.cs @@ -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 + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequest.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequest.cs new file mode 100644 index 00000000..e44a84ec --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequest.cs @@ -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; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequestFormat.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequestFormat.cs new file mode 100644 index 00000000..592f11f4 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequestFormat.cs @@ -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 + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifResponse.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifResponse.cs new file mode 100644 index 00000000..2ff31eb6 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifResponse.cs @@ -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; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CommandType.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CommandType.cs new file mode 100644 index 00000000..82c0648b --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CommandType.cs @@ -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 + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObject.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObject.cs new file mode 100644 index 00000000..14839687 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObject.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.Horizon.Sdk.Sf.Cmif +{ + abstract partial class DomainServiceObject : ServerDomainBase, IServiceObject + { + public abstract ServerDomainBase GetServerDomain(); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObjectDispatchTable.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObjectDispatchTable.cs new file mode 100644 index 00000000..b0b4498d --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObjectDispatchTable.cs @@ -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[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[..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; + } + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObjectProcessor.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObjectProcessor.cs new file mode 100644 index 00000000..796b8a78 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObjectProcessor.cs @@ -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)[..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[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[outHeaderSize..]; + + _domain.UnreserveIds(new Span<int>(_reservedObjectIds)[..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)[_outObjectIdsOffset..]); + + for (int i = 0; i < outObjectsCount; i++) + { + outObjectIds[i] = objectIds[i]; + } + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/HandlesToClose.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/HandlesToClose.cs new file mode 100644 index 00000000..0f3b259a --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/HandlesToClose.cs @@ -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(); + } + } + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/InlineContext.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/InlineContext.cs new file mode 100644 index 00000000..ddb6943f --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/InlineContext.cs @@ -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; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/PointerAndSize.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/PointerAndSize.cs new file mode 100644 index 00000000..ad0e1824 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/PointerAndSize.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Horizon.Sdk.Sf.Cmif +{ + struct PointerAndSize + { + public static PointerAndSize Empty => new(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; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ScopedInlineContextChange.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ScopedInlineContextChange.cs new file mode 100644 index 00000000..eabe544f --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ScopedInlineContextChange.cs @@ -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); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainBase.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainBase.cs new file mode 100644 index 00000000..f38fa030 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainBase.cs @@ -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); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainManager.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainManager.cs new file mode 100644 index 00000000..62ee2738 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainManager.cs @@ -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); + } + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerMessageProcessor.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerMessageProcessor.cs new file mode 100644 index 00000000..e7650238 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerMessageProcessor.cs @@ -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); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerMessageRuntimeMetadata.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerMessageRuntimeMetadata.cs new file mode 100644 index 00000000..6a92e8d5 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerMessageRuntimeMetadata.cs @@ -0,0 +1,30 @@ +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; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchContext.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchContext.cs new file mode 100644 index 00000000..31be810d --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchContext.cs @@ -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; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchMeta.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchMeta.cs new file mode 100644 index 00000000..7fbd8eb8 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchMeta.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Horizon.Sdk.Sf.Cmif +{ + struct ServiceDispatchMeta + { + public ServiceDispatchTableBase DispatchTable { get; } + + public ServiceDispatchMeta(ServiceDispatchTableBase dispatchTable) + { + DispatchTable = dispatchTable; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchTable.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchTable.cs new file mode 100644 index 00000000..21b342df --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchTable.cs @@ -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()); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchTableBase.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchTableBase.cs new file mode 100644 index 00000000..81600067 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchTableBase.cs @@ -0,0 +1,94 @@ +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)) + { + if (HorizonStatic.Options.IgnoreMissingServices) + { + // If ignore missing services is enabled, just pretend that everything is fine. + PrepareForStubReply(ref context, out Span<byte> outRawData); + CommandHandler.GetCmifOutHeaderPointer(ref outHeader, ref outRawData); + outHeader[0] = new CmifOutHeader() { Magic = CmifMessage.CmifOutHeaderMagic, Result = Result.Success }; + + Logger.Warning?.Print(LogClass.Service, $"Missing service {objectName} (command ID: {commandId}) ignored"); + + return Result.Success; + } + else if (HorizonStatic.Options.ThrowOnInvalidCommandIds) + { + throw new NotImplementedException($"{objectName} command ID: {commandId} is not implemented"); + } + + 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 void 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); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceObjectHolder.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceObjectHolder.cs new file mode 100644 index 00000000..6e87e340 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceObjectHolder.cs @@ -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); + } + } +} |
