From 08831eecf77cedd3c4192ebab5a9c485fb15d51e Mon Sep 17 00:00:00 2001 From: gdkchan <gab.dark.100@gmail.com> Date: Wed, 4 Jan 2023 19:15:45 -0300 Subject: 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 --- Ryujinx.Horizon/HeapAllocator.cs | 143 +++++++ Ryujinx.Horizon/HorizonOptions.cs | 12 + Ryujinx.Horizon/HorizonStatic.cs | 44 +++ Ryujinx.Horizon/IService.cs | 7 + Ryujinx.Horizon/LogManager/LmIpcServer.cs | 54 +++ Ryujinx.Horizon/LogManager/LmLog.cs | 19 + Ryujinx.Horizon/LogManager/LmLogger.cs | 139 +++++++ Ryujinx.Horizon/LogManager/LmMain.cs | 14 + Ryujinx.Horizon/Ryujinx.Horizon.csproj | 14 + Ryujinx.Horizon/Sdk/DebugUtil.cs | 12 + Ryujinx.Horizon/Sdk/Diag/LogSeverity.cs | 11 + Ryujinx.Horizon/Sdk/Lm/LogDataChunkKey.cs | 19 + Ryujinx.Horizon/Sdk/Lm/LogDestination.cs | 14 + Ryujinx.Horizon/Sdk/Lm/LogPacketFlags.cs | 12 + Ryujinx.Horizon/Sdk/Lm/LogPacketHeader.cs | 15 + Ryujinx.Horizon/Sdk/OsTypes/Event.cs | 61 +++ Ryujinx.Horizon/Sdk/OsTypes/EventClearMode.cs | 8 + Ryujinx.Horizon/Sdk/OsTypes/EventType.cs | 15 + .../Sdk/OsTypes/Impl/InterProcessEvent.cs | 89 +++++ .../Sdk/OsTypes/Impl/InterProcessEventImpl.cs | 136 +++++++ Ryujinx.Horizon/Sdk/OsTypes/Impl/MultiWaitImpl.cs | 250 ++++++++++++ Ryujinx.Horizon/Sdk/OsTypes/InitializationState.cs | 8 + .../Sdk/OsTypes/InterProcessEventType.cs | 27 ++ Ryujinx.Horizon/Sdk/OsTypes/MultiWait.cs | 43 +++ Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolder.cs | 16 + Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderBase.cs | 39 ++ .../Sdk/OsTypes/MultiWaitHolderOfEvent.cs | 45 +++ .../Sdk/OsTypes/MultiWaitHolderOfHandle.cs | 14 + Ryujinx.Horizon/Sdk/OsTypes/OsEvent.cs | 130 +++++++ Ryujinx.Horizon/Sdk/OsTypes/OsMultiWait.cs | 10 + Ryujinx.Horizon/Sdk/OsTypes/OsProcessHandle.cs | 33 ++ Ryujinx.Horizon/Sdk/OsTypes/OsResult.cs | 11 + Ryujinx.Horizon/Sdk/OsTypes/OsSystemEvent.cs | 85 +++++ Ryujinx.Horizon/Sdk/OsTypes/OsThreadManager.cs | 10 + Ryujinx.Horizon/Sdk/OsTypes/SystemEventType.cs | 17 + Ryujinx.Horizon/Sdk/OsTypes/TriBool.cs | 9 + Ryujinx.Horizon/Sdk/ServiceUtil.cs | 38 ++ Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainInHeader.cs | 12 + Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainOutHeader.cs | 12 + .../Sdk/Sf/Cmif/CmifDomainRequestType.cs | 9 + Ryujinx.Horizon/Sdk/Sf/Cmif/CmifInHeader.cs | 10 + Ryujinx.Horizon/Sdk/Sf/Cmif/CmifMessage.cs | 128 +++++++ Ryujinx.Horizon/Sdk/Sf/Cmif/CmifOutHeader.cs | 14 + Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequest.cs | 14 + Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequestFormat.cs | 24 ++ Ryujinx.Horizon/Sdk/Sf/Cmif/CmifResponse.cs | 12 + Ryujinx.Horizon/Sdk/Sf/Cmif/CommandType.cs | 14 + Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObject.cs | 7 + .../Sf/Cmif/DomainServiceObjectDispatchTable.cs | 75 ++++ .../Sdk/Sf/Cmif/DomainServiceObjectProcessor.cs | 140 +++++++ Ryujinx.Horizon/Sdk/Sf/Cmif/HandlesToClose.cs | 52 +++ Ryujinx.Horizon/Sdk/Sf/Cmif/InlineContext.cs | 11 + Ryujinx.Horizon/Sdk/Sf/Cmif/PointerAndSize.cs | 17 + .../Sdk/Sf/Cmif/ScopedInlineContextChange.cs | 19 + Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainBase.cs | 15 + Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainManager.cs | 246 ++++++++++++ .../Sdk/Sf/Cmif/ServerMessageProcessor.cs | 18 + .../Sdk/Sf/Cmif/ServerMessageRuntimeMetadata.cs | 29 ++ .../Sdk/Sf/Cmif/ServiceDispatchContext.cs | 18 + Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchMeta.cs | 12 + .../Sdk/Sf/Cmif/ServiceDispatchTable.cs | 33 ++ .../Sdk/Sf/Cmif/ServiceDispatchTableBase.cs | 90 +++++ Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceObjectHolder.cs | 34 ++ Ryujinx.Horizon/Sdk/Sf/CmifCommandAttribute.cs | 15 + Ryujinx.Horizon/Sdk/Sf/CommandArg.cs | 56 +++ Ryujinx.Horizon/Sdk/Sf/CommandArgAttributes.cs | 38 ++ Ryujinx.Horizon/Sdk/Sf/CommandHandler.cs | 57 +++ Ryujinx.Horizon/Sdk/Sf/CommandSerialization.cs | 68 ++++ Ryujinx.Horizon/Sdk/Sf/Hipc/Api.cs | 89 +++++ Ryujinx.Horizon/Sdk/Sf/Hipc/Header.cs | 65 ++++ .../Sdk/Sf/Hipc/HipcBufferDescriptor.cs | 15 + Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferFlags.cs | 17 + Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferMode.cs | 10 + Ryujinx.Horizon/Sdk/Sf/Hipc/HipcManager.cs | 115 ++++++ Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessage.cs | 222 +++++++++++ Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessageData.cs | 16 + Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMetadata.cs | 16 + .../Sdk/Sf/Hipc/HipcReceiveListEntry.cs | 14 + Ryujinx.Horizon/Sdk/Sf/Hipc/HipcResult.cs | 22 ++ .../Sdk/Sf/Hipc/HipcStaticDescriptor.cs | 22 ++ Ryujinx.Horizon/Sdk/Sf/Hipc/ManagerOptions.cs | 20 + Ryujinx.Horizon/Sdk/Sf/Hipc/ReceiveResult.cs | 9 + Ryujinx.Horizon/Sdk/Sf/Hipc/Server.cs | 36 ++ .../Sdk/Sf/Hipc/ServerDomainSessionManager.cs | 23 ++ Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManager.cs | 198 ++++++++++ Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManagerBase.cs | 307 +++++++++++++++ Ryujinx.Horizon/Sdk/Sf/Hipc/ServerSession.cs | 23 ++ .../Sdk/Sf/Hipc/ServerSessionManager.cs | 335 ++++++++++++++++ Ryujinx.Horizon/Sdk/Sf/Hipc/SpecialHeader.cs | 27 ++ Ryujinx.Horizon/Sdk/Sf/HipcCommandProcessor.cs | 421 +++++++++++++++++++++ Ryujinx.Horizon/Sdk/Sf/IServiceObject.cs | 9 + Ryujinx.Horizon/Sdk/Sf/RawDataOffsetCalculator.cs | 51 +++ Ryujinx.Horizon/Sdk/Sf/SfResult.cs | 31 ++ Ryujinx.Horizon/Sdk/Sm/ServiceName.cs | 98 +++++ Ryujinx.Horizon/Sdk/Sm/SmApi.cs | 107 ++++++ Ryujinx.Horizon/ServiceEntry.cs | 25 ++ Ryujinx.Horizon/ServiceTable.cs | 22 ++ Ryujinx.Horizon/Sm/Impl/ServiceInfo.cs | 20 + Ryujinx.Horizon/Sm/Impl/ServiceManager.cs | 197 ++++++++++ Ryujinx.Horizon/Sm/ManagerService.cs | 8 + Ryujinx.Horizon/Sm/SmMain.cs | 30 ++ Ryujinx.Horizon/Sm/SmResult.cs | 19 + Ryujinx.Horizon/Sm/UserService.cs | 66 ++++ 103 files changed, 5627 insertions(+) create mode 100644 Ryujinx.Horizon/HeapAllocator.cs create mode 100644 Ryujinx.Horizon/HorizonOptions.cs create mode 100644 Ryujinx.Horizon/HorizonStatic.cs create mode 100644 Ryujinx.Horizon/IService.cs create mode 100644 Ryujinx.Horizon/LogManager/LmIpcServer.cs create mode 100644 Ryujinx.Horizon/LogManager/LmLog.cs create mode 100644 Ryujinx.Horizon/LogManager/LmLogger.cs create mode 100644 Ryujinx.Horizon/LogManager/LmMain.cs create mode 100644 Ryujinx.Horizon/Ryujinx.Horizon.csproj create mode 100644 Ryujinx.Horizon/Sdk/DebugUtil.cs create mode 100644 Ryujinx.Horizon/Sdk/Diag/LogSeverity.cs create mode 100644 Ryujinx.Horizon/Sdk/Lm/LogDataChunkKey.cs create mode 100644 Ryujinx.Horizon/Sdk/Lm/LogDestination.cs create mode 100644 Ryujinx.Horizon/Sdk/Lm/LogPacketFlags.cs create mode 100644 Ryujinx.Horizon/Sdk/Lm/LogPacketHeader.cs create mode 100644 Ryujinx.Horizon/Sdk/OsTypes/Event.cs create mode 100644 Ryujinx.Horizon/Sdk/OsTypes/EventClearMode.cs create mode 100644 Ryujinx.Horizon/Sdk/OsTypes/EventType.cs create mode 100644 Ryujinx.Horizon/Sdk/OsTypes/Impl/InterProcessEvent.cs create mode 100644 Ryujinx.Horizon/Sdk/OsTypes/Impl/InterProcessEventImpl.cs create mode 100644 Ryujinx.Horizon/Sdk/OsTypes/Impl/MultiWaitImpl.cs create mode 100644 Ryujinx.Horizon/Sdk/OsTypes/InitializationState.cs create mode 100644 Ryujinx.Horizon/Sdk/OsTypes/InterProcessEventType.cs create mode 100644 Ryujinx.Horizon/Sdk/OsTypes/MultiWait.cs create mode 100644 Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolder.cs create mode 100644 Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderBase.cs create mode 100644 Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderOfEvent.cs create mode 100644 Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderOfHandle.cs create mode 100644 Ryujinx.Horizon/Sdk/OsTypes/OsEvent.cs create mode 100644 Ryujinx.Horizon/Sdk/OsTypes/OsMultiWait.cs create mode 100644 Ryujinx.Horizon/Sdk/OsTypes/OsProcessHandle.cs create mode 100644 Ryujinx.Horizon/Sdk/OsTypes/OsResult.cs create mode 100644 Ryujinx.Horizon/Sdk/OsTypes/OsSystemEvent.cs create mode 100644 Ryujinx.Horizon/Sdk/OsTypes/OsThreadManager.cs create mode 100644 Ryujinx.Horizon/Sdk/OsTypes/SystemEventType.cs create mode 100644 Ryujinx.Horizon/Sdk/OsTypes/TriBool.cs create mode 100644 Ryujinx.Horizon/Sdk/ServiceUtil.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainInHeader.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainOutHeader.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainRequestType.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/CmifInHeader.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/CmifMessage.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/CmifOutHeader.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequest.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequestFormat.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/CmifResponse.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/CommandType.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObject.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObjectDispatchTable.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObjectProcessor.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/HandlesToClose.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/InlineContext.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/PointerAndSize.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/ScopedInlineContextChange.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainBase.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainManager.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/ServerMessageProcessor.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/ServerMessageRuntimeMetadata.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchContext.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchMeta.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchTable.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchTableBase.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceObjectHolder.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/CmifCommandAttribute.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/CommandArg.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/CommandArgAttributes.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/CommandHandler.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/CommandSerialization.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Hipc/Api.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Hipc/Header.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferDescriptor.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferFlags.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferMode.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Hipc/HipcManager.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessage.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessageData.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMetadata.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Hipc/HipcReceiveListEntry.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Hipc/HipcResult.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Hipc/HipcStaticDescriptor.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Hipc/ManagerOptions.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Hipc/ReceiveResult.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Hipc/Server.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Hipc/ServerDomainSessionManager.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManager.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManagerBase.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Hipc/ServerSession.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Hipc/ServerSessionManager.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/Hipc/SpecialHeader.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/HipcCommandProcessor.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/IServiceObject.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/RawDataOffsetCalculator.cs create mode 100644 Ryujinx.Horizon/Sdk/Sf/SfResult.cs create mode 100644 Ryujinx.Horizon/Sdk/Sm/ServiceName.cs create mode 100644 Ryujinx.Horizon/Sdk/Sm/SmApi.cs create mode 100644 Ryujinx.Horizon/ServiceEntry.cs create mode 100644 Ryujinx.Horizon/ServiceTable.cs create mode 100644 Ryujinx.Horizon/Sm/Impl/ServiceInfo.cs create mode 100644 Ryujinx.Horizon/Sm/Impl/ServiceManager.cs create mode 100644 Ryujinx.Horizon/Sm/ManagerService.cs create mode 100644 Ryujinx.Horizon/Sm/SmMain.cs create mode 100644 Ryujinx.Horizon/Sm/SmResult.cs create mode 100644 Ryujinx.Horizon/Sm/UserService.cs (limited to 'Ryujinx.Horizon') diff --git a/Ryujinx.Horizon/HeapAllocator.cs b/Ryujinx.Horizon/HeapAllocator.cs new file mode 100644 index 00000000..867c9677 --- /dev/null +++ b/Ryujinx.Horizon/HeapAllocator.cs @@ -0,0 +1,143 @@ +using Ryujinx.Common; +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Ryujinx.Horizon +{ + class HeapAllocator + { + private const ulong InvalidAddress = ulong.MaxValue; + + private struct Range : IComparable<Range> + { + public ulong Offset { get; } + public ulong Size { get; } + + public Range(ulong offset, ulong size) + { + Offset = offset; + Size = size; + } + + public int CompareTo(Range other) + { + return Offset.CompareTo(other.Offset); + } + } + + private readonly List<Range> _freeRanges; + private ulong _currentHeapSize; + + public HeapAllocator() + { + _freeRanges = new List<Range>(); + _currentHeapSize = 0; + } + + public ulong Allocate(ulong size, ulong alignment = 1UL) + { + ulong address = AllocateImpl(size, alignment); + + if (address == InvalidAddress) + { + ExpandHeap(size + alignment - 1UL); + + address = AllocateImpl(size, alignment); + + Debug.Assert(address != InvalidAddress); + } + + return address; + } + + private void ExpandHeap(ulong expansionSize) + { + ulong oldHeapSize = _currentHeapSize; + ulong newHeapSize = BitUtils.AlignUp(oldHeapSize + expansionSize, 0x200000UL); + + _currentHeapSize = newHeapSize; + + HorizonStatic.Syscall.SetHeapSize(out ulong heapAddress, newHeapSize).AbortOnFailure(); + + Free(heapAddress + oldHeapSize, newHeapSize - oldHeapSize); + } + + private ulong AllocateImpl(ulong size, ulong alignment) + { + for (int i = 0; i < _freeRanges.Count; i++) + { + var range = _freeRanges[i]; + + ulong alignedOffset = BitUtils.AlignUp(range.Offset, alignment); + ulong sizeDelta = alignedOffset - range.Offset; + ulong usableSize = range.Size - sizeDelta; + + if (sizeDelta < range.Size && usableSize >= size) + { + _freeRanges.RemoveAt(i); + + if (sizeDelta != 0) + { + InsertFreeRange(range.Offset, sizeDelta); + } + + ulong endOffset = range.Offset + range.Size; + ulong remainingSize = endOffset - (alignedOffset + size); + if (remainingSize != 0) + { + InsertFreeRange(endOffset - remainingSize, remainingSize); + } + + return alignedOffset; + } + } + + return InvalidAddress; + } + + public void Free(ulong offset, ulong size) + { + InsertFreeRangeComingled(offset, size); + } + + private void InsertFreeRange(ulong offset, ulong size) + { + var range = new Range(offset, size); + int index = _freeRanges.BinarySearch(range); + if (index < 0) + { + index = ~index; + } + + _freeRanges.Insert(index, range); + } + + private void InsertFreeRangeComingled(ulong offset, ulong size) + { + ulong endOffset = offset + size; + var range = new Range(offset, size); + int index = _freeRanges.BinarySearch(range); + if (index < 0) + { + index = ~index; + } + + if (index < _freeRanges.Count && _freeRanges[index].Offset == endOffset) + { + endOffset = _freeRanges[index].Offset + _freeRanges[index].Size; + _freeRanges.RemoveAt(index); + } + + if (index > 0 && _freeRanges[index - 1].Offset + _freeRanges[index - 1].Size == offset) + { + offset = _freeRanges[index - 1].Offset; + _freeRanges.RemoveAt(--index); + } + + range = new Range(offset, endOffset - offset); + + _freeRanges.Insert(index, range); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Horizon/HorizonOptions.cs b/Ryujinx.Horizon/HorizonOptions.cs new file mode 100644 index 00000000..b1567c6a --- /dev/null +++ b/Ryujinx.Horizon/HorizonOptions.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Horizon +{ + public struct HorizonOptions + { + public bool IgnoreMissingServices { get; } + + public HorizonOptions(bool ignoreMissingServices) + { + IgnoreMissingServices = ignoreMissingServices; + } + } +} diff --git a/Ryujinx.Horizon/HorizonStatic.cs b/Ryujinx.Horizon/HorizonStatic.cs new file mode 100644 index 00000000..1e483cd4 --- /dev/null +++ b/Ryujinx.Horizon/HorizonStatic.cs @@ -0,0 +1,44 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Memory; +using System; + +namespace Ryujinx.Horizon +{ + static class HorizonStatic + { + [ThreadStatic] + private static HorizonOptions _options; + + [ThreadStatic] + private static ISyscallApi _syscall; + + [ThreadStatic] + private static IVirtualMemoryManager _addressSpace; + + [ThreadStatic] + private static IThreadContext _threadContext; + + [ThreadStatic] + private static int _threadHandle; + + public static HorizonOptions Options => _options; + public static ISyscallApi Syscall => _syscall; + public static IVirtualMemoryManager AddressSpace => _addressSpace; + public static IThreadContext ThreadContext => _threadContext; + public static int CurrentThreadHandle => _threadHandle; + + public static void Register( + HorizonOptions options, + ISyscallApi syscallApi, + IVirtualMemoryManager addressSpace, + IThreadContext threadContext, + int threadHandle) + { + _options = options; + _syscall = syscallApi; + _addressSpace = addressSpace; + _threadContext = threadContext; + _threadHandle = threadHandle; + } + } +} diff --git a/Ryujinx.Horizon/IService.cs b/Ryujinx.Horizon/IService.cs new file mode 100644 index 00000000..67c12cef --- /dev/null +++ b/Ryujinx.Horizon/IService.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.Horizon +{ + interface IService + { + abstract static void Main(); + } +} diff --git a/Ryujinx.Horizon/LogManager/LmIpcServer.cs b/Ryujinx.Horizon/LogManager/LmIpcServer.cs new file mode 100644 index 00000000..7b757fe9 --- /dev/null +++ b/Ryujinx.Horizon/LogManager/LmIpcServer.cs @@ -0,0 +1,54 @@ +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Sm; +using Ryujinx.Horizon.Sm; + +namespace Ryujinx.Horizon.LogManager +{ + class LmIpcServer + { + private const int LogMaxSessionsCount = 42; + + private const int PointerBufferSize = 0x400; + private const int MaxDomains = 31; + private const int MaxDomainObjects = 61; + + private const int MaxPortsCount = 1; + + private static readonly ManagerOptions _logManagerOptions = new ManagerOptions( + PointerBufferSize, + MaxDomains, + MaxDomainObjects, + false); + + private static readonly ServiceName _logServiceName = ServiceName.Encode("lm"); + + private SmApi _sm; + private ServerManager _serverManager; + + private LmLog _logServiceObject; + + public void Initialize() + { + HeapAllocator allocator = new HeapAllocator(); + + _sm = new SmApi(); + _sm.Initialize().AbortOnFailure(); + + _serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _logManagerOptions, LogMaxSessionsCount); + + _logServiceObject = new LmLog(); + + _serverManager.RegisterObjectForServer(_logServiceObject, _logServiceName, LogMaxSessionsCount); + } + + public void ServiceRequests() + { + _serverManager.ServiceRequests(); + } + + public void Shutdown() + { + _serverManager.Dispose(); + } + } +} diff --git a/Ryujinx.Horizon/LogManager/LmLog.cs b/Ryujinx.Horizon/LogManager/LmLog.cs new file mode 100644 index 00000000..772465c4 --- /dev/null +++ b/Ryujinx.Horizon/LogManager/LmLog.cs @@ -0,0 +1,19 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Lm; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.LogManager +{ + partial class LmLog : IServiceObject + { + public LogDestination LogDestination { get; set; } = LogDestination.TargetManager; + + [CmifCommand(0)] + public Result OpenLogger(out LmLogger logger, [ClientProcessId] ulong clientProcessId) + { + logger = new LmLogger(this, clientProcessId); + + return Result.Success; + } + } +} diff --git a/Ryujinx.Horizon/LogManager/LmLogger.cs b/Ryujinx.Horizon/LogManager/LmLogger.cs new file mode 100644 index 00000000..461776cd --- /dev/null +++ b/Ryujinx.Horizon/LogManager/LmLogger.cs @@ -0,0 +1,139 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Common.Memory; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Lm; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.Horizon.LogManager +{ + partial class LmLogger : IServiceObject + { + private readonly LmLog _log; + private readonly ulong _clientProcessId; + + public LmLogger(LmLog log, ulong clientProcessId) + { + _log = log; + _clientProcessId = clientProcessId; + } + + [CmifCommand(0)] + public Result Log([Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] Span<byte> message) + { + if (!SetProcessId(message, _clientProcessId)) + { + return Result.Success; + } + + Logger.Guest?.Print(LogClass.ServiceLm, LogImpl(message)); + + return Result.Success; + } + + [CmifCommand(1)] + public Result SetDestination(LogDestination destination) + { + _log.LogDestination = destination; + + return Result.Success; + } + + private static bool SetProcessId(Span<byte> message, ulong processId) + { + ref LogPacketHeader header = ref MemoryMarshal.Cast<byte, LogPacketHeader>(message)[0]; + + uint expectedMessageSize = (uint)Unsafe.SizeOf<LogPacketHeader>() + header.PayloadSize; + + if (expectedMessageSize != (uint)message.Length) + { + Logger.Warning?.Print(LogClass.ServiceLm, $"Invalid message size (expected 0x{expectedMessageSize:X} but got 0x{message.Length:X})."); + + return false; + } + + header.ProcessId = processId; + + return true; + } + + private static string LogImpl(ReadOnlySpan<byte> message) + { + SpanReader reader = new SpanReader(message); + + LogPacketHeader header = reader.Read<LogPacketHeader>(); + + StringBuilder sb = new StringBuilder(); + + sb.AppendLine($"Guest Log:\n Log level: {header.Severity}"); + + while (reader.Length > 0) + { + int type = ReadUleb128(ref reader); + int size = ReadUleb128(ref reader); + + LogDataChunkKey field = (LogDataChunkKey)type; + + string fieldStr = string.Empty; + + if (field == LogDataChunkKey.Start) + { + reader.Skip(size); + + continue; + } + else if (field == LogDataChunkKey.Stop) + { + break; + } + else if (field == LogDataChunkKey.Line) + { + fieldStr = $"{field}: {reader.Read<int>()}"; + } + else if (field == LogDataChunkKey.DropCount) + { + fieldStr = $"{field}: {reader.Read<long>()}"; + } + else if (field == LogDataChunkKey.Time) + { + fieldStr = $"{field}: {reader.Read<long>()}s"; + } + else if (field < LogDataChunkKey.Count) + { + fieldStr = $"{field}: '{Encoding.UTF8.GetString(reader.GetSpan(size)).TrimEnd()}'"; + } + else + { + fieldStr = $"Field{field}: '{Encoding.UTF8.GetString(reader.GetSpan(size)).TrimEnd()}'"; + } + + sb.AppendLine($" {fieldStr}"); + } + + return sb.ToString(); + } + + private static int ReadUleb128(ref SpanReader reader) + { + int result = 0; + int count = 0; + + byte encoded; + + do + { + encoded = reader.Read<byte>(); + + result += (encoded & 0x7F) << (7 * count); + + count++; + } while ((encoded & 0x80) != 0); + + return result; + } + } +} diff --git a/Ryujinx.Horizon/LogManager/LmMain.cs b/Ryujinx.Horizon/LogManager/LmMain.cs new file mode 100644 index 00000000..8c0262ac --- /dev/null +++ b/Ryujinx.Horizon/LogManager/LmMain.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Horizon.LogManager +{ + class LmMain : IService + { + public static void Main() + { + LmIpcServer ipcServer = new LmIpcServer(); + + ipcServer.Initialize(); + ipcServer.ServiceRequests(); + ipcServer.Shutdown(); + } + } +} diff --git a/Ryujinx.Horizon/Ryujinx.Horizon.csproj b/Ryujinx.Horizon/Ryujinx.Horizon.csproj new file mode 100644 index 00000000..e4591c6f --- /dev/null +++ b/Ryujinx.Horizon/Ryujinx.Horizon.csproj @@ -0,0 +1,14 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net7.0</TargetFramework> + </PropertyGroup> + + <ItemGroup> + <ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" /> + <ProjectReference Include="..\Ryujinx.Horizon.Common\Ryujinx.Horizon.Common.csproj" /> + <ProjectReference Include="..\Ryujinx.Horizon.Generators\Ryujinx.Horizon.Generators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /> + <ProjectReference Include="..\Ryujinx.Memory\Ryujinx.Memory.csproj" /> + </ItemGroup> + +</Project> diff --git a/Ryujinx.Horizon/Sdk/DebugUtil.cs b/Ryujinx.Horizon/Sdk/DebugUtil.cs new file mode 100644 index 00000000..f56a50ec --- /dev/null +++ b/Ryujinx.Horizon/Sdk/DebugUtil.cs @@ -0,0 +1,12 @@ +using System.Diagnostics; + +namespace Ryujinx.Horizon.Sdk +{ + static class DebugUtil + { + public static void Assert(bool condition) + { + Debug.Assert(condition); + } + } +} diff --git a/Ryujinx.Horizon/Sdk/Diag/LogSeverity.cs b/Ryujinx.Horizon/Sdk/Diag/LogSeverity.cs new file mode 100644 index 00000000..72acf789 --- /dev/null +++ b/Ryujinx.Horizon/Sdk/Diag/LogSeverity.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Horizon.Sdk.Diag +{ + enum LogSeverity : byte + { + Trace = 0, + Info = 1, + Warn = 2, + Error = 3, + Fatal = 4 + } +} \ No newline at end of file diff --git a/Ryujinx.Horizon/Sdk/Lm/LogDataChunkKey.cs b/Ryujinx.Horizon/Sdk/Lm/LogDataChunkKey.cs new file mode 100644 index 00000000..90756ece --- /dev/null +++ b/Ryujinx.Horizon/Sdk/Lm/LogDataChunkKey.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.Horizon.Sdk.Lm +{ + enum LogDataChunkKey + { + Start = 0, + Stop = 1, + Message = 2, + Line = 3, + Filename = 4, + Function = 5, + Module = 6, + Thread = 7, + DropCount = 8, + Time = 9, + ProgramName = 10, + + Count + } +} \ No newline at end of file diff --git a/Ryujinx.Horizon/Sdk/Lm/LogDestination.cs b/Ryujinx.Horizon/Sdk/Lm/LogDestination.cs new file mode 100644 index 00000000..8b08548d --- /dev/null +++ b/Ryujinx.Horizon/Sdk/Lm/LogDestination.cs @@ -0,0 +1,14 @@ +using System; + +namespace Ryujinx.Horizon.Sdk.Lm +{ + [Flags] + enum LogDestination + { + TargetManager = 1 << 0, + Uart = 1 << 1, + UartIfSleep = 1 << 2, + + All = 0xffff + } +} diff --git a/Ryujinx.Horizon/Sdk/Lm/LogPacketFlags.cs b/Ryujinx.Horizon/Sdk/Lm/LogPacketFlags.cs new file mode 100644 index 00000000..75d9f40b --- /dev/null +++ b/Ryujinx.Horizon/Sdk/Lm/LogPacketFlags.cs @@ -0,0 +1,12 @@ +using System; + +namespace Ryujinx.Horizon.Sdk.Lm +{ + [Flags] + enum LogPacketFlags : byte + { + IsHead = 1 << 0, + IsTail = 1 << 1, + IsLittleEndian = 1 << 2 + } +} diff --git a/Ryujinx.Horizon/Sdk/Lm/LogPacketHeader.cs b/Ryujinx.Horizon/Sdk/Lm/LogPacketHeader.cs new file mode 100644 index 00000000..022ba8da --- /dev/null +++ b/Ryujinx.Horizon/Sdk/Lm/LogPacketHeader.cs @@ -0,0 +1,15 @@ +using Ryujinx.Horizon.Sdk.Diag; + +namespace Ryujinx.Horizon.Sdk.Lm +{ + struct LogPacketHeader + { + public ulong ProcessId; + public ulong ThreadId; + public LogPacketFlags Flags; + public byte Padding; + public LogSeverity Severity; + public byte Verbosity; + public uint PayloadSize; + } +} diff --git a/Ryujinx.Horizon/Sdk/OsTypes/Event.cs b/Ryujinx.Horizon/Sdk/OsTypes/Event.cs new file mode 100644 index 00000000..79d7408e --- /dev/null +++ b/Ryujinx.Horizon/Sdk/OsTypes/Event.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; + +namespace Ryujinx.Horizon.Sdk.OsTypes +{ + class Event : IDisposable + { + private EventType _event; + + public object EventLock => _event.Lock; + public LinkedList<MultiWaitHolderBase> MultiWaitHolders => _event.MultiWaitHolders; + + public Event(EventClearMode clearMode) + { + Os.InitializeEvent(out _event, signaled: false, clearMode); + } + + public TriBool IsSignaledThreadUnsafe() + { + return _event.Signaled ? TriBool.True : TriBool.False; + } + + public void Wait() + { + Os.WaitEvent(ref _event); + } + + public bool TryWait() + { + return Os.TryWaitEvent(ref _event); + } + + public bool TimedWait(TimeSpan timeout) + { + return Os.TimedWaitEvent(ref _event, timeout); + } + + public void Signal() + { + Os.SignalEvent(ref _event); + } + + public void Clear() + { + Os.ClearEvent(ref _event); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + Os.FinalizeEvent(ref _event); + } + } + + public void Dispose() + { + Dispose(true); + } + } +} diff --git a/Ryujinx.Horizon/Sdk/OsTypes/EventClearMode.cs b/Ryujinx.Horizon/Sdk/OsTypes/EventClearMode.cs new file mode 100644 index 00000000..b500e6b3 --- /dev/null +++ b/Ryujinx.Horizon/Sdk/OsTypes/EventClearMode.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Horizon.Sdk.OsTypes +{ + enum EventClearMode + { + ManualClear, + AutoClear + } +} diff --git a/Ryujinx.Horizon/Sdk/OsTypes/EventType.cs b/Ryujinx.Horizon/Sdk/OsTypes/EventType.cs new file mode 100644 index 00000000..b4b1a275 --- /dev/null +++ b/Ryujinx.Horizon/Sdk/OsTypes/EventType.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace Ryujinx.Horizon.Sdk.OsTypes +{ + struct EventType + { + public LinkedList<MultiWaitHolderBase> MultiWaitHolders; + public bool Signaled; + public bool InitiallySignaled; + public EventClearMode ClearMode; + public InitializationState State; + public ulong BroadcastCounter; + public object Lock; + } +} diff --git a/Ryujinx.Horizon/Sdk/OsTypes/Impl/InterProcessEvent.cs b/Ryujinx.Horizon/Sdk/OsTypes/Impl/InterProcessEvent.cs new file mode 100644 index 00000000..62b5bf06 --- /dev/null +++ b/Ryujinx.Horizon/Sdk/OsTypes/Impl/InterProcessEvent.cs @@ -0,0 +1,89 @@ +using Ryujinx.Horizon.Common; + +namespace Ryujinx.Horizon.Sdk.OsTypes.Impl +{ + static class InterProcessEvent + { + public static Result Create(ref InterProcessEventType ipEvent, EventClearMode clearMode) + { + Result result = InterProcessEventImpl.Create(out int writableHandle, out int readableHandle); + + if (result != Result.Success) + { + return result; + } + + ipEvent = new InterProcessEventType( + clearMode == EventClearMode.AutoClear, + true, + true, + readableHandle, + writableHandle); + + return Result.Success; + } + + public static void Destroy(ref InterProcessEventType ipEvent) + { + ipEvent.State = InitializationState.NotInitialized; + + if (ipEvent.ReadableHandleManaged) + { + if (ipEvent.ReadableHandle != 0) + { + InterProcessEventImpl.Close(ipEvent.ReadableHandle); + } + ipEvent.ReadableHandleManaged = false; + } + + if (ipEvent.WritableHandleManaged) + { + if (ipEvent.WritableHandle != 0) + { + InterProcessEventImpl.Close(ipEvent.WritableHandle); + } + ipEvent.WritableHandleManaged = false; + } + } + + public static int DetachReadableHandle(ref InterProcessEventType ipEvent) + { + int handle = ipEvent.ReadableHandle; + + ipEvent.ReadableHandle = 0; + ipEvent.ReadableHandleManaged = false; + + return handle; + } + + public static int DetachWritableHandle(ref InterProcessEventType ipEvent) + { + int handle = ipEvent.WritableHandle; + + ipEvent.WritableHandle = 0; + ipEvent.WritableHandleManaged = false; + + return handle; + } + + public static int GetReadableHandle(ref InterProcessEventType ipEvent) + { + return ipEvent.ReadableHandle; + } + + public static int GetWritableHandle(ref InterProcessEventType ipEvent) + { + return ipEvent.WritableHandle; + } + + public static void Signal(ref InterProcessEventType ipEvent) + { + InterProcessEventImpl.Signal(ipEvent.WritableHandle); + } + + public static void Clear(ref InterProcessEventType ipEvent) + { + InterProcessEventImpl.Clear(ipEvent.ReadableHandle == 0 ? ipEvent.WritableHandle : ipEvent.ReadableHandle); + } + } +} diff --git a/Ryujinx.Horizon/Sdk/OsTypes/Impl/InterProcessEventImpl.cs b/Ryujinx.Horizon/Sdk/OsTypes/Impl/InterProcessEventImpl.cs new file mode 100644 index 00000000..a8aeacc9 --- /dev/null +++ b/Ryujinx.Horizon/Sdk/OsTypes/Impl/InterProcessEventImpl.cs @@ -0,0 +1,136 @@ +using Ryujinx.Horizon.Common; +using System; + +namespace Ryujinx.Horizon.Sdk.OsTypes.Impl +{ + static class InterProcessEventImpl + { + public static Result Create(out int writableHandle, out int readableHandle) + { + Result result = HorizonStatic.Syscall.CreateEvent(out writableHandle, out readableHandle); + + if (result == KernelResult.OutOfResource) + { + return OsResult.OutOfResource; + } + + result.AbortOnFailure(); + + return Result.Success; + } + + public static void Close(int handle) + { + if (handle != 0) + { + HorizonStatic.Syscall.CloseHandle(handle).AbortOnFailure(); + } + } + + public static void Signal(int handle) + { + HorizonStatic.Syscall.SignalEvent(handle).AbortOnFailure(); + } + + public static void Clear(int handle) + { + HorizonStatic.Syscall.ClearEvent(handle).AbortOnFailure(); + } + + public static void Wait(int handle, bool autoClear) + { + Span<int> handles = stackalloc int[1]; + + handles[0] = handle; + + while (true) + { + Result result = HorizonStatic.Syscall.WaitSynchronization(out _, handles, -1L); + + if (result == Result.Success) + { + if (autoClear) + { + result = HorizonStatic.Syscall.ResetSignal(handle); + + if (result == KernelResult.InvalidState) + { + continue; + } + + result.AbortOnFailure(); + } + + return; + } + + result.AbortUnless(KernelResult.Cancelled); + } + } + + public static bool TryWait(int handle, bool autoClear) + { + if (autoClear) + { + return HorizonStatic.Syscall.ResetSignal(handle) == Result.Success; + } + + Span<int> handles = stackalloc int[1]; + + handles[0] = handle; + + while (true) + { + Result result = HorizonStatic.Syscall.WaitSynchronization(out _, handles, 0); + + if (result == Result.Success) + { + return true; + } + else if (result == KernelResult.TimedOut) + { + return false; + } + + result.AbortUnless(KernelResult.Cancelled); + } + } + + public static bool TimedWait(int handle, bool autoClear, TimeSpan timeout) + { + Span<int> handles = stackalloc int[1]; + + handles[0] = handle; + + long timeoutNs = timeout.Milliseconds * 1000000L; + + while (true) + { + Result result = HorizonStatic.Syscall.WaitSynchronization(out _, handles, timeoutNs); + + if (result == Result.Success) + { + if (autoClear) + { + result = HorizonStatic.Syscall.ResetSignal(handle); + + if (result == KernelResult.InvalidState) + { + continue; + } + + result.AbortOnFailure(); + } + + return true; + } + else if (result == KernelResult.TimedOut) + { + return false; + } + + result.AbortUnless(KernelResult.Cancelled); + } + } + } +} diff --git a/Ryujinx.Horizon/Sdk/OsTypes/Impl/MultiWaitImpl.cs b/Ryujinx.Horizon/Sdk/OsTypes/Impl/MultiWaitImpl.cs new file mode 100644 index 00000000..fd45792d --- /dev/null +++ b/Ryujinx.Horizon/Sdk/OsTypes/Impl/MultiWaitImpl.cs @@ -0,0 +1,250 @@ +using Ryujinx.Common; +using Ryujinx.Horizon.Common; +using System.Collections.Generic; +using System; + +namespace Ryujinx.Horizon.Sdk.OsTypes.Impl +{ + class MultiWaitImpl + { + private const int WaitTimedOut = -1; + private const int WaitCancelled = -2; + private const int WaitInvalid = -3; + + private readonly List<MultiWaitHolderBase> _multiWaits; + + private object _lock; + + private int _waitingThreadHandle; + + private MultiWaitHolderBase _signaledHolder; + + public long CurrentTime { get; private set; } + + public MultiWaitImpl() + { + _multiWaits = new List<MultiWaitHolderBase>(); + + _lock = new object(); + } + + public void LinkMultiWaitHolder(MultiWaitHolderBase multiWaitHolder) + { + _multiWaits.Add(multiWaitHolder); + } + + public void UnlinkMultiWaitHolder(MultiWaitHolderBase multiWaitHolder) + { + _multiWaits.Remove(multiWaitHolder); + } + + public void MoveAllFrom(MultiWaitImpl other) + { + foreach (MultiWaitHolderBase multiWait in other._multiWaits) + { + multiWait.SetMultiWait(this); + } + + _multiWaits.AddRange(other._multiWaits); + + other._multiWaits.Clear(); + } + + public MultiWaitHolderBase WaitAnyImpl(bool infinite, long timeout) + { + _signaledHolder = null; + _waitingThreadHandle = Os.GetCurrentThreadHandle(); + + MultiWaitHolderBase result = LinkHoldersToObjectList(); + + lock (_lock) + { + if (_signaledHolder != null) + { + result = _signaledHolder; + } + } + + if (result == null) + { + result = WaitAnyHandleImpl(infinite, timeout); + } + + UnlinkHoldersFromObjectsList(); + _waitingThreadHandle = 0; + + return result; + } + + private MultiWaitHolderBase WaitAnyHandleImpl(bool infinite, long timeout) + { + Span<int> objectHandles = new int[64]; + + Span<MultiWaitHolderBase> objects = new MultiWaitHolderBase[64]; + + int count = FillObjectsArray(objectHandles, objects); + + long endTime = infinite ? long.MaxValue : PerformanceCounter.ElapsedMilliseconds * 1000000; + + while (true) + { + CurrentTime = PerformanceCounter.ElapsedMilliseconds * 1000000; + + MultiWaitHolderBase minTimeoutObject = RecalcMultiWaitTimeout(endTime, out long minTimeout); + + int index; + + if (count == 0 && minTimeout == 0) + { + index = WaitTimedOut; + } + else + { + index = WaitSynchronization(objectHandles.Slice(0, count), minTimeout); + + DebugUtil.Assert(index != WaitInvalid); + } + + switch (index) + { + case WaitTimedOut: + if (minTimeoutObject != null) + { + CurrentTime = PerformanceCounter.ElapsedMilliseconds * 1000000; + + if (minTimeoutObject.Signaled == TriBool.True) + { + lock (_lock) + { + _signaledHolder = minTimeoutObject; + + return _signaledHolder; + } + } + } + else + { + return null; + } + break; + case WaitCancelled: + lock (_lock) + { + if (_signaledHolder != null) + { + return _signaledHolder; + } + } + break; + default: + lock (_lock) + { + _signaledHolder = objects[index]; + + return _signaledHolder; + } + } + } + } + + private int FillObjectsArray(Span<int> handles, Span<MultiWaitHolderBase> objects) + { + int count = 0; + + foreach (MultiWaitHolderBase holder in _multiWaits) + { + int handle = holder.Handle; + + if (handle != 0) + { + handles[count] = handle; + objects[count] = holder; + + count++; + } + } + + return count; + } + + private MultiWaitHolderBase RecalcMultiWaitTimeout(long endTime, out long minTimeout) + { + MultiWaitHolderBase minTimeHolder = null; + + long minTime = endTime; + + foreach (MultiWaitHolder holder in _multiWaits) + { + long currentTime = holder.GetAbsoluteTimeToWakeup(); + + if ((ulong)currentTime < (ulong)minTime) + { + minTimeHolder = holder; + + minTime = currentTime; + } + } + + minTimeout = (ulong)minTime < (ulong)CurrentTime ? 0 : minTime - CurrentTime; + + return minTimeHolder; + } + + private static int WaitSynchronization(ReadOnlySpan<int> handles, long timeout) + { + Result result = HorizonStatic.Syscall.WaitSynchronization(out int index, handles, timeout); + + if (result == KernelResult.TimedOut) + { + return WaitTimedOut; + } + else if (result == KernelResult.Cancelled) + { + return WaitCancelled; + } + else + { + result.AbortOnFailure(); + } + + return index; + } + + public void NotifyAndWakeUpThread(MultiWaitHolderBase holder) + { + lock (_lock) + { + if (_signaledHolder == null) + { + _signaledHolder = holder; + HorizonStatic.Syscall.CancelSynchronization(_waitingThreadHandle).AbortOnFailure(); + } + } + } + + private MultiWaitHolderBase LinkHoldersToObjectList() + { + MultiWaitHolderBase signaledHolder = null; + + foreach (MultiWaitHolderBase holder in _multiWaits) + { + TriBool isSignaled = holder.LinkToObjectList(); + + if (signaledHolder == null && isSignaled == TriBool.True) + { + signaledHolder = holder; + } + } + + return signaledHolder; + } + + private void UnlinkHoldersFromObjectsList() + { + foreach (MultiWaitHolderBase holder in _multiWaits) + { + holder.UnlinkFromObjectList(); + } + } + } +} diff --git a/Ryujinx.Horizon/Sdk/OsTypes/InitializationState.cs b/Ryujinx.Horizon/Sdk/OsTypes/InitializationState.cs new file mode 100644 index 00000000..45ffd258 --- /dev/null +++ b/Ryujinx.Horizon/Sdk/OsTypes/InitializationState.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Horizon.Sdk.OsTypes +{ + enum InitializationState : byte + { + NotInitialized, + Initialized + } +} \ No newline at end of file diff --git a/Ryujinx.Horizon/Sdk/OsTypes/InterProcessEventType.cs b/Ryujinx.Horizon/Sdk/OsTypes/InterProcessEventType.cs new file mode 100644 index 00000000..5f6824fe --- /dev/null +++ b/Ryujinx.Horizon/Sdk/OsTypes/InterProcessEventType.cs @@ -0,0 +1,27 @@ +namespace Ryujinx.Horizon.Sdk.OsTypes +{ + struct InterProcessEventType + { + public readonly bool AutoClear; + public InitializationState State; + public bool ReadableHandleManaged; + public bool WritableHandleManaged; + public int ReadableHandle; + public int WritableHandle; + + public InterProcessEventType( + bool autoClear, + bool readableHandleManaged, + bool writableHandleManaged, + int readableHandle, + int writableHandle) + { + AutoClear = autoClear; + State = InitializationState.Initialized; + ReadableHandleManaged = readableHandleManaged; + WritableHandleManaged = writableHandleManaged; + ReadableHandle = readableHandle; + WritableHandle = writableHandle; + } + } +} diff --git a/Ryujinx.Horizon/Sdk/OsTypes/MultiWait.cs b/Ryujinx.Horizon/Sdk/OsTypes/MultiWait.cs new file mode 100644 index 00000000..5a91f6c3 --- /dev/null +++ b/Ryujinx.Horizon/Sdk/OsTypes/MultiWait.cs @@ -0,0 +1,43 @@ +using Ryujinx.Horizon.Sdk.OsTypes.Impl; + +namespace Ryujinx.Horizon.Sdk.OsTypes +{ + class MultiWait + { + private readonly MultiWaitImpl _impl; + + public MultiWait() + { + _impl = new MultiWaitImpl(); + } + + public void LinkMultiWaitHolder(MultiWaitHolderBase multiWaitHolder) + { + DebugUtil.Assert(!multiWaitHolder.IsLinked); + + _impl.LinkMultiWaitHolder(multiWaitHolder); + + multiWaitHolder.SetMultiWait(_impl); + } + + public void MoveAllFrom(MultiWait other) + { + _impl.MoveAllFrom(other._impl); + } + + public MultiWaitHolder WaitAny() + { + return (MultiWaitHolder)_impl.WaitAnyImpl(true, -1L); + } + + public MultiWaitHolder TryWaitAny() + { + return (MultiWaitHolder)_impl.WaitAnyImpl(false, 0); + } + + public MultiWaitHolder TimedWaitAny(long timeout) + { + return (MultiWaitHolder)_impl.WaitAnyImpl(false, timeout); + } + } +} diff --git a/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolder.cs b/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolder.cs new file mode 100644 index 00000000..a24b1906 --- /dev/null +++ b/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolder.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.Horizon.Sdk.OsTypes +{ + class MultiWaitHolder : MultiWaitHolderBase + { + public object UserData { get; set; } + + public void UnlinkFromMultiWaitHolder() + { + DebugUtil.Assert(IsLinked); + + MultiWait.UnlinkMultiWaitHolder(this); + + SetMultiWait(null); + } + } +} diff --git a/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderBase.cs b/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderBase.cs new file mode 100644 index 00000000..018305ba --- /dev/null +++ b/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderBase.cs @@ -0,0 +1,39 @@ +using Ryujinx.Horizon.Sdk.OsTypes.Impl; + +namespace Ryujinx.Horizon.Sdk.OsTypes +{ + class MultiWaitHolderBase + { + protected MultiWaitImpl MultiWait; + + public bool IsLinked => MultiWait != null; + + public virtual TriBool Signaled => TriBool.False; + + public virtual int Handle => 0; + + public void SetMultiWait(MultiWaitImpl multiWait) + { + MultiWait = multiWait; + } + + public MultiWaitImpl GetMultiWait() + { + return MultiWait; + } + + public virtual TriBool LinkToObjectList() + { + return TriBool.Undefined; + } + + public virtual void UnlinkFromObjectList() + { + } + + public virtual long GetAbsoluteTimeToWakeup() + { + return long.MaxValue; + } + } +} diff --git a/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderOfEvent.cs b/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderOfEvent.cs new file mode 100644 index 00000000..37ac22f0 --- /dev/null +++ b/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderOfEvent.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; + +namespace Ryujinx.Horizon.Sdk.OsTypes +{ + class MultiWaitHolderOfEvent : MultiWaitHolder + { + private Event _event; + private LinkedListNode<MultiWaitHolderBase> _node; + + public override TriBool Signaled + { + get + { + lock (_event.EventLock) + { + return _event.IsSignaledThreadUnsafe(); + } + } + } + + public MultiWaitHolderOfEvent(Event evnt) + { + _event = evnt; + } + + public override TriBool LinkToObjectList() + { + lock (_event.EventLock) + { + _node = _event.MultiWaitHolders.AddLast(this); + + return _event.IsSignaledThreadUnsafe(); + } + } + + public override void UnlinkFromObjectList() + { + lock (_event.EventLock) + { + _event.MultiWaitHolders.Remove(_node); + _node = null; + } + } + } +} diff --git a/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderOfHandle.cs b/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderOfHandle.cs new file mode 100644 index 00000000..6fc5c75b --- /dev/null +++ b/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderOfHandle.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Horizon.Sdk.OsTypes +{ + class MultiWaitHolderOfHandle : MultiWaitHolder + { + private int _handle; + + public override int Handle => _handle; + + public MultiWaitHolderOfHandle(int handle) + { + _handle = handle; + } + } +} diff --git a/Ryujinx.Horizon/Sdk/OsTypes/OsEvent.cs b/Ryujinx.Horizon/Sdk/OsTypes/OsEvent.cs new file mode 100644 index 00000000..cc7e8483 --- /dev/null +++ b/Ryujinx.Horizon/Sdk/OsTypes/OsEvent.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.Horizon.Sdk.OsTypes +{ + static partial class Os + { + public static void InitializeEvent(out EventType evnt, bool signaled, EventClearMode clearMode) + { + evnt = new EventType + { + MultiWaitHolders = new LinkedList<MultiWaitHolderBase>(), + Signaled = signaled, + InitiallySignaled = signaled, + ClearMode = clearMode, + State = InitializationState.Initialized, + Lock = new object() + }; + } + + public static void FinalizeEvent(ref EventType evnt) + { + evnt.State = InitializationState.NotInitialized; + } + + public static void WaitEvent(ref EventType evnt) + { + lock (evnt.Lock) + { + ulong currentCounter = evnt.BroadcastCounter; + + while (!evnt.Signaled) + { + if (currentCounter != evnt.BroadcastCounter) + { + break; + } + + Monitor.Wait(evnt.Lock); + } + + if (evnt.ClearMode == EventClearMode.AutoClear) + { + evnt.Signaled = false; + } + } + } + + public static bool TryWaitEvent(ref EventType evnt) + { + lock (evnt.Lock) + { + bool signaled = evnt.Signaled; + + if (evnt.ClearMode == EventClearMode.AutoClear) + { + evnt.Signaled = false; + } + + return signaled; + } + } + + public static bool TimedWaitEvent(ref EventType evnt, TimeSpan timeout) + { + lock (evnt.Lock) + { + ulong currentCounter = evnt.BroadcastCounter; + + while (!evnt.Signaled) + { + if (currentCounter != evnt.BroadcastCounter) + { + break; + } + + bool wasSignaledInTime = Monitor.Wait(evnt.Lock, timeout); + if (!wasSignaledInTime) + { + return false; + } + } + + if (evnt.ClearMode == EventClearMode.AutoClear) + { + evnt.Signaled = false; + } + } + + return true; + } + + public static void SignalEvent(ref EventType evnt) + { + lock (evnt.Lock) + { + if (evnt.Signaled) + { + return; + } + + evnt.Signaled = true; + + if (evnt.ClearMode == EventClearMode.ManualClear) + { + evnt.BroadcastCounter++; + Monitor.PulseAll(evnt.Lock); + } + else + { + Monitor.Pulse(evnt.Lock); + } + + foreach (MultiWaitHolderBase holder in evnt.MultiWaitHolders) + { + holder.GetMultiWait().NotifyAndWakeUpThread(holder); + } + } + } + + public static void ClearEvent(ref EventType evnt) + { + lock (evnt.Lock) + { + evnt.Signaled = false; + } + } + } +} diff --git a/Ryujinx.Horizon/Sdk/OsTypes/OsMultiWait.cs b/Ryujinx.Horizon/Sdk/OsTypes/OsMultiWait.cs new file mode 100644 index 00000000..827de231 --- /dev/null +++ b/Ryujinx.Horizon/Sdk/OsTypes/OsMultiWait.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Horizon.Sdk.OsTypes +{ + static partial class Os + { + public static void FinalizeMultiWaitHolder(MultiWaitHolderBase holder) + { + DebugUtil.Assert(!holder.IsLinked); + } + } +} diff --git a/Ryujinx.Horizon/Sdk/OsTypes/OsProcessHandle.cs b/Ryujinx.Horizon/Sdk/OsTypes/OsProcessHandle.cs new file mode 100644 index 00000000..6a6d9bf2 --- /dev/null +++ b/Ryujinx.Horizon/Sdk/OsTypes/OsProcessHandle.cs @@ -0,0 +1,33 @@ +using Ryujinx.Horizon.Common; + +namespace Ryujinx.Horizon.Sdk.OsTypes +{ + static partial class Os + { + private const int SelfProcessHandle = (0x1ffff << 15) | 1; + + public static int GetCurrentProcessHandle() + { + return SelfProcessHandle; + } + + public static ulong GetCurrentProcessId() + { + return GetProcessId(GetCurrentProcessHandle()); + } + + private static ulong GetProcessId(int handle) + { + Result result = TryGetProcessId(handle, out ulong pid); + + result.AbortOnFailure(); + + return pid; + } + + private static Result TryGetProcessId(int handle, out ulong pid) + { + return HorizonStatic.Syscall.GetProcessId(out pid, handle); + } + } +} diff --git a/Ryujinx.Horizon/Sdk/OsTypes/OsResult.cs b/Ryujinx.Horizon/Sdk/OsTypes/OsResult.cs new file mode 100644 index 00000000..86dcd1fa --- /dev/null +++ b/Ryujinx.Horizon/Sdk/OsTypes/OsResult.cs @@ -0,0 +1,11 @@ +using Ryujinx.Horizon.Common; + +namespace Ryujinx.Horizon.Sdk.OsTypes +{ + static class OsResult + { + private const int ModuleId = 3; + + public static Result OutOfResource => new Result(ModuleId, 9); + } +} diff --git a/Ryujinx.Horizon/Sdk/OsTypes/OsSystemEvent.cs b/Ryujinx.Horizon/Sdk/OsTypes/OsSystemEvent.cs new file mode 100644 index 00000000..061d7a3c --- /dev/null +++ b/Ryujinx.Horizon/Sdk/OsTypes/OsSystemEvent.cs @@ -0,0 +1,85 @@ +using Ryujinx.Horizon.Sdk.OsTypes.Impl; +using Ryujinx.Horizon.Common; +using System; + +namespace Ryujinx.Horizon.Sdk.OsTypes +{ + static partial class Os + { + public static Result CreateSystemEvent(out SystemEventType sysEvent, EventClearMode clearMode, bool interProcess) + { + sysEvent = new SystemEventType(); + + if (interProcess) + { + Result result = InterProcessEvent.Create(ref sysEvent.InterProcessEvent, clearMode); + + if (result != Result.Success) + { + return result; + } + + sysEvent.State = SystemEventType.InitializationState.InitializedAsInterProcess; + } + else + { + throw new NotImplementedException(); + } + + return Result.Success; + } + + public static void DestroySystemEvent(ref SystemEventType sysEvent) + { + var oldState = sysEvent.State; + sysEvent.State = SystemEventType.InitializationState.NotInitialized; + + switch (oldState) + { + case SystemEventType.InitializationState.InitializedAsInterProcess: + InterProcessEvent.Destroy(ref sysEvent.InterProcessEvent); + break; + } + } + + public static int DetachReadableHandleOfSystemEvent(ref SystemEventType sysEvent) + { + return InterProcessEvent.DetachReadableHandle(ref sysEvent.InterProcessEvent); + } + + public static int DetachWritableHandleOfSystemEvent(ref SystemEventType sysEvent) + { + return InterProcessEvent.DetachWritableHandle(ref sysEvent.InterProcessEvent); + } + + public static int GetReadableHandleOfSystemEvent(ref SystemEventType sysEvent) + { + return InterProcessEvent.GetReadableHandle(ref sysEvent.InterProcessEvent); + } + + public static int GetWritableHandleOfSystemEvent(ref SystemEventType sysEvent) + { + return InterProcessEvent.GetWritableHandle(ref sysEvent.InterProcessEvent); + } + + public static void SignalSystemEvent(ref SystemEventType sysEvent) + { + switch (sysEvent.State) + { + case SystemEventType.InitializationState.InitializedAsInterProcess: + InterProcessEvent.Signal(ref sysEvent.InterProcessEvent); + break; + } + } + + public static void ClearSystemEvent(ref SystemEventType sysEvent) + { + switch (sysEvent.State) + { + case SystemEventType.InitializationState.InitializedAsInterProcess: + InterProcessEvent.Clear(ref sysEvent.InterProcessEvent); + break; + } + } + } +} diff --git a/Ryujinx.Horizon/Sdk/OsTypes/OsThreadManager.cs b/Ryujinx.Horizon/Sdk/OsTypes/OsThreadManager.cs new file mode 100644 index 00000000..2037cd7f --- /dev/null +++ b/Ryujinx.Horizon/Sdk/OsTypes/OsThreadManager.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Horizon.Sdk.OsTypes +{ + static partial class Os + { + public static int GetCurrentThreadHandle() + { + return HorizonStatic.CurrentThreadHandle; + } + } +} diff --git a/Ryujinx.Horizon/Sdk/OsTypes/SystemEventType.cs b/Ryujinx.Horizon/Sdk/OsTypes/SystemEventType.cs new file mode 100644 index 00000000..338493d2 --- /dev/null +++ b/Ryujinx.Horizon/Sdk/OsTypes/SystemEventType.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Horizon.Sdk.OsTypes +{ + struct SystemEventType + { + public enum InitializationState : byte + { + NotInitialized, + InitializedAsEvent, + InitializedAsInterProcess + } + + public InterProcessEventType InterProcessEvent; + public InitializationState State; + + public bool NotInitialized => State == InitializationState.NotInitialized; + } +} diff --git a/Ryujinx.Horizon/Sdk/OsTypes/TriBool.cs b/Ryujinx.Horizon/Sdk/OsTypes/TriBool.cs new file mode 100644 index 00000000..7debd9e2 --- /dev/null +++ b/Ryujinx.Horizon/Sdk/OsTypes/TriBool.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Horizon.Sdk.OsTypes +{ + enum TriBool + { + False, + True, + Undefined + } +} diff --git a/Ryujinx.Horizon/Sdk/ServiceUtil.cs b/Ryujinx.Horizon/Sdk/ServiceUtil.cs new file mode 100644 index 00000000..413ac1f6 --- /dev/null +++ b/Ryujinx.Horizon/Sdk/ServiceUtil.cs @@ -0,0 +1,38 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf.Cmif; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; + +namespace Ryujinx.Horizon.Sdk +{ + static class ServiceUtil + { + public static Result SendRequest(out CmifResponse response, int sessionHandle, uint requestId, bool sendPid, scoped ReadOnlySpan<byte> data) + { + ulong tlsAddress = HorizonStatic.ThreadContext.TlsAddress; + int tlsSize = Api.TlsMessageBufferSize; + + using (var tlsRegion = HorizonStatic.AddressSpace.GetWritableRegion(tlsAddress, tlsSize)) + { + CmifRequest request = CmifMessage.CreateRequest(tlsRegion.Memory.Span, new CmifRequestFormat() + { + DataSize = data.Length, + RequestId = requestId, + SendPid = sendPid + }); + + data.CopyTo(request.Data); + } + + Result result = HorizonStatic.Syscall.SendSyncRequest(sessionHandle); + + if (result.IsFailure) + { + response = default; + return result; + } + + return CmifMessage.ParseResponse(out response, HorizonStatic.AddressSpace.GetWritableRegion(tlsAddress, tlsSize).Memory.Span, false, 0); + } + } +} diff --git a/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainInHeader.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainInHeader.cs new file mode 100644 index 00000000..88211501 --- /dev/null +++ b/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/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainOutHeader.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainOutHeader.cs new file mode 100644 index 00000000..2086d24c --- /dev/null +++ b/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/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainRequestType.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainRequestType.cs new file mode 100644 index 00000000..b913db94 --- /dev/null +++ b/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/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifInHeader.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifInHeader.cs new file mode 100644 index 00000000..55b859fc --- /dev/null +++ b/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/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifMessage.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifMessage.cs new file mode 100644 index 00000000..781452e3 --- /dev/null +++ b/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifMessage.cs @@ -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; + } + } +} diff --git a/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifOutHeader.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifOutHeader.cs new file mode 100644 index 00000000..2828cde5 --- /dev/null +++ b/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/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequest.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequest.cs new file mode 100644 index 00000000..80772ad3 --- /dev/null +++ b/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/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequestFormat.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequestFormat.cs new file mode 100644 index 00000000..d1154578 --- /dev/null +++ b/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/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifResponse.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifResponse.cs new file mode 100644 index 00000000..d1d8dc9c --- /dev/null +++ b/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/Ryujinx.Horizon/Sdk/Sf/Cmif/CommandType.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/CommandType.cs new file mode 100644 index 00000000..b3b05864 --- /dev/null +++ b/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/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObject.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObject.cs new file mode 100644 index 00000000..14839687 --- /dev/null +++ b/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/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObjectDispatchTable.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObjectDispatchTable.cs new file mode 100644 index 00000000..bcf311b2 --- /dev/null +++ b/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.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; + } + } + } +} diff --git a/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObjectProcessor.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObjectProcessor.cs new file mode 100644 index 00000000..92d86196 --- /dev/null +++ b/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).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]; + } + } + } +} diff --git a/Ryujinx.Horizon/Sdk/Sf/Cmif/HandlesToClose.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/HandlesToClose.cs new file mode 100644 index 00000000..0f3b259a --- /dev/null +++ b/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/Ryujinx.Horizon/Sdk/Sf/Cmif/InlineContext.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/InlineContext.cs new file mode 100644 index 00000000..ddb6943f --- /dev/null +++ b/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/Ryujinx.Horizon/Sdk/Sf/Cmif/PointerAndSize.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/PointerAndSize.cs new file mode 100644 index 00000000..5af00077 --- /dev/null +++ b/Ryujinx.Horizon/Sdk/Sf/Cmif/PointerAndSize.cs @@ -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; + } + } +} diff --git a/Ryujinx.Horizon/Sdk/Sf/Cmif/ScopedInlineContextChange.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/ScopedInlineContextChange.cs new file mode 100644 index 00000000..eabe544f --- /dev/null +++ b/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/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainBase.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainBase.cs new file mode 100644 index 00000000..f38fa030 --- /dev/null +++ b/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/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainManager.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainManager.cs new file mode 100644 index 00000000..62ee2738 --- /dev/null +++ b/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/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerMessageProcessor.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerMessageProcessor.cs new file mode 100644 index 00000000..e7650238 --- /dev/null +++ b/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/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerMessageRuntimeMetadata.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerMessageRuntimeMetadata.cs new file mode 100644 index 00000000..18a40430 --- /dev/null +++ b/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerMessageRuntimeMetadata.cs @@ -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; + } + } +} diff --git a/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchContext.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchContext.cs new file mode 100644 index 00000000..3339a1a6 --- /dev/null +++ b/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; + } +} diff --git a/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchMeta.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchMeta.cs new file mode 100644 index 00000000..7fbd8eb8 --- /dev/null +++ b/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/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchTable.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchTable.cs new file mode 100644 index 00000000..145c1783 --- /dev/null +++ b/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()); + } + } +} diff --git a/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchTableBase.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchTableBase.cs new file mode 100644 index 00000000..a0e28ca8 --- /dev/null +++ b/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchTableBase.cs @@ -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; + } + } +} diff --git a/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceObjectHolder.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceObjectHolder.cs new file mode 100644 index 00000000..6e87e340 --- /dev/null +++ b/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); + } + } +} diff --git a/Ryujinx.Horizon/Sdk/Sf/CmifCommandAttribute.cs b/Ryujinx.Horizon/Sdk/Sf/CmifCommandAttribute.cs new file mode 100644 index 00000000..51a7b597 --- /dev/null +++ b/Ryujinx.Horizon/Sdk/Sf/CmifCommandAttribute.cs @@ -0,0 +1,15 @@ +using System; + +namespace Ryujinx.Horizon.Sdk.Sf +{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + class CmifCommandAttribute : Attribute + { + public uint CommandId { get; } + + public CmifCommandAttribute(uint commandId) + { + CommandId = commandId; + } + } +} diff --git a/Ryujinx.Horizon/Sdk/Sf/CommandArg.cs b/Ryujinx.Horizon/Sdk/Sf/CommandArg.cs new file mode 100644 index 00000000..8f367b4e --- /dev/null +++ b/Ryujinx.Horizon/Sdk/Sf/CommandArg.cs @@ -0,0 +1,56 @@ +using Ryujinx.Horizon.Sdk.Sf.Hipc; + +namespace Ryujinx.Horizon.Sdk.Sf +{ + enum CommandArgType : byte + { + Invalid, + + Buffer, + InArgument, + InCopyHandle, + InMoveHandle, + InObject, + OutArgument, + OutCopyHandle, + OutMoveHandle, + OutObject, + ProcessId + } + + struct CommandArg + { + public CommandArgType Type { get; } + public HipcBufferFlags BufferFlags { get; } + public ushort BufferFixedSize { get; } + public int ArgSize { get; } + public int ArgAlignment { get; } + + public CommandArg(CommandArgType type) + { + Type = type; + BufferFlags = default; + BufferFixedSize = 0; + ArgSize = 0; + ArgAlignment = 0; + } + + public CommandArg(CommandArgType type, int argSize, int argAlignment) + { + Type = type; + BufferFlags = default; + BufferFixedSize = 0; + ArgSize = argSize; + ArgAlignment = argAlignment; + } + + public CommandArg(HipcBufferFlags flags, ushort fixedSize = 0) + { + Type = CommandArgType.Buffer; + BufferFlags = flags; + BufferFixedSize = fixedSize; + ArgSize = 0; + ArgAlignment = 0; + } + } +} diff --git a/Ryujinx.Horizon/Sdk/Sf/CommandArgAttributes.cs b/Ryujinx.Horizon/Sdk/Sf/CommandArgAttributes.cs new file mode 100644 index 00000000..5b7c302f --- /dev/null +++ b/Ryujinx.Horizon/Sdk/Sf/CommandArgAttributes.cs @@ -0,0 +1,38 @@ +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; + +namespace Ryujinx.Horizon.Sdk.Sf +{ + [AttributeUsage(AttributeTargets.Parameter)] + class BufferAttribute : Attribute + { + public HipcBufferFlags Flags { get; } + public ushort FixedSize { get; } + + public BufferAttribute(HipcBufferFlags flags) + { + Flags = flags; + } + + public BufferAttribute(HipcBufferFlags flags, ushort fixedSize) + { + Flags = flags | HipcBufferFlags.FixedSize; + FixedSize = fixedSize; + } + } + + [AttributeUsage(AttributeTargets.Parameter)] + class ClientProcessIdAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Parameter)] + class CopyHandleAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Parameter)] + class MoveHandleAttribute : Attribute + { + } +} diff --git a/Ryujinx.Horizon/Sdk/Sf/CommandHandler.cs b/Ryujinx.Horizon/Sdk/Sf/CommandHandler.cs new file mode 100644 index 00000000..ae42a8ef --- /dev/null +++ b/Ryujinx.Horizon/Sdk/Sf/CommandHandler.cs @@ -0,0 +1,57 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf.Cmif; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Sf +{ + class CommandHandler + { + public delegate Result MethodInvoke( + ref ServiceDispatchContext context, + HipcCommandProcessor processor, + ServerMessageRuntimeMetadata runtimeMetadata, + ReadOnlySpan<byte> inRawData, + ref Span<CmifOutHeader> outHeader); + + private readonly MethodInvoke _invoke; + private readonly HipcCommandProcessor _processor; + + public string MethodName => _invoke.Method.Name; + + public CommandHandler(MethodInvoke invoke, params CommandArg[] args) + { + _invoke = invoke; + _processor = new HipcCommandProcessor(args); + } + + public Result Invoke(ref Span<CmifOutHeader> outHeader, ref ServiceDispatchContext context, ReadOnlySpan<byte> inRawData) + { + if (context.Processor == null) + { + context.Processor = _processor; + } + else + { + context.Processor.SetImplementationProcessor(_processor); + } + + var runtimeMetadata = context.Processor.GetRuntimeMetadata(); + Result result = context.Processor.PrepareForProcess(ref context, runtimeMetadata); + + if (result.IsFailure) + { + return result; + } + + return _invoke(ref context, _processor, runtimeMetadata, inRawData, ref outHeader); + } + + public static void GetCmifOutHeaderPointer(ref Span<CmifOutHeader> outHeader, ref Span<byte> outRawData) + { + outHeader = MemoryMarshal.Cast<byte, CmifOutHeader>(outRawData).Slice(0, 1); + outRawData = outRawData.Slice(Unsafe.SizeOf<CmifOutHeader>()); + } + } +} diff --git a/Ryujinx.Horizon/Sdk/Sf/CommandSerialization.cs b/Ryujinx.Horizon/Sdk/Sf/CommandSerialization.cs new file mode 100644 index 00000000..9a3a511a --- /dev/null +++ b/Ryujinx.Horizon/Sdk/Sf/CommandSerialization.cs @@ -0,0 +1,68 @@ +using Ryujinx.Horizon.Sdk.Sf.Cmif; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Memory; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Sf +{ + static class CommandSerialization + { + public static ReadOnlySpan<byte> GetReadOnlySpan(PointerAndSize bufferRange) + { + return HorizonStatic.AddressSpace.GetSpan(bufferRange.Address, checked((int)bufferRange.Size)); + } + + public static WritableRegion GetWritableRegion(PointerAndSize bufferRange) + { + return HorizonStatic.AddressSpace.GetWritableRegion(bufferRange.Address, checked((int)bufferRange.Size)); + } + + public static ref T GetRef<T>(PointerAndSize bufferRange) where T : unmanaged + { + var writableRegion = GetWritableRegion(bufferRange); + return ref MemoryMarshal.Cast<byte, T>(writableRegion.Memory.Span)[0]; + } + + public static object DeserializeArg<T>(ref ServiceDispatchContext context, ReadOnlySpan<byte> inRawData, int offset) where T : unmanaged + { + return MemoryMarshal.Cast<byte, T>(inRawData.Slice(offset, Unsafe.SizeOf<T>()))[0]; + } + + public static T DeserializeArg<T>(ReadOnlySpan<byte> inRawData, int offset) where T : unmanaged + { + return MemoryMarshal.Cast<byte, T>(inRawData.Slice(offset, Unsafe.SizeOf<T>()))[0]; + } + + public static ulong DeserializeClientProcessId(ref ServiceDispatchContext context) + { + return context.Request.Pid; + } + + public static int DeserializeCopyHandle(ref ServiceDispatchContext context, int index) + { + return context.Request.Data.CopyHandles[index]; + } + + public static int DeserializeMoveHandle(ref ServiceDispatchContext context, int index) + { + return context.Request.Data.MoveHandles[index]; + } + + public static void SerializeArg<T>(Span<byte> outRawData, int offset, T value) where T : unmanaged + { + MemoryMarshal.Cast<byte, T>(outRawData.Slice(offset, Unsafe.SizeOf<T>()))[0] = (T)value; + } + + public static void SerializeCopyHandle(HipcMessageData response, int index, int value) + { + response.CopyHandles[index] = value; + } + + public static void SerializeMoveHandle(HipcMessageData response, int index, int value) + { + response.MoveHandles[index] = value; + } + } +} diff --git a/Ryujinx.Horizon/Sdk/Sf/Hipc/Api.cs b/Ryujinx.Horizon/Sdk/Sf/Hipc/Api.cs new file mode 100644 index 00000000..deac524c --- /dev/null +++ b/Ryujinx.Horizon/Sdk/Sf/Hipc/Api.cs @@ -0,0 +1,89 @@ +using Ryujinx.Horizon.Common; +using System; + +namespace Ryujinx.Horizon.Sdk.Sf.Hipc +{ + static class Api + { + public const int TlsMessageBufferSize = 0x100; + + public static Result Receive(out ReceiveResult recvResult, int sessionHandle, Span<byte> messageBuffer) + { + Result result = ReceiveImpl(sessionHandle, messageBuffer); + + if (result == KernelResult.PortRemoteClosed) + { + recvResult = ReceiveResult.Closed; + + return Result.Success; + } + else if (result == KernelResult.ReceiveListBroken) + { + recvResult = ReceiveResult.NeedsRetry; + + return Result.Success; + } + + recvResult = ReceiveResult.Success; + + return result; + } + + private static Result ReceiveImpl(int sessionHandle, Span<byte> messageBuffer) + { + Span<int> handles = stackalloc int[1]; + + handles[0] = sessionHandle; + + var tlsSpan = HorizonStatic.AddressSpace.GetSpan(HorizonStatic.ThreadContext.TlsAddress, TlsMessageBufferSize); + + if (messageBuffer == tlsSpan) + { + return HorizonStatic.Syscall.ReplyAndReceive(out _, handles, 0, -1L); + } + else + { + throw new NotImplementedException(); + } + } + + public static Result Reply(int sessionHandle, ReadOnlySpan<byte> messageBuffer) + { + Result result = ReplyImpl(sessionHandle, messageBuffer); + + result.AbortUnless(KernelResult.TimedOut, KernelResult.PortRemoteClosed); + + return Result.Success; + } + + private static Result ReplyImpl(int sessionHandle, ReadOnlySpan<byte> messageBuffer) + { + Span<int> handles = stackalloc int[1]; + + handles[0] = sessionHandle; + + var tlsSpan = HorizonStatic.AddressSpace.GetSpan(HorizonStatic.ThreadContext.TlsAddress, TlsMessageBufferSize); + + if (messageBuffer == tlsSpan) + { + return HorizonStatic.Syscall.ReplyAndReceive(out _, handles, sessionHandle, 0); + } + else + { + throw new NotImplementedException(); + } + } + + public static Result CreateSession(out int serverHandle, out int clientHandle) + { + Result result = HorizonStatic.Syscall.CreateSession(out serverHandle, out clientHandle, false, null); + + if (result == KernelResult.OutOfResource) + { + return HipcResult.OutOfSessions; + } + + return result; + } + } +} diff --git a/Ryujinx.Horizon/Sdk/Sf/Hipc/Header.cs b/Ryujinx.Horizon/Sdk/Sf/Hipc/Header.cs new file mode 100644 index 00000000..cdb50b57 --- /dev/null +++ b/Ryujinx.Horizon/Sdk/Sf/Hipc/Header.cs @@ -0,0 +1,65 @@ +using Ryujinx.Common.Utilities; +using Ryujinx.Horizon.Sdk.Sf.Cmif; + +namespace Ryujinx.Horizon.Sdk.Sf.Hipc +{ + struct Header + { + private uint _word0; + private uint _word1; + + public CommandType Type + { + get => (CommandType)_word0.Extract(0, 16); + set => _word0 = _word0.Insert(0, 16, (uint)value); + } + + public int SendStaticsCount + { + get => (int)_word0.Extract(16, 4); + set => _word0 = _word0.Insert(16, 4, (uint)value); + } + + public int SendBuffersCount + { + get => (int)_word0.Extract(20, 4); + set => _word0 = _word0.Insert(20, 4, (uint)value); + } + + public int ReceiveBuffersCount + { + get => (int)_word0.Extract(24, 4); + set => _word0 = _word0.Insert(24, 4, (uint)value); + } + + public int ExchangeBuffersCount + { + get => (int)_word0.Extract(28, 4); + set => _word0 = _word0.Insert(28, 4, (uint)value); + } + + public int DataWordsCount + { + get => (int)_word1.Extract(0, 10); + set => _word1 = _word1.Insert(0, 10, (uint)value); + } + + public int ReceiveStaticMode + { + get => (int)_word1.Extract(10, 4); + set => _word1 = _word1.Insert(10, 4, (uint)value); + } + + public int ReceiveListOffset + { + get => (int)_word1.Extract(20, 11); + set => _word1 = _word1.Insert(20, 11, (uint)value); + } + + public bool HasSpecialHeader + { + get => _word1.Extract(31); + set => _word1 = _word1.Insert(31, value); + } + } +} diff --git a/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferDescriptor.cs b/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferDescriptor.cs new file mode 100644 index 00000000..7778d5bc --- /dev/null +++ b/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferDescriptor.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Horizon.Sdk.Sf.Hipc +{ + struct HipcBufferDescriptor + { +#pragma warning disable CS0649 + private uint _sizeLow; + private uint _addressLow; + private uint _word2; +#pragma warning restore CS0649 + + public ulong Address => _addressLow | (((ulong)_word2 << 4) & 0xf00000000UL) | (((ulong)_word2 << 34) & 0x7000000000UL); + public ulong Size => _sizeLow | ((ulong)_word2 << 8) & 0xf00000000UL; + public HipcBufferMode Mode => (HipcBufferMode)(_word2 & 3); + } +} diff --git a/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferFlags.cs b/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferFlags.cs new file mode 100644 index 00000000..594af2c8 --- /dev/null +++ b/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferFlags.cs @@ -0,0 +1,17 @@ +using System; + +namespace Ryujinx.Horizon.Sdk.Sf.Hipc +{ + [Flags] + enum HipcBufferFlags : byte + { + In = 1 << 0, + Out = 1 << 1, + MapAlias = 1 << 2, + Pointer = 1 << 3, + FixedSize = 1 << 4, + AutoSelect = 1 << 5, + MapTransferAllowsNonSecure = 1 << 6, + MapTransferAllowsNonDevice = 1 << 7 + } +} diff --git a/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferMode.cs b/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferMode.cs new file mode 100644 index 00000000..4ef6374b --- /dev/null +++ b/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferMode.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Horizon.Sdk.Sf.Hipc +{ + enum HipcBufferMode + { + Normal = 0, + NonSecure = 1, + Invalid = 2, + NonDevice = 3 + } +} diff --git a/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcManager.cs b/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcManager.cs new file mode 100644 index 00000000..ea2ec650 --- /dev/null +++ b/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcManager.cs @@ -0,0 +1,115 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf.Cmif; +using System; + +namespace Ryujinx.Horizon.Sdk.Sf.Hipc +{ + partial class HipcManager : IServiceObject + { + private readonly ServerDomainSessionManager _manager; + private readonly ServerSession _session; + + public HipcManager(ServerDomainSessionManager manager, ServerSession session) + { + _manager = manager; + _session = session; + } + + [CmifCommand(0)] + public Result ConvertCurrentObjectToDomain(out int objectId) + { + objectId = 0; + + var domain = _manager.Domain.AllocateDomainServiceObject(); + if (domain == null) + { + return HipcResult.OutOfDomains; + } + + bool succeeded = false; + + try + { + Span<int> objectIds = stackalloc int[1]; + + Result result = domain.ReserveIds(objectIds); + + if (result.IsFailure) + { + return result; + } + + objectId = objectIds[0]; + succeeded = true; + } + finally + { + if (!succeeded) + { + ServerDomainManager.DestroyDomainServiceObject(domain); + } + } + + domain.RegisterObject(objectId, _session.ServiceObjectHolder); + _session.ServiceObjectHolder = new ServiceObjectHolder(domain); + + return Result.Success; + } + + [CmifCommand(1)] + public Result CopyFromCurrentDomain([MoveHandle] out int clientHandle, int objectId) + { + clientHandle = 0; + + if (!(_session.ServiceObjectHolder.ServiceObject is DomainServiceObject domain)) + { + return HipcResult.TargetNotDomain; + } + + var obj = domain.GetObject(objectId); + if (obj == null) + { + return HipcResult.DomainObjectNotFound; + } + + Api.CreateSession(out int serverHandle, out clientHandle).AbortOnFailure(); + _manager.RegisterSession(serverHandle, obj).AbortOnFailure(); + + return Result.Success; + } + + [CmifCommand(2)] + public Result CloneCurrentObject([MoveHandle] out int clientHandle) + { + return CloneCurrentObjectImpl(out clientHandle, _manager); + } + + [CmifCommand(3)] + public void QueryPointerBufferSize(out ushort size) + { + size = (ushort)_session.PointerBuffer.Size; + } + + [CmifCommand(4)] + public Result CloneCurrentObjectEx([MoveHandle] out int clientHandle, uint tag) + { + return CloneCurrentObjectImpl(out clientHandle, _manager.GetSessionManagerByTag(tag)); + } + + private Result CloneCurrentObjectImpl(out int clientHandle, ServerSessionManager manager) + { + clientHandle = 0; + + var clone = _session.ServiceObjectHolder.Clone(); + if (clone == null) + { + return HipcResult.DomainObjectNotFound; + } + + Api.CreateSession(out int serverHandle, out clientHandle).AbortOnFailure(); + manager.RegisterSession(serverHandle, clone).AbortOnFailure(); + + return Result.Success; + } + } +} diff --git a/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessage.cs b/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessage.cs new file mode 100644 index 00000000..3017f404 --- /dev/null +++ b/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessage.cs @@ -0,0 +1,222 @@ +using Ryujinx.Common; +using Ryujinx.Horizon.Sdk.Sf.Cmif; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Sf.Hipc +{ + ref struct HipcMessage + { + public const int AutoReceiveStatic = byte.MaxValue; + + public HipcMetadata Meta; + public HipcMessageData Data; + public ulong Pid; + + public HipcMessage(Span<byte> data) + { + int initialLength = data.Length; + + Header header = MemoryMarshal.Cast<byte, Header>(data)[0]; + + data = data.Slice(Unsafe.SizeOf<Header>()); + + int receiveStaticsCount = 0; + ulong pid = 0; + + if (header.ReceiveStaticMode != 0) + { + if (header.ReceiveStaticMode == 2) + { + receiveStaticsCount = AutoReceiveStatic; + } + else if (header.ReceiveStaticMode > 2) + { + receiveStaticsCount = header.ReceiveStaticMode - 2; + } + } + + SpecialHeader specialHeader = default; + + if (header.HasSpecialHeader) + { + specialHeader = MemoryMarshal.Cast<byte, SpecialHeader>(data)[0]; + + data = data.Slice(Unsafe.SizeOf<SpecialHeader>()); + + if (specialHeader.SendPid) + { + pid = MemoryMarshal.Cast<byte, ulong>(data)[0]; + + data = data.Slice(sizeof(ulong)); + } + } + + Meta = new HipcMetadata() + { + Type = (int)header.Type, + SendStaticsCount = header.SendStaticsCount, + SendBuffersCount = header.SendBuffersCount, + ReceiveBuffersCount = header.ReceiveBuffersCount, + ExchangeBuffersCount = header.ExchangeBuffersCount, + DataWordsCount = header.DataWordsCount, + ReceiveStaticsCount = receiveStaticsCount, + SendPid = specialHeader.SendPid, + CopyHandlesCount = specialHeader.CopyHandlesCount, + MoveHandlesCount = specialHeader.MoveHandlesCount + }; + + Data = CreateMessageData(Meta, data, initialLength); + Pid = pid; + } + + public static HipcMessageData WriteResponse( + Span<byte> destination, + int sendStaticCount, + int dataWordsCount, + int copyHandlesCount, + int moveHandlesCount) + { + return WriteMessage(destination, new HipcMetadata() + { + SendStaticsCount = sendStaticCount, + DataWordsCount = dataWordsCount, + CopyHandlesCount = copyHandlesCount, + MoveHandlesCount = moveHandlesCount + }); + } + + public static HipcMessageData WriteMessage(Span<byte> destination, HipcMetadata meta) + { + int initialLength = destination.Length; + + bool hasSpecialHeader = meta.SendPid || meta.CopyHandlesCount != 0 || meta.MoveHandlesCount != 0; + + MemoryMarshal.Cast<byte, Header>(destination)[0] = new Header() + { + Type = (CommandType)meta.Type, + SendStaticsCount = meta.SendStaticsCount, + SendBuffersCount = meta.SendBuffersCount, + ReceiveBuffersCount = meta.ReceiveBuffersCount, + ExchangeBuffersCount = meta.ExchangeBuffersCount, + DataWordsCount = meta.DataWordsCount, + ReceiveStaticMode = meta.ReceiveStaticsCount != 0 ? (meta.ReceiveStaticsCount != AutoReceiveStatic ? meta.ReceiveStaticsCount + 2 : 2) : 0, + HasSpecialHeader = hasSpecialHeader + }; + + destination = destination.Slice(Unsafe.SizeOf<Header>()); + + if (hasSpecialHeader) + { + MemoryMarshal.Cast<byte, SpecialHeader>(destination)[0] = new SpecialHeader() + { + SendPid = meta.SendPid, + CopyHandlesCount = meta.CopyHandlesCount, + MoveHandlesCount = meta.MoveHandlesCount + }; + + destination = destination.Slice(Unsafe.SizeOf<SpecialHeader>()); + + if (meta.SendPid) + { + destination = destination.Slice(sizeof(ulong)); + } + } + + return CreateMessageData(meta, destination, initialLength); + } + + private static HipcMessageData CreateMessageData(HipcMetadata meta, Span<byte> data, int initialLength) + { + Span<int> copyHandles = Span<int>.Empty; + + if (meta.CopyHandlesCount != 0) + { + copyHandles = MemoryMarshal.Cast<byte, int>(data).Slice(0, meta.CopyHandlesCount); + + data = data.Slice(meta.CopyHandlesCount * sizeof(int)); + } + + Span<int> moveHandles = Span<int>.Empty; + + if (meta.MoveHandlesCount != 0) + { + moveHandles = MemoryMarshal.Cast<byte, int>(data).Slice(0, meta.MoveHandlesCount); + + data = data.Slice(meta.MoveHandlesCount * sizeof(int)); + } + + Span<HipcStaticDescriptor> sendStatics = Span<HipcStaticDescriptor>.Empty; + + if (meta.SendStaticsCount != 0) + { + sendStatics = MemoryMarshal.Cast<byte, HipcStaticDescriptor>(data).Slice(0, meta.SendStaticsCount); + + data = data.Slice(meta.SendStaticsCount * Unsafe.SizeOf<HipcStaticDescriptor>()); + } + + Span<HipcBufferDescriptor> sendBuffers = Span<HipcBufferDescriptor>.Empty; + + if (meta.SendBuffersCount != 0) + { + sendBuffers = MemoryMarshal.Cast<byte, HipcBufferDescriptor>(data).Slice(0, meta.SendBuffersCount); + + data = data.Slice(meta.SendBuffersCount * Unsafe.SizeOf<HipcBufferDescriptor>()); + } + + Span<HipcBufferDescriptor> receiveBuffers = Span<HipcBufferDescriptor>.Empty; + + if (meta.ReceiveBuffersCount != 0) + { + receiveBuffers = MemoryMarshal.Cast<byte, HipcBufferDescriptor>(data).Slice(0, meta.ReceiveBuffersCount); + + data = data.Slice(meta.ReceiveBuffersCount * Unsafe.SizeOf<HipcBufferDescriptor>()); + } + + Span<HipcBufferDescriptor> exchangeBuffers = Span<HipcBufferDescriptor>.Empty; + + if (meta.ExchangeBuffersCount != 0) + { + exchangeBuffers = MemoryMarshal.Cast<byte, HipcBufferDescriptor>(data).Slice(0, meta.ExchangeBuffersCount); + + data = data.Slice(meta.ExchangeBuffersCount * Unsafe.SizeOf<HipcBufferDescriptor>()); + } + + Span<uint> dataWords = Span<uint>.Empty; + + if (meta.DataWordsCount != 0) + { + int dataOffset = initialLength - data.Length; + int dataOffsetAligned = BitUtils.AlignUp(dataOffset, 0x10); + + int padding = (dataOffsetAligned - dataOffset) / sizeof(uint); + + dataWords = MemoryMarshal.Cast<byte, uint>(data).Slice(padding, meta.DataWordsCount - padding); + + data = data.Slice(meta.DataWordsCount * sizeof(uint)); + } + + Span<HipcReceiveListEntry> receiveList = Span<HipcReceiveListEntry>.Empty; + + if (meta.ReceiveStaticsCount != 0) + { + int receiveListSize = meta.ReceiveStaticsCount == AutoReceiveStatic ? 1 : meta.ReceiveStaticsCount; + + receiveList = MemoryMarshal.Cast<byte, HipcReceiveListEntry>(data).Slice(0, receiveListSize); + } + + return new HipcMessageData() + { + SendStatics = sendStatics, + SendBuffers = sendBuffers, + ReceiveBuffers = receiveBuffers, + ExchangeBuffers = exchangeBuffers, + DataWords = dataWords, + ReceiveList = receiveList, + CopyHandles = copyHandles, + MoveHandles = moveHandles + }; + } + } +} diff --git a/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessageData.cs b/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessageData.cs new file mode 100644 index 00000000..c83c422c --- /dev/null +++ b/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessageData.cs @@ -0,0 +1,16 @@ +using System; + +namespace Ryujinx.Horizon.Sdk.Sf.Hipc +{ + ref struct HipcMessageData + { + public Span<HipcStaticDescriptor> SendStatics; + public Span<HipcBufferDescriptor> SendBuffers; + public Span<HipcBufferDescriptor> ReceiveBuffers; + public Span<HipcBufferDescriptor> ExchangeBuffers; + public Span<uint> DataWords; + public Span<HipcReceiveListEntry> ReceiveList; + public Span<int> CopyHandles; + public Span<int> MoveHandles; + } +} diff --git a/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMetadata.cs b/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMetadata.cs new file mode 100644 index 00000000..fe13137a --- /dev/null +++ b/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMetadata.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.Horizon.Sdk.Sf.Hipc +{ + struct HipcMetadata + { + public int Type; + public int SendStaticsCount; + public int SendBuffersCount; + public int ReceiveBuffersCount; + public int ExchangeBuffersCount; + public int DataWordsCount; + public int ReceiveStaticsCount; + public bool SendPid; + public int CopyHandlesCount; + public int MoveHandlesCount; + } +} diff --git a/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcReceiveListEntry.cs b/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcReceiveListEntry.cs new file mode 100644 index 00000000..94bf0968 --- /dev/null +++ b/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcReceiveListEntry.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Horizon.Sdk.Sf.Hipc +{ + struct HipcReceiveListEntry + { + private uint _addressLow; + private uint _word1; + + public HipcReceiveListEntry(ulong address, ulong size) + { + _addressLow = (uint)address; + _word1 = (ushort)(address >> 32) | (uint)(size << 16); + } + } +} diff --git a/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcResult.cs b/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcResult.cs new file mode 100644 index 00000000..ef989a98 --- /dev/null +++ b/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcResult.cs @@ -0,0 +1,22 @@ +using Ryujinx.Horizon.Common; + +namespace Ryujinx.Horizon.Sdk.Sf.Hipc +{ + static class HipcResult + { + public const int ModuleId = 11; + + public static Result OutOfSessionMemory => new Result(ModuleId, 102); + public static Result OutOfSessions => new Result(ModuleId, 131); + public static Result PointerBufferTooSmall => new Result(ModuleId, 141); + public static Result OutOfDomains => new Result(ModuleId, 200); + + public static Result InvalidRequestSize => new Result(ModuleId, 402); + public static Result UnknownCommandType => new Result(ModuleId, 403); + + public static Result InvalidCmifRequest => new Result(ModuleId, 420); + + public static Result TargetNotDomain => new Result(ModuleId, 491); + public static Result DomainObjectNotFound => new Result(ModuleId, 492); + } +} diff --git a/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcStaticDescriptor.cs b/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcStaticDescriptor.cs new file mode 100644 index 00000000..5cebf47c --- /dev/null +++ b/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcStaticDescriptor.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.Horizon.Sdk.Sf.Hipc +{ + struct HipcStaticDescriptor + { + private readonly ulong _data; + + public ulong Address => ((((_data >> 2) & 0x70) | ((_data >> 12) & 0xf)) << 32) | (_data >> 32); + public ushort Size => (ushort)(_data >> 16); + public int ReceiveIndex => (int)(_data & 0xf); + + public HipcStaticDescriptor(ulong address, ushort size, int receiveIndex) + { + ulong data = (uint)(receiveIndex & 0xf) | ((uint)size << 16); + + data |= address << 32; + data |= (address >> 20) & 0xf000; + data |= (address >> 30) & 0xffc0; + + _data = data; + } + } +} diff --git a/Ryujinx.Horizon/Sdk/Sf/Hipc/ManagerOptions.cs b/Ryujinx.Horizon/Sdk/Sf/Hipc/ManagerOptions.cs new file mode 100644 index 00000000..e087cb22 --- /dev/null +++ b/Ryujinx.Horizon/Sdk/Sf/Hipc/ManagerOptions.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Horizon.Sdk.Sf.Hipc +{ + struct ManagerOptions + { + public static ManagerOptions Default => new ManagerOptions(0, 0, 0, false); + + public int PointerBufferSize { get; } + public int MaxDomains { get; } + public int MaxDomainObjects { get; } + public bool CanDeferInvokeRequest { get; } + + public ManagerOptions(int pointerBufferSize, int maxDomains, int maxDomainObjects, bool canDeferInvokeRequest) + { + PointerBufferSize = pointerBufferSize; + MaxDomains = maxDomains; + MaxDomainObjects = maxDomainObjects; + CanDeferInvokeRequest = canDeferInvokeRequest; + } + } +} diff --git a/Ryujinx.Horizon/Sdk/Sf/Hipc/ReceiveResult.cs b/Ryujinx.Horizon/Sdk/Sf/Hipc/ReceiveResult.cs new file mode 100644 index 00000000..7c380a01 --- /dev/null +++ b/Ryujinx.Horizon/Sdk/Sf/Hipc/ReceiveResult.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Horizon.Sdk.Sf.Hipc +{ + enum ReceiveResult + { + Success, + Closed, + NeedsRetry + } +} diff --git a/Ryujinx.Horizon/Sdk/Sf/Hipc/Server.cs b/Ryujinx.Horizon/Sdk/Sf/Hipc/Server.cs new file mode 100644 index 00000000..923f2d52 --- /dev/null +++ b/Ryujinx.Horizon/Sdk/Sf/Hipc/Server.cs @@ -0,0 +1,36 @@ +using Ryujinx.Horizon.Sdk.OsTypes; +using Ryujinx.Horizon.Sdk.Sf.Cmif; +using Ryujinx.Horizon.Sdk.Sm; + +namespace Ryujinx.Horizon.Sdk.Sf.Hipc +{ + class Server : MultiWaitHolderOfHandle + { + public int PortIndex { get; } + public int PortHandle { get; } + public ServiceName Name { get; } + public bool Managed { get; } + public ServiceObjectHolder StaticObject { get; } + + public Server( + int portIndex, + int portHandle, + ServiceName name, + bool managed, + ServiceObjectHolder staticHoder) : base(portHandle) + { + PortHandle = portHandle; + Name = name; + Managed = managed; + + if (staticHoder != null) + { + StaticObject = staticHoder; + } + else + { + PortIndex = portIndex; + } + } + } +} diff --git a/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerDomainSessionManager.cs b/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerDomainSessionManager.cs new file mode 100644 index 00000000..d920a659 --- /dev/null +++ b/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerDomainSessionManager.cs @@ -0,0 +1,23 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf.Cmif; +using System; + +namespace Ryujinx.Horizon.Sdk.Sf.Hipc +{ + class ServerDomainSessionManager : ServerSessionManager + { + public ServerDomainManager Domain { get; } + + public ServerDomainSessionManager(int entryCount, int maxDomains) + { + Domain = new ServerDomainManager(entryCount, maxDomains); + } + + protected override Result DispatchManagerRequest(ServerSession session, Span<byte> inMessage, Span<byte> outMessage) + { + HipcManager hipcManager = new HipcManager(this, session); + + return DispatchRequest(new ServiceObjectHolder(hipcManager), session, inMessage, outMessage); + } + } +} diff --git a/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManager.cs b/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManager.cs new file mode 100644 index 00000000..5bb2de25 --- /dev/null +++ b/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManager.cs @@ -0,0 +1,198 @@ +using Ryujinx.Horizon.Sdk.OsTypes; +using Ryujinx.Horizon.Sdk.Sf.Cmif; +using Ryujinx.Horizon.Sdk.Sm; +using System; +using System.Collections.Generic; +using System.Numerics; + +namespace Ryujinx.Horizon.Sdk.Sf.Hipc +{ + class ServerManager : ServerManagerBase, IDisposable + { + private readonly SmApi _sm; + private readonly int _pointerBufferSize; + private readonly bool _canDeferInvokeRequest; + private readonly int _maxSessions; + + private ulong _pointerBuffersBaseAddress; + private ulong _savedMessagesBaseAddress; + + private readonly object _resourceLock; + private readonly ulong[] _sessionAllocationBitmap; + private readonly HashSet<ServerSession> _sessions; + private readonly HashSet<Server> _servers; + + public ServerManager(HeapAllocator allocator, SmApi sm, int maxPorts, ManagerOptions options, int maxSessions) : base(sm, options) + { + _sm = sm; + _pointerBufferSize = options.PointerBufferSize; + _canDeferInvokeRequest = options.CanDeferInvokeRequest; + _maxSessions = maxSessions; + + if (allocator != null) + { + _pointerBuffersBaseAddress = allocator.Allocate((ulong)maxSessions * (ulong)options.PointerBufferSize); + + if (options.CanDeferInvokeRequest) + { + _savedMessagesBaseAddress = allocator.Allocate((ulong)maxSessions * (ulong)Api.TlsMessageBufferSize); + } + } + + _resourceLock = new object(); + _sessionAllocationBitmap = new ulong[(maxSessions + 63) / 64]; + _sessions = new HashSet<ServerSession>(); + _servers = new HashSet<Server>(); + } + + private PointerAndSize GetObjectBySessionIndex(ServerSession session, ulong baseAddress, ulong size) + { + return new PointerAndSize(baseAddress + (ulong)session.SessionIndex * size, size); + } + + protected override ServerSession AllocateSession(int sessionHandle, ServiceObjectHolder obj) + { + int sessionIndex = -1; + + lock (_resourceLock) + { + if (_sessions.Count >= _maxSessions) + { + return null; + } + + for (int i = 0; i <_sessionAllocationBitmap.Length; i++) + { + ref ulong mask = ref _sessionAllocationBitmap[i]; + + if (mask != ulong.MaxValue) + { + int bit = BitOperations.TrailingZeroCount(~mask); + sessionIndex = i * 64 + bit; + mask |= 1UL << bit; + + break; + } + } + + if (sessionIndex == -1) + { + return null; + } + + ServerSession session = new ServerSession(sessionIndex, sessionHandle, obj); + + _sessions.Add(session); + + return session; + } + } + + protected override void FreeSession(ServerSession session) + { + if (session.ServiceObjectHolder.ServiceObject is IDisposable disposableObj) + { + disposableObj.Dispose(); + } + + lock (_resourceLock) + { + _sessionAllocationBitmap[session.SessionIndex / 64] &= ~(1UL << (session.SessionIndex & 63)); + _sessions.Remove(session); + } + } + + protected override Server AllocateServer( + int portIndex, + int portHandle, + ServiceName name, + bool managed, + ServiceObjectHolder staticHoder) + { + lock (_resourceLock) + { + Server server = new Server(portIndex, portHandle, name, managed, staticHoder); + + _servers.Add(server); + + return server; + } + } + + protected override void DestroyServer(Server server) + { + lock (_resourceLock) + { + server.UnlinkFromMultiWaitHolder(); + Os.FinalizeMultiWaitHolder(server); + + if (server.Managed) + { + // We should AbortOnFailure, but sometimes SM is already gone when this is called, + // so let's just ignore potential errors. + _sm.UnregisterService(server.Name); + + HorizonStatic.Syscall.CloseHandle(server.PortHandle); + } + + _servers.Remove(server); + } + } + + protected override PointerAndSize GetSessionPointerBuffer(ServerSession session) + { + if (_pointerBufferSize > 0) + { + return GetObjectBySessionIndex(session, _pointerBuffersBaseAddress, (ulong)_pointerBufferSize); + } + else + { + return PointerAndSize.Empty; + } + } + + protected override PointerAndSize GetSessionSavedMessageBuffer(ServerSession session) + { + if (_canDeferInvokeRequest) + { + return GetObjectBySessionIndex(session, _savedMessagesBaseAddress, Api.TlsMessageBufferSize); + } + else + { + return PointerAndSize.Empty; + } + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + lock (_resourceLock) + { + ServerSession[] sessionsToClose = new ServerSession[_sessions.Count]; + + _sessions.CopyTo(sessionsToClose); + + foreach (ServerSession session in sessionsToClose) + { + CloseSessionImpl(session); + } + + Server[] serversToClose = new Server[_servers.Count]; + + _servers.CopyTo(serversToClose); + + foreach (Server server in serversToClose) + { + DestroyServer(server); + } + } + } + } + + public void Dispose() + { + Dispose(true); + } + } +} diff --git a/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManagerBase.cs b/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManagerBase.cs new file mode 100644 index 00000000..68cae6bc --- /dev/null +++ b/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManagerBase.cs @@ -0,0 +1,307 @@ +using Ryujinx.Horizon.Sdk.OsTypes; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf.Cmif; +using Ryujinx.Horizon.Sdk.Sm; +using System; + +namespace Ryujinx.Horizon.Sdk.Sf.Hipc +{ + class ServerManagerBase : ServerDomainSessionManager + { + private readonly SmApi _sm; + + private bool _canDeferInvokeRequest; + + private readonly MultiWait _multiWait; + private readonly MultiWait _waitList; + + private readonly object _multiWaitSelectionLock; + private readonly object _waitListLock; + + private readonly Event _requestStopEvent; + private readonly Event _notifyEvent; + + private readonly MultiWaitHolderBase _requestStopEventHolder; + private readonly MultiWaitHolderBase _notifyEventHolder; + + private enum UserDataTag + { + Server = 1, + Session = 2 + } + + public ServerManagerBase(SmApi sm, ManagerOptions options) : base(options.MaxDomainObjects, options.MaxDomains) + { + _sm = sm; + _canDeferInvokeRequest = options.CanDeferInvokeRequest; + + _multiWait = new MultiWait(); + _waitList = new MultiWait(); + + _multiWaitSelectionLock = new object(); + _waitListLock = new object(); + + _requestStopEvent = new Event(EventClearMode.ManualClear); + _notifyEvent = new Event(EventClearMode.ManualClear); + + _requestStopEventHolder = new MultiWaitHolderOfEvent(_requestStopEvent); + _multiWait.LinkMultiWaitHolder(_requestStopEventHolder); + _notifyEventHolder = new MultiWaitHolderOfEvent(_notifyEvent); + _multiWait.LinkMultiWaitHolder(_notifyEventHolder); + } + + public void RegisterObjectForServer(IServiceObject staticObject, int portHandle) + { + RegisterServerImpl(0, new ServiceObjectHolder(staticObject), portHandle); + } + + public Result RegisterObjectForServer(IServiceObject staticObject, ServiceName name, int maxSessions) + { + return RegisterServerImpl(0, new ServiceObjectHolder(staticObject), name, maxSessions); + } + + public void RegisterServer(int portIndex, int portHandle) + { + RegisterServerImpl(portIndex, null, portHandle); + } + + public Result RegisterServer(int portIndex, ServiceName name, int maxSessions) + { + return RegisterServerImpl(portIndex, null, name, maxSessions); + } + + private void RegisterServerImpl(int portIndex, ServiceObjectHolder staticHolder, int portHandle) + { + Server server = AllocateServer(portIndex, portHandle, ServiceName.Invalid, managed: false, staticHolder); + RegisterServerImpl(server); + } + + private Result RegisterServerImpl(int portIndex, ServiceObjectHolder staticHolder, ServiceName name, int maxSessions) + { + Result result = _sm.RegisterService(out int portHandle, name, maxSessions, isLight: false); + + if (result.IsFailure) + { + return result; + } + + Server server = AllocateServer(portIndex, portHandle, name, managed: true, staticHolder); + RegisterServerImpl(server); + + return Result.Success; + } + + private void RegisterServerImpl(Server server) + { + server.UserData = UserDataTag.Server; + + _multiWait.LinkMultiWaitHolder(server); + } + + protected virtual Result OnNeedsToAccept(int portIndex, Server server) + { + throw new NotSupportedException(); + } + + public void ServiceRequests() + { + while (WaitAndProcessRequestsImpl()); + } + + public void WaitAndProcessRequests() + { + WaitAndProcessRequestsImpl(); + } + + private bool WaitAndProcessRequestsImpl() + { + try + { + MultiWaitHolder multiWait = WaitSignaled(); + + if (multiWait == null) + { + return false; + } + + DebugUtil.Assert(Process(multiWait).IsSuccess); + + return HorizonStatic.ThreadContext.Running; + } + catch (ThreadTerminatedException) + { + return false; + } + } + + private MultiWaitHolder WaitSignaled() + { + lock (_multiWaitSelectionLock) + { + while (true) + { + ProcessWaitList(); + + MultiWaitHolder selected = _multiWait.WaitAny(); + + if (selected == _requestStopEventHolder) + { + return null; + } + else if (selected == _notifyEventHolder) + { + _notifyEvent.Clear(); + } + else + { + selected.UnlinkFromMultiWaitHolder(); + + return selected; + } + } + } + } + + public void ResumeProcessing() + { + _requestStopEvent.Clear(); + } + + public void RequestStopProcessing() + { + _requestStopEvent.Signal(); + } + + protected override void RegisterSessionToWaitList(ServerSession session) + { + session.HasReceived = false; + session.UserData = UserDataTag.Session; + RegisterToWaitList(session); + } + + private void RegisterToWaitList(MultiWaitHolder holder) + { + lock (_waitListLock) + { + _waitList.LinkMultiWaitHolder(holder); + _notifyEvent.Signal(); + } + } + + private void ProcessWaitList() + { + lock (_waitListLock) + { + _multiWait.MoveAllFrom(_waitList); + } + } + + private Result Process(MultiWaitHolder holder) + { + switch ((UserDataTag)holder.UserData) + { + case UserDataTag.Server: + return ProcessForServer(holder); + case UserDataTag.Session: + return ProcessForSession(holder); + default: + throw new NotImplementedException(((UserDataTag)holder.UserData).ToString()); + } + } + + private Result ProcessForServer(MultiWaitHolder holder) + { + DebugUtil.Assert((UserDataTag)holder.UserData == UserDataTag.Server); + + Server server = (Server)holder; + + try + { + if (server.StaticObject != null) + { + return AcceptSession(server.PortHandle, server.StaticObject.Clone()); + } + else + { + return OnNeedsToAccept(server.PortIndex, server); + } + } + finally + { + RegisterToWaitList(server); + } + } + + private Result ProcessForSession(MultiWaitHolder holder) + { + DebugUtil.Assert((UserDataTag)holder.UserData == UserDataTag.Session); + + ServerSession session = (ServerSession)holder; + + using var tlsMessage = HorizonStatic.AddressSpace.GetWritableRegion(HorizonStatic.ThreadContext.TlsAddress, Api.TlsMessageBufferSize); + + Result result; + + if (_canDeferInvokeRequest) + { + // If the request is deferred, we save the message on a temporary buffer to process it later. + using var savedMessage = HorizonStatic.AddressSpace.GetWritableRegion(session.SavedMessage.Address, (int)session.SavedMessage.Size); + + DebugUtil.Assert(tlsMessage.Memory.Length == savedMessage.Memory.Length); + + if (!session.HasReceived) + { + result = ReceiveRequest(session, tlsMessage.Memory.Span); + + if (result.IsFailure) + { + return result; + } + + session.HasReceived = true; + tlsMessage.Memory.Span.CopyTo(savedMessage.Memory.Span); + } + else + { + savedMessage.Memory.Span.CopyTo(tlsMessage.Memory.Span); + } + + result = ProcessRequest(session, tlsMessage.Memory.Span); + + if (result.IsFailure && !SfResult.Invalidated(result)) + { + return result; + } + } + else + { + if (!session.HasReceived) + { + result = ReceiveRequest(session, tlsMessage.Memory.Span); + + if (result.IsFailure) + { + return result; + } + + session.HasReceived = true; + } + + result = ProcessRequest(session, tlsMessage.Memory.Span); + + if (result.IsFailure) + { + // Those results are not valid because the service does not support deferral. + if (SfResult.RequestDeferred(result) || SfResult.Invalidated(result)) + { + result.AbortOnFailure(); + } + + return result; + } + } + + return Result.Success; + } + } +} diff --git a/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerSession.cs b/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerSession.cs new file mode 100644 index 00000000..eb98fefd --- /dev/null +++ b/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerSession.cs @@ -0,0 +1,23 @@ +using Ryujinx.Horizon.Sdk.OsTypes; +using Ryujinx.Horizon.Sdk.Sf.Cmif; + +namespace Ryujinx.Horizon.Sdk.Sf.Hipc +{ + class ServerSession : MultiWaitHolderOfHandle + { + public ServiceObjectHolder ServiceObjectHolder { get; set; } + public PointerAndSize PointerBuffer { get; set; } + public PointerAndSize SavedMessage { get; set; } + public int SessionIndex { get; } + public int SessionHandle { get; } + public bool IsClosed { get; set; } + public bool HasReceived { get; set; } + + public ServerSession(int index, int handle, ServiceObjectHolder obj) : base(handle) + { + ServiceObjectHolder = obj; + SessionIndex = index; + SessionHandle = handle; + } + } +} diff --git a/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerSessionManager.cs b/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerSessionManager.cs new file mode 100644 index 00000000..e85892f2 --- /dev/null +++ b/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerSessionManager.cs @@ -0,0 +1,335 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.OsTypes; +using Ryujinx.Horizon.Sdk.Sf.Cmif; +using Ryujinx.Horizon.Sdk.Sm; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Sf.Hipc +{ + class ServerSessionManager + { + public Result AcceptSession(int portHandle, ServiceObjectHolder obj) + { + return AcceptSession(out _, portHandle, obj); + } + + private Result AcceptSession(out ServerSession session, int portHandle, ServiceObjectHolder obj) + { + return AcceptSessionImpl(out session, portHandle, obj); + } + + private Result AcceptSessionImpl(out ServerSession session, int portHandle, ServiceObjectHolder obj) + { + session = null; + + Result result = HorizonStatic.Syscall.AcceptSession(out int sessionHandle, portHandle); + + if (result.IsFailure) + { + return result; + } + + bool succeeded = false; + + try + { + result = RegisterSessionImpl(out session, sessionHandle, obj); + + if (result.IsFailure) + { + return result; + } + + succeeded = true; + } + finally + { + if (!succeeded) + { + HorizonStatic.Syscall.CloseHandle(sessionHandle); + } + } + + return Result.Success; + } + + public Result RegisterSession(int sessionHandle, ServiceObjectHolder obj) + { + return RegisterSession(out _, sessionHandle, obj); + } + + public Result RegisterSession(out ServerSession session, int sessionHandle, ServiceObjectHolder obj) + { + return RegisterSessionImpl(out session, sessionHandle, obj); + } + + private Result RegisterSessionImpl(out ServerSession session, int sessionHandle, ServiceObjectHolder obj) + { + Result result = CreateSessionImpl(out session, sessionHandle, obj); + + if (result.IsFailure) + { + return result; + } + + session.PointerBuffer = GetSessionPointerBuffer(session); + session.SavedMessage = GetSessionSavedMessageBuffer(session); + + RegisterSessionToWaitList(session); + return Result.Success; + } + + protected virtual void RegisterSessionToWaitList(ServerSession session) + { + throw new NotSupportedException(); + } + + private Result CreateSessionImpl(out ServerSession session, int sessionHandle, ServiceObjectHolder obj) + { + session = AllocateSession(sessionHandle, obj); + + if (session == null) + { + return HipcResult.OutOfSessionMemory; + } + + return Result.Success; + } + + protected virtual ServerSession AllocateSession(int sessionHandle, ServiceObjectHolder obj) + { + throw new NotSupportedException(); + } + + protected virtual void FreeSession(ServerSession session) + { + throw new NotSupportedException(); + } + + protected virtual Server AllocateServer( + int portIndex, + int portHandle, + ServiceName name, + bool managed, + ServiceObjectHolder staticHoder) + { + throw new NotSupportedException(); + } + + protected virtual void DestroyServer(Server server) + { + throw new NotSupportedException(); + } + + protected virtual PointerAndSize GetSessionPointerBuffer(ServerSession session) + { + throw new NotSupportedException(); + } + + protected virtual PointerAndSize GetSessionSavedMessageBuffer(ServerSession session) + { + throw new NotSupportedException(); + } + + private void DestroySession(ServerSession session) + { + FreeSession(session); + } + + protected void CloseSessionImpl(ServerSession session) + { + int sessionHandle = session.Handle; + Os.FinalizeMultiWaitHolder(session); + DestroySession(session); + HorizonStatic.Syscall.CloseHandle(sessionHandle).AbortOnFailure(); + } + + private static CommandType GetCmifCommandType(ReadOnlySpan<byte> message) + { + return MemoryMarshal.Cast<byte, Header>(message)[0].Type; + } + + public Result ProcessRequest(ServerSession session, Span<byte> message) + { + if (session.IsClosed || GetCmifCommandType(message) == CommandType.Close) + { + CloseSessionImpl(session); + return Result.Success; + } + else + { + Result result = ProcessRequestImpl(session, message, message); + + if (result.IsSuccess) + { + RegisterSessionToWaitList(session); + return Result.Success; + } + else if (SfResult.RequestContextChanged(result)) + { + return result; + } + else + { + Logger.Warning?.Print(LogClass.KernelIpc, $"Request processing returned error {result}"); + + CloseSessionImpl(session); + return Result.Success; + } + } + } + + private Result ProcessRequestImpl(ServerSession session, Span<byte> inMessage, Span<byte> outMessage) + { + CommandType commandType = GetCmifCommandType(inMessage); + + using var _ = new ScopedInlineContextChange(GetInlineContext(commandType, inMessage)); + + switch (commandType) + { + case CommandType.Request: + case CommandType.RequestWithContext: + return DispatchRequest(session.ServiceObjectHolder, session, inMessage, outMessage); + case CommandType.Control: + case CommandType.ControlWithContext: + return DispatchManagerRequest(session, inMessage, outMessage); + default: + return HipcResult.UnknownCommandType; + } + } + + private static int GetInlineContext(CommandType commandType, ReadOnlySpan<byte> inMessage) + { + switch (commandType) + { + case CommandType.RequestWithContext: + case CommandType.ControlWithContext: + if (inMessage.Length >= 0x10) + { + return MemoryMarshal.Cast<byte, int>(inMessage)[3]; + } + break; + } + + return 0; + } + + protected Result ReceiveRequest(ServerSession session, Span<byte> message) + { + return ReceiveRequestImpl(session, message); + } + + private Result ReceiveRequestImpl(ServerSession session, Span<byte> message) + { + PointerAndSize pointerBuffer = session.PointerBuffer; + + while (true) + { + if (pointerBuffer.Address != 0) + { + HipcMessageData messageData = HipcMessage.WriteMessage(message, new HipcMetadata() + { + Type = (int)CommandType.Invalid, + ReceiveStaticsCount = HipcMessage.AutoReceiveStatic + }); + + messageData.ReceiveList[0] = new HipcReceiveListEntry(pointerBuffer.Address, pointerBuffer.Size); + } + else + { + MemoryMarshal.Cast<byte, Header>(message)[0] = new Header() + { + Type = CommandType.Invalid + }; + } + + Result result = Api.Receive(out ReceiveResult recvResult, session.Handle, message); + + if (result.IsFailure) + { + return result; + } + + switch (recvResult) + { + case ReceiveResult.Success: + session.IsClosed = false; + return Result.Success; + case ReceiveResult.Closed: + session.IsClosed = true; + return Result.Success; + } + } + } + + protected virtual Result DispatchManagerRequest(ServerSession session, Span<byte> inMessage, Span<byte> outMessage) + { + return SfResult.NotSupported; + } + + protected virtual Result DispatchRequest( + ServiceObjectHolder objectHolder, + ServerSession session, + Span<byte> inMessage, + Span<byte> outMessage) + { + HipcMessage request; + + try + { + request = new HipcMessage(inMessage); + } + catch (ArgumentOutOfRangeException) + { + return HipcResult.InvalidRequestSize; + } + + var dispatchCtx = new ServiceDispatchContext() + { + ServiceObject = objectHolder.ServiceObject, + Manager = this, + Session = session, + HandlesToClose = new HandlesToClose(), + PointerBuffer = session.PointerBuffer, + InMessageBuffer = inMessage, + OutMessageBuffer = outMessage, + Request = request + }; + + ReadOnlySpan<byte> inRawData = MemoryMarshal.Cast<uint, byte>(dispatchCtx.Request.Data.DataWords); + + int inRawSize = dispatchCtx.Request.Meta.DataWordsCount * sizeof(uint); + + if (inRawSize < 0x10) + { + return HipcResult.InvalidRequestSize; + } + + Result result = objectHolder.ProcessMessage(ref dispatchCtx, inRawData); + + if (result.IsFailure) + { + return result; + } + + result = Api.Reply(session.SessionHandle, outMessage); + + ref var handlesToClose = ref dispatchCtx.HandlesToClose; + + for (int i = 0; i < handlesToClose.Count; i++) + { + HorizonStatic.Syscall.CloseHandle(handlesToClose[i]).AbortOnFailure(); + } + + return result; + } + + public ServerSessionManager GetSessionManagerByTag(uint tag) + { + // Official FW does not do anything with the tag currently. + return this; + } + } +} diff --git a/Ryujinx.Horizon/Sdk/Sf/Hipc/SpecialHeader.cs b/Ryujinx.Horizon/Sdk/Sf/Hipc/SpecialHeader.cs new file mode 100644 index 00000000..8b747626 --- /dev/null +++ b/Ryujinx.Horizon/Sdk/Sf/Hipc/SpecialHeader.cs @@ -0,0 +1,27 @@ +using Ryujinx.Common.Utilities; + +namespace Ryujinx.Horizon.Sdk.Sf.Hipc +{ + struct SpecialHeader + { + private uint _word; + + public bool SendPid + { + get => _word.Extract(0); + set => _word = _word.Insert(0, value); + } + + public int CopyHandlesCount + { + get => (int)_word.Extract(1, 4); + set => _word = _word.Insert(1, 4, (uint)value); + } + + public int MoveHandlesCount + { + get => (int)_word.Extract(5, 4); + set => _word = _word.Insert(5, 4, (uint)value); + } + } +} diff --git a/Ryujinx.Horizon/Sdk/Sf/HipcCommandProcessor.cs b/Ryujinx.Horizon/Sdk/Sf/HipcCommandProcessor.cs new file mode 100644 index 00000000..53202ede --- /dev/null +++ b/Ryujinx.Horizon/Sdk/Sf/HipcCommandProcessor.cs @@ -0,0 +1,421 @@ +using Ryujinx.Common; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf.Cmif; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Sf +{ + class HipcCommandProcessor : ServerMessageProcessor + { + private readonly CommandArg[] _args; + + private readonly int[] _inOffsets; + private readonly int[] _outOffsets; + private readonly PointerAndSize[] _bufferRanges; + + private readonly bool _hasInProcessIdHolder; + private readonly int _inObjectsCount; + private readonly int _outObjectsCount; + private readonly int _inMapAliasBuffersCount; + private readonly int _outMapAliasBuffersCount; + private readonly int _inPointerBuffersCount; + private readonly int _outPointerBuffersCount; + private readonly int _outFixedSizePointerBuffersCount; + private readonly int _inMoveHandlesCount; + private readonly int _inCopyHandlesCount; + private readonly int _outMoveHandlesCount; + private readonly int _outCopyHandlesCount; + + public int FunctionArgumentsCount => _args.Length; + + public int InRawDataSize => BitUtils.AlignUp(_inOffsets[^1], sizeof(ushort)); + public int OutRawDataSize => BitUtils.AlignUp(_outOffsets[^1], sizeof(uint)); + + private int OutUnfixedSizePointerBuffersCount => _outPointerBuffersCount - _outFixedSizePointerBuffersCount; + + public HipcCommandProcessor(CommandArg[] args) + { + _args = args; + + for (int i = 0; i < args.Length; i++) + { + var argInfo = args[i]; + + switch (argInfo.Type) + { + case CommandArgType.Buffer: + var flags = argInfo.BufferFlags; + + if (flags.HasFlag(HipcBufferFlags.In)) + { + if (flags.HasFlag(HipcBufferFlags.AutoSelect)) + { + _inMapAliasBuffersCount++; + _inPointerBuffersCount++; + } + else if (flags.HasFlag(HipcBufferFlags.MapAlias)) + { + _inMapAliasBuffersCount++; + } + else if (flags.HasFlag(HipcBufferFlags.Pointer)) + { + _inPointerBuffersCount++; + } + } + else + { + bool autoSelect = flags.HasFlag(HipcBufferFlags.AutoSelect); + if (autoSelect || flags.HasFlag(HipcBufferFlags.Pointer)) + { + _outPointerBuffersCount++; + + if (flags.HasFlag(HipcBufferFlags.FixedSize)) + { + _outFixedSizePointerBuffersCount++; + } + } + + if (autoSelect || flags.HasFlag(HipcBufferFlags.MapAlias)) + { + _outMapAliasBuffersCount++; + } + } + break; + case CommandArgType.InCopyHandle: + _inCopyHandlesCount++; + break; + case CommandArgType.InMoveHandle: + _inMoveHandlesCount++; + break; + case CommandArgType.InObject: + _inObjectsCount++; + break; + case CommandArgType.ProcessId: + _hasInProcessIdHolder = true; + break; + case CommandArgType.OutCopyHandle: + _outCopyHandlesCount++; + break; + case CommandArgType.OutMoveHandle: + _outMoveHandlesCount++; + break; + case CommandArgType.OutObject: + _outObjectsCount++; + break; + } + } + + _inOffsets = RawDataOffsetCalculator.Calculate(args.Where(x => x.Type == CommandArgType.InArgument).ToArray()); + _outOffsets = RawDataOffsetCalculator.Calculate(args.Where(x => x.Type == CommandArgType.OutArgument).ToArray()); + _bufferRanges = new PointerAndSize[args.Length]; + } + + public int GetInArgOffset(int argIndex) + { + return _inOffsets[argIndex]; + } + + public int GetOutArgOffset(int argIndex) + { + return _outOffsets[argIndex]; + } + + public PointerAndSize GetBufferRange(int argIndex) + { + return _bufferRanges[argIndex]; + } + + public Result ProcessBuffers(ref ServiceDispatchContext context, bool[] isBufferMapAlias, ServerMessageRuntimeMetadata runtimeMetadata) + { + bool mapAliasBuffersValid = true; + + ulong pointerBufferTail = context.PointerBuffer.Address; + ulong pointerBufferHead = pointerBufferTail + context.PointerBuffer.Size; + + int sendMapAliasIndex = 0; + int recvMapAliasIndex = 0; + int sendPointerIndex = 0; + int unfixedRecvPointerIndex = 0; + + for (int i = 0; i < _args.Length; i++) + { + if (_args[i].Type != CommandArgType.Buffer) + { + continue; + } + + var flags = _args[i].BufferFlags; + bool isMapAlias; + + if (flags.HasFlag(HipcBufferFlags.MapAlias)) + { + isMapAlias = true; + } + else if (flags.HasFlag(HipcBufferFlags.Pointer)) + { + isMapAlias = false; + } + else /* if (flags.HasFlag(HipcBufferFlags.HipcAutoSelect)) */ + { + var descriptor = flags.HasFlag(HipcBufferFlags.In) + ? context.Request.Data.SendBuffers[sendMapAliasIndex] + : context.Request.Data.ReceiveBuffers[recvMapAliasIndex]; + + isMapAlias = descriptor.Address != 0UL; + } + + isBufferMapAlias[i] = isMapAlias; + + if (isMapAlias) + { + var descriptor = flags.HasFlag(HipcBufferFlags.In) + ? context.Request.Data.SendBuffers[sendMapAliasIndex++] + : context.Request.Data.ReceiveBuffers[recvMapAliasIndex++]; + + _bufferRanges[i] = new PointerAndSize(descriptor.Address, descriptor.Size); + + if (!IsMapTransferModeValid(flags, descriptor.Mode)) + { + mapAliasBuffersValid = false; + } + } + else + { + if (flags.HasFlag(HipcBufferFlags.In)) + { + var descriptor = context.Request.Data.SendStatics[sendPointerIndex++]; + ulong address = descriptor.Address; + ulong size = descriptor.Size; + _bufferRanges[i] = new PointerAndSize(address, size); + + if (size != 0) + { + pointerBufferTail = Math.Max(pointerBufferTail, address + size); + } + } + else /* if (flags.HasFlag(HipcBufferFlags.Out)) */ + { + ulong size; + + if (flags.HasFlag(HipcBufferFlags.FixedSize)) + { + size = _args[i].BufferFixedSize; + } + else + { + var data = MemoryMarshal.Cast<uint, byte>(context.Request.Data.DataWords); + var recvPointerSizes = MemoryMarshal.Cast<byte, ushort>(data.Slice(runtimeMetadata.UnfixedOutPointerSizeOffset)); + size = recvPointerSizes[unfixedRecvPointerIndex++]; + } + + pointerBufferHead = BitUtils.AlignDown(pointerBufferHead - size, 0x10UL); + _bufferRanges[i] = new PointerAndSize(pointerBufferHead, size); + } + } + } + + if (!mapAliasBuffersValid) + { + return HipcResult.InvalidCmifRequest; + } + + if (_outPointerBuffersCount != 0 && pointerBufferTail > pointerBufferHead) + { + return HipcResult.PointerBufferTooSmall; + } + + return Result.Success; + } + + private static bool IsMapTransferModeValid(HipcBufferFlags flags, HipcBufferMode mode) + { + if (flags.HasFlag(HipcBufferFlags.MapTransferAllowsNonSecure)) + { + return mode == HipcBufferMode.NonSecure; + } + else if (flags.HasFlag(HipcBufferFlags.MapTransferAllowsNonDevice)) + { + return mode == HipcBufferMode.NonDevice; + } + else + { + return mode == HipcBufferMode.Normal; + } + } + + public void SetOutBuffers(HipcMessageData response, bool[] isBufferMapAlias) + { + int recvPointerIndex = 0; + + for (int i = 0; i < _args.Length; i++) + { + if (_args[i].Type != CommandArgType.Buffer) + { + continue; + } + + var flags = _args[i].BufferFlags; + if (flags.HasFlag(HipcBufferFlags.Out)) + { + var buffer = _bufferRanges[i]; + + if (flags.HasFlag(HipcBufferFlags.Pointer)) + { + response.SendStatics[recvPointerIndex] = new HipcStaticDescriptor(buffer.Address, (ushort)buffer.Size, recvPointerIndex); + } + else if (flags.HasFlag(HipcBufferFlags.AutoSelect)) + { + if (!isBufferMapAlias[i]) + { + response.SendStatics[recvPointerIndex] = new HipcStaticDescriptor(buffer.Address, (ushort)buffer.Size, recvPointerIndex); + } + else + { + response.SendStatics[recvPointerIndex] = new HipcStaticDescriptor(0UL, 0, recvPointerIndex); + } + } + + recvPointerIndex++; + } + } + } + + public override void SetImplementationProcessor(ServerMessageProcessor impl) + { + // We don't need to do anything here as this should be always the last processor to be called. + } + + public override ServerMessageRuntimeMetadata GetRuntimeMetadata() + { + return new ServerMessageRuntimeMetadata( + (ushort)InRawDataSize, + (ushort)OutRawDataSize, + (byte)Unsafe.SizeOf<CmifInHeader>(), + (byte)Unsafe.SizeOf<CmifOutHeader>(), + (byte)_inObjectsCount, + (byte)_outObjectsCount); + } + + public override Result PrepareForProcess(ref ServiceDispatchContext context, ServerMessageRuntimeMetadata runtimeMetadata) + { + ref var meta = ref context.Request.Meta; + bool requestValid = true; + requestValid &= meta.SendPid == _hasInProcessIdHolder; + requestValid &= meta.SendStaticsCount == _inPointerBuffersCount; + requestValid &= meta.SendBuffersCount == _inMapAliasBuffersCount; + requestValid &= meta.ReceiveBuffersCount == _outMapAliasBuffersCount; + requestValid &= meta.ExchangeBuffersCount == 0; + requestValid &= meta.CopyHandlesCount == _inCopyHandlesCount; + requestValid &= meta.MoveHandlesCount == _inMoveHandlesCount; + + int rawSizeInBytes = meta.DataWordsCount * sizeof(uint); + int commandRawSize = BitUtils.AlignUp(runtimeMetadata.UnfixedOutPointerSizeOffset + (OutUnfixedSizePointerBuffersCount * sizeof(ushort)), sizeof(uint)); + requestValid &= rawSizeInBytes >= commandRawSize; + + return requestValid ? Result.Success : HipcResult.InvalidCmifRequest; + } + + public Result GetInObjects(ServerMessageProcessor processor, Span<IServiceObject> objects) + { + if (objects.Length == 0) + { + return Result.Success; + } + + ServiceObjectHolder[] inObjects = new ServiceObjectHolder[objects.Length]; + Result result = processor.GetInObjects(inObjects); + + if (result.IsFailure) + { + return result; + } + + int inObjectIndex = 0; + + for (int i = 0; i < _args.Length; i++) + { + if (_args[i].Type == CommandArgType.InObject) + { + int index = inObjectIndex++; + var inObject = inObjects[index]; + + objects[index] = inObject?.ServiceObject; + } + } + + return Result.Success; + } + + public override Result GetInObjects(Span<ServiceObjectHolder> inObjects) + { + return SfResult.NotSupported; + } + + public override HipcMessageData PrepareForReply(scoped ref ServiceDispatchContext context, out Span<byte> outRawData, ServerMessageRuntimeMetadata runtimeMetadata) + { + int rawDataSize = OutRawDataSize + runtimeMetadata.OutHeadersSize; + var response = HipcMessage.WriteResponse( + context.OutMessageBuffer, + _outPointerBuffersCount, + (BitUtils.AlignUp(rawDataSize, 4) + 0x10) / sizeof(uint), + _outCopyHandlesCount, + _outMoveHandlesCount + runtimeMetadata.OutObjectsCount); + outRawData = MemoryMarshal.Cast<uint, byte>(response.DataWords); + return response; + } + + public override void PrepareForErrorReply(scoped ref ServiceDispatchContext context, out Span<byte> outRawData, ServerMessageRuntimeMetadata runtimeMetadata) + { + int rawDataSize = runtimeMetadata.OutHeadersSize; + var response = HipcMessage.WriteResponse( + context.OutMessageBuffer, + 0, + (BitUtils.AlignUp(rawDataSize, 4) + 0x10) / sizeof(uint), + 0, + 0); + outRawData = MemoryMarshal.Cast<uint, byte>(response.DataWords); + } + + public void SetOutObjects(ref ServiceDispatchContext context, HipcMessageData response, Span<IServiceObject> objects) + { + if (objects.Length == 0) + { + return; + } + + ServiceObjectHolder[] outObjects = new ServiceObjectHolder[objects.Length]; + + for (int i = 0; i < objects.Length; i++) + { + outObjects[i] = objects[i] != null ? new ServiceObjectHolder(objects[i]) : null; + } + + context.Processor.SetOutObjects(ref context, response, outObjects); + } + + public override void SetOutObjects(scoped ref ServiceDispatchContext context, HipcMessageData response, Span<ServiceObjectHolder> outObjects) + { + for (int index = 0; index < _outObjectsCount; index++) + { + SetOutObjectImpl(index, response, context.Manager, outObjects[index]); + } + } + + private void SetOutObjectImpl(int index, HipcMessageData response, ServerSessionManager manager, ServiceObjectHolder obj) + { + if (obj == null) + { + response.MoveHandles[index] = 0; + return; + } + + Api.CreateSession(out int serverHandle, out int clientHandle).AbortOnFailure(); + manager.RegisterSession(serverHandle, obj).AbortOnFailure(); + response.MoveHandles[index] = clientHandle; + } + } +} diff --git a/Ryujinx.Horizon/Sdk/Sf/IServiceObject.cs b/Ryujinx.Horizon/Sdk/Sf/IServiceObject.cs new file mode 100644 index 00000000..afa57fef --- /dev/null +++ b/Ryujinx.Horizon/Sdk/Sf/IServiceObject.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Ryujinx.Horizon.Sdk.Sf +{ + interface IServiceObject + { + IReadOnlyDictionary<int, CommandHandler> GetCommandHandlers(); + } +} diff --git a/Ryujinx.Horizon/Sdk/Sf/RawDataOffsetCalculator.cs b/Ryujinx.Horizon/Sdk/Sf/RawDataOffsetCalculator.cs new file mode 100644 index 00000000..982f454f --- /dev/null +++ b/Ryujinx.Horizon/Sdk/Sf/RawDataOffsetCalculator.cs @@ -0,0 +1,51 @@ +using Ryujinx.Common; + +namespace Ryujinx.Horizon.Sdk.Sf +{ + static class RawDataOffsetCalculator + { + public static int[] Calculate(CommandArg[] args) + { + int[] offsets = new int[args.Length + 1]; + + if (args.Length != 0) + { + int argsCount = args.Length; + + int[] sizes = new int[argsCount]; + int[] aligns = new int[argsCount]; + int[] map = new int[argsCount]; + + for (int i = 0; i < argsCount; i++) + { + sizes[i] = args[i].ArgSize; + aligns[i] = args[i].ArgAlignment; + map[i] = i; + } + + for (int i = 1; i < argsCount; i++) + { + for (int j = i; j > 0 && aligns[map[j - 1]] > aligns[map[j]]; j--) + { + var temp = map[j - 1]; + map[j - 1] = map[j]; + map[j] = temp; + } + } + + int offset = 0; + + foreach (int i in map) + { + offset = BitUtils.AlignUp(offset, aligns[i]); + offsets[i] = offset; + offset += sizes[i]; + } + + offsets[argsCount] = offset; + } + + return offsets; + } + } +} diff --git a/Ryujinx.Horizon/Sdk/Sf/SfResult.cs b/Ryujinx.Horizon/Sdk/Sf/SfResult.cs new file mode 100644 index 00000000..6aa11ba5 --- /dev/null +++ b/Ryujinx.Horizon/Sdk/Sf/SfResult.cs @@ -0,0 +1,31 @@ +using Ryujinx.Horizon.Common; + +namespace Ryujinx.Horizon.Sdk.Sf +{ + static class SfResult + { + public const int ModuleId = 10; + + public static Result NotSupported => new Result(ModuleId, 1); + public static Result InvalidHeaderSize => new Result(ModuleId, 202); + public static Result InvalidInHeader => new Result(ModuleId, 211); + public static Result InvalidOutHeader => new Result(ModuleId, 212); + public static Result UnknownCommandId => new Result(ModuleId, 221); + public static Result InvalidOutRawSize => new Result(ModuleId, 232); + public static Result InvalidInObjectsCount => new Result(ModuleId, 235); + public static Result InvalidOutObjectsCount => new Result(ModuleId, 236); + public static Result InvalidInObject => new Result(ModuleId, 239); + + public static Result TargetNotFound => new Result(ModuleId, 261); + + public static Result OutOfDomainEntries => new Result(ModuleId, 301); + + public static Result InvalidatedByUser => new Result(ModuleId, 802); + public static Result RequestDeferredByUser => new Result(ModuleId, 812); + + public static bool RequestContextChanged(Result result) => result.InRange(800, 899); + public static bool Invalidated(Result result) => result.InRange(801, 809); + + public static bool RequestDeferred(Result result) => result.InRange(811, 819); + } +} diff --git a/Ryujinx.Horizon/Sdk/Sm/ServiceName.cs b/Ryujinx.Horizon/Sdk/Sm/ServiceName.cs new file mode 100644 index 00000000..dbb30078 --- /dev/null +++ b/Ryujinx.Horizon/Sdk/Sm/ServiceName.cs @@ -0,0 +1,98 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Sm +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct ServiceName + { + public static ServiceName Invalid { get; } = new ServiceName(0); + + public bool IsInvalid => Packed == 0; + + public int Length => sizeof(ulong); + + public ulong Packed { get; } + + public byte this[int index] + { + get + { + if ((uint)index >= sizeof(ulong)) + { + throw new IndexOutOfRangeException(); + } + + return (byte)(Packed >> (index * 8)); + } + } + + private ServiceName(ulong packed) + { + Packed = packed; + } + + public static ServiceName Encode(string name) + { + ulong packed = 0; + + for (int index = 0; index < sizeof(ulong); index++) + { + if (index < name.Length) + { + packed |= (ulong)(byte)name[index] << (index * 8); + } + else + { + break; + } + } + + return new ServiceName(packed); + } + + public override bool Equals(object obj) + { + return obj is ServiceName serviceName && serviceName.Equals(this); + } + + public bool Equals(ServiceName other) + { + return other.Packed == Packed; + } + + public override int GetHashCode() + { + return Packed.GetHashCode(); + } + + public static bool operator ==(ServiceName lhs, ServiceName rhs) + { + return lhs.Equals(rhs); + } + + public static bool operator !=(ServiceName lhs, ServiceName rhs) + { + return !lhs.Equals(rhs); + } + + public override string ToString() + { + string name = string.Empty; + + for (int index = 0; index < sizeof(ulong); index++) + { + byte character = (byte)(Packed >> (index * 8)); + + if (character == 0) + { + break; + } + + name += (char)character; + } + + return name; + } + } +} diff --git a/Ryujinx.Horizon/Sdk/Sm/SmApi.cs b/Ryujinx.Horizon/Sdk/Sm/SmApi.cs new file mode 100644 index 00000000..e4b0eea1 --- /dev/null +++ b/Ryujinx.Horizon/Sdk/Sm/SmApi.cs @@ -0,0 +1,107 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf.Cmif; +using System; + +namespace Ryujinx.Horizon.Sdk.Sm +{ + class SmApi + { + private int _portHandle; + + public Result Initialize() + { + Result result = HorizonStatic.Syscall.ConnectToNamedPort(out int portHandle, "sm:"); + + while (result == KernelResult.NotFound) + { + HorizonStatic.Syscall.SleepThread(50000000L); + result = HorizonStatic.Syscall.ConnectToNamedPort(out portHandle, "sm:"); + } + + if (result.IsFailure) + { + return result; + } + + _portHandle = portHandle; + + return RegisterClient(); + } + + private Result RegisterClient() + { + Span<byte> data = stackalloc byte[8]; + + SpanWriter writer = new SpanWriter(data); + + writer.Write(0UL); + + return ServiceUtil.SendRequest(out _, _portHandle, 0, sendPid: true, data); + } + + public Result GetServiceHandle(out int handle, ServiceName name) + { + Span<byte> data = stackalloc byte[8]; + + SpanWriter writer = new SpanWriter(data); + + writer.Write(name); + + Result result = ServiceUtil.SendRequest(out CmifResponse response, _portHandle, 1, sendPid: false, data); + + if (result.IsFailure) + { + handle = 0; + return result; + } + + handle = response.MoveHandles[0]; + return Result.Success; + } + + public Result RegisterService(out int handle, ServiceName name, int maxSessions, bool isLight) + { + Span<byte> data = stackalloc byte[16]; + + SpanWriter writer = new SpanWriter(data); + + writer.Write(name); + writer.Write(isLight ? 1 : 0); + writer.Write(maxSessions); + + Result result = ServiceUtil.SendRequest(out CmifResponse response, _portHandle, 2, sendPid: false, data); + + if (result.IsFailure) + { + handle = 0; + return result; + } + + handle = response.MoveHandles[0]; + return Result.Success; + } + + public Result UnregisterService(ServiceName name) + { + Span<byte> data = stackalloc byte[8]; + + SpanWriter writer = new SpanWriter(data); + + writer.Write(name); + + return ServiceUtil.SendRequest(out _, _portHandle, 3, sendPid: false, data); + } + + public Result DetachClient() + { + Span<byte> data = stackalloc byte[8]; + + SpanWriter writer = new SpanWriter(data); + + writer.Write(0UL); + + return ServiceUtil.SendRequest(out _, _portHandle, 4, sendPid: true, data); + } + } +} diff --git a/Ryujinx.Horizon/ServiceEntry.cs b/Ryujinx.Horizon/ServiceEntry.cs new file mode 100644 index 00000000..3fea46c1 --- /dev/null +++ b/Ryujinx.Horizon/ServiceEntry.cs @@ -0,0 +1,25 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Memory; +using System; + +namespace Ryujinx.Horizon +{ + public struct ServiceEntry + { + private readonly Action _entrypoint; + private readonly HorizonOptions _options; + + internal ServiceEntry(Action entrypoint, HorizonOptions options) + { + _entrypoint = entrypoint; + _options = options; + } + + public void Start(ISyscallApi syscallApi, IVirtualMemoryManager addressSpace, IThreadContext threadContext) + { + HorizonStatic.Register(_options, syscallApi, addressSpace, threadContext, (int)threadContext.GetX(1)); + + _entrypoint(); + } + } +} diff --git a/Ryujinx.Horizon/ServiceTable.cs b/Ryujinx.Horizon/ServiceTable.cs new file mode 100644 index 00000000..933b6a59 --- /dev/null +++ b/Ryujinx.Horizon/ServiceTable.cs @@ -0,0 +1,22 @@ +using Ryujinx.Horizon.LogManager; +using System.Collections.Generic; + +namespace Ryujinx.Horizon +{ + public static class ServiceTable + { + public static IEnumerable<ServiceEntry> GetServices(HorizonOptions options) + { + List<ServiceEntry> entries = new List<ServiceEntry>(); + + void RegisterService<T>() where T : IService + { + entries.Add(new ServiceEntry(T.Main, options)); + } + + RegisterService<LmMain>(); + + return entries; + } + } +} diff --git a/Ryujinx.Horizon/Sm/Impl/ServiceInfo.cs b/Ryujinx.Horizon/Sm/Impl/ServiceInfo.cs new file mode 100644 index 00000000..fed420aa --- /dev/null +++ b/Ryujinx.Horizon/Sm/Impl/ServiceInfo.cs @@ -0,0 +1,20 @@ +using Ryujinx.Horizon.Sdk.Sm; + +namespace Ryujinx.Horizon.Sm.Impl +{ + struct ServiceInfo + { + public ServiceName Name; + public ulong OwnerProcessId; + public int PortHandle; + + public void Free() + { + HorizonStatic.Syscall.CloseHandle(PortHandle); + + Name = ServiceName.Invalid; + OwnerProcessId = 0L; + PortHandle = 0; + } + } +} diff --git a/Ryujinx.Horizon/Sm/Impl/ServiceManager.cs b/Ryujinx.Horizon/Sm/Impl/ServiceManager.cs new file mode 100644 index 00000000..cdf2d17f --- /dev/null +++ b/Ryujinx.Horizon/Sm/Impl/ServiceManager.cs @@ -0,0 +1,197 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.OsTypes; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sm; + +namespace Ryujinx.Horizon.Sm.Impl +{ + class ServiceManager + { + private const int MaxServicesCount = 256; + + private readonly ServiceInfo[] _services; + + public ServiceManager() + { + _services = new ServiceInfo[MaxServicesCount]; + } + + public Result GetService(out int handle, ulong processId, ServiceName name) + { + handle = 0; + Result result = ValidateServiceName(name); + + if (result.IsFailure) + { + return result; + } + + // TODO: Validation with GetProcessInfo etc. + + int serviceIndex = GetServiceInfo(name); + + if (serviceIndex < 0) + { + return SfResult.RequestDeferredByUser; + } + + result = GetServiceImpl(out handle, ref _services[serviceIndex]); + + if (result == KernelResult.SessionCountExceeded) + { + return SmResult.OutOfSessions; + } + + return result; + } + + private Result GetServiceImpl(out int handle, ref ServiceInfo serviceInfo) + { + return HorizonStatic.Syscall.ConnectToPort(out handle, serviceInfo.PortHandle); + } + + public Result RegisterService(out int handle, ulong processId, ServiceName name, int maxSessions, bool isLight) + { + handle = 0; + Result result = ValidateServiceName(name); + + if (result.IsFailure) + { + return result; + } + + // TODO: Validation with GetProcessInfo etc. + + if (HasServiceInfo(name)) + { + return SmResult.AlreadyRegistered; + } + + return RegisterServiceImpl(out handle, processId, name, maxSessions, isLight); + } + + public Result RegisterServiceForSelf(out int handle, ServiceName name, int maxSessions) + { + return RegisterServiceImpl(out handle, Os.GetCurrentProcessId(), name, maxSessions, false); + } + + private Result RegisterServiceImpl(out int handle, ulong processId, ServiceName name, int maxSessions, bool isLight) + { + handle = 0; + + Result result = ValidateServiceName(name); + + if (!result.IsSuccess) + { + return result; + } + + if (HasServiceInfo(name)) + { + return SmResult.AlreadyRegistered; + } + + int freeServiceIndex = GetFreeService(); + + if (freeServiceIndex < 0) + { + return SmResult.OutOfServices; + } + + ref ServiceInfo freeService = ref _services[freeServiceIndex]; + + result = HorizonStatic.Syscall.CreatePort(out handle, out int clientPort, maxSessions, isLight, null); + + if (!result.IsSuccess) + { + return result; + } + + freeService.PortHandle = clientPort; + freeService.Name = name; + freeService.OwnerProcessId = processId; + + return Result.Success; + } + + public Result UnregisterService(ulong processId, ServiceName name) + { + Result result = ValidateServiceName(name); + + if (result.IsFailure) + { + return result; + } + + // TODO: Validation with GetProcessInfo etc. + + int serviceIndex = GetServiceInfo(name); + + if (serviceIndex < 0) + { + return SmResult.NotRegistered; + } + + ref var serviceInfo = ref _services[serviceIndex]; + + if (serviceInfo.OwnerProcessId != processId) + { + return SmResult.NotAllowed; + } + + serviceInfo.Free(); + return Result.Success; + } + + private static Result ValidateServiceName(ServiceName name) + { + if (name[0] == 0) + { + return SmResult.InvalidServiceName; + } + + int nameLength = 1; + + for (; nameLength < name.Length; nameLength++) + { + if (name[nameLength] == 0) + { + break; + } + } + + while (nameLength < name.Length) + { + if (name[nameLength++] != 0) + { + return SmResult.InvalidServiceName; + } + } + + return Result.Success; + } + + private bool HasServiceInfo(ServiceName name) + { + return GetServiceInfo(name) != -1; + } + + private int GetFreeService() + { + return GetServiceInfo(ServiceName.Invalid); + } + + private int GetServiceInfo(ServiceName name) + { + for (int index = 0; index < MaxServicesCount; index++) + { + if (_services[index].Name == name) + { + return index; + } + } + + return -1; + } + } +} diff --git a/Ryujinx.Horizon/Sm/ManagerService.cs b/Ryujinx.Horizon/Sm/ManagerService.cs new file mode 100644 index 00000000..1719dcfd --- /dev/null +++ b/Ryujinx.Horizon/Sm/ManagerService.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sm +{ + partial class ManagerService : IServiceObject + { + } +} diff --git a/Ryujinx.Horizon/Sm/SmMain.cs b/Ryujinx.Horizon/Sm/SmMain.cs new file mode 100644 index 00000000..8c37bece --- /dev/null +++ b/Ryujinx.Horizon/Sm/SmMain.cs @@ -0,0 +1,30 @@ +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Sm; +using Ryujinx.Horizon.Sm.Impl; + +namespace Ryujinx.Horizon.Sm +{ + public class SmMain + { + private enum PortIndex + { + User, + Manager + } + + private const int MaxPortsCount = 2; + + private readonly ServerManager _serverManager = new ServerManager(null, null, MaxPortsCount, ManagerOptions.Default, 0); + private readonly ServiceManager _serviceManager = new ServiceManager(); + + public void Main() + { + HorizonStatic.Syscall.ManageNamedPort(out int smHandle, "sm:", 64).AbortOnFailure(); + + _serverManager.RegisterServer((int)PortIndex.User, smHandle); + _serviceManager.RegisterServiceForSelf(out int smmHandle, ServiceName.Encode("sm:m"), 1).AbortOnFailure(); + _serverManager.RegisterServer((int)PortIndex.Manager, smmHandle); + _serverManager.ServiceRequests(); + } + } +} diff --git a/Ryujinx.Horizon/Sm/SmResult.cs b/Ryujinx.Horizon/Sm/SmResult.cs new file mode 100644 index 00000000..3063445d --- /dev/null +++ b/Ryujinx.Horizon/Sm/SmResult.cs @@ -0,0 +1,19 @@ +using Ryujinx.Horizon.Common; + +namespace Ryujinx.Horizon.Sm +{ + static class SmResult + { + private const int ModuleId = 21; + + public static Result OutOfProcess => new Result(ModuleId, 1); + public static Result InvalidClient => new Result(ModuleId, 2); + public static Result OutOfSessions => new Result(ModuleId, 3); + public static Result AlreadyRegistered => new Result(ModuleId, 4); + public static Result OutOfServices => new Result(ModuleId, 5); + public static Result InvalidServiceName => new Result(ModuleId, 6); + public static Result NotRegistered => new Result(ModuleId, 7); + public static Result NotAllowed => new Result(ModuleId, 8); + public static Result TooLargeAccessControl => new Result(ModuleId, 9); + } +} diff --git a/Ryujinx.Horizon/Sm/UserService.cs b/Ryujinx.Horizon/Sm/UserService.cs new file mode 100644 index 00000000..d3b4537b --- /dev/null +++ b/Ryujinx.Horizon/Sm/UserService.cs @@ -0,0 +1,66 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sm; +using Ryujinx.Horizon.Sm.Impl; + +namespace Ryujinx.Horizon.Sm +{ + partial class UserService : IServiceObject + { + private readonly ServiceManager _serviceManager; + + private ulong _clientProcessId; + private bool _initialized; + + public UserService(ServiceManager serviceManager) + { + _serviceManager = serviceManager; + } + + [CmifCommand(0)] + public Result Initialize([ClientProcessId] ulong clientProcessId) + { + _clientProcessId = clientProcessId; + _initialized = true; + + return Result.Success; + } + + [CmifCommand(1)] + public Result GetService([MoveHandle] out int handle, ServiceName name) + { + if (!_initialized) + { + handle = 0; + + return SmResult.InvalidClient; + } + + return _serviceManager.GetService(out handle, _clientProcessId, name); + } + + [CmifCommand(2)] + public Result RegisterService([MoveHandle] out int handle, ServiceName name, int maxSessions, bool isLight) + { + if (!_initialized) + { + handle = 0; + + return SmResult.InvalidClient; + } + + return _serviceManager.RegisterService(out handle, _clientProcessId, name, maxSessions, isLight); + } + + [CmifCommand(3)] + public Result UnregisterService(ServiceName name) + { + if (!_initialized) + { + return SmResult.InvalidClient; + } + + return _serviceManager.UnregisterService(_clientProcessId, name); + } + } +} -- cgit v1.2.3-70-g09d2