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

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

* Make types match on calls to AlignUp/AlignDown

* Formatting

* Address some PR feedback

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

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

* Implement EventType

* Address more PR feedback

* Log request processing errors since they are not normal

* Rename waitable to multiwait and add missing lock

* PR feedback

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

View file

@ -0,0 +1,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);
}
}
}

View file

@ -0,0 +1,12 @@
namespace Ryujinx.Horizon
{
public struct HorizonOptions
{
public bool IgnoreMissingServices { get; }
public HorizonOptions(bool ignoreMissingServices)
{
IgnoreMissingServices = ignoreMissingServices;
}
}
}

View file

@ -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;
}
}
}

View file

@ -0,0 +1,7 @@
namespace Ryujinx.Horizon
{
interface IService
{
abstract static void Main();
}
}

View file

@ -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();
}
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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();
}
}
}

View file

@ -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>

View file

@ -0,0 +1,12 @@
using System.Diagnostics;
namespace Ryujinx.Horizon.Sdk
{
static class DebugUtil
{
public static void Assert(bool condition)
{
Debug.Assert(condition);
}
}
}

View file

@ -0,0 +1,11 @@
namespace Ryujinx.Horizon.Sdk.Diag
{
enum LogSeverity : byte
{
Trace = 0,
Info = 1,
Warn = 2,
Error = 3,
Fatal = 4
}
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -0,0 +1,12 @@
using System;
namespace Ryujinx.Horizon.Sdk.Lm
{
[Flags]
enum LogPacketFlags : byte
{
IsHead = 1 << 0,
IsTail = 1 << 1,
IsLittleEndian = 1 << 2
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}
}

View file

@ -0,0 +1,8 @@
namespace Ryujinx.Horizon.Sdk.OsTypes
{
enum EventClearMode
{
ManualClear,
AutoClear
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}
}

View file

@ -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();
}
}
}
}

View file

@ -0,0 +1,8 @@
namespace Ryujinx.Horizon.Sdk.OsTypes
{
enum InitializationState : byte
{
NotInitialized,
Initialized
}
}

View file

@ -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;
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}
}

View file

@ -0,0 +1,10 @@
namespace Ryujinx.Horizon.Sdk.OsTypes
{
static partial class Os
{
public static void FinalizeMultiWaitHolder(MultiWaitHolderBase holder)
{
DebugUtil.Assert(!holder.IsLinked);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}
}
}

View file

@ -0,0 +1,10 @@
namespace Ryujinx.Horizon.Sdk.OsTypes
{
static partial class Os
{
public static int GetCurrentThreadHandle()
{
return HorizonStatic.CurrentThreadHandle;
}
}
}

View file

@ -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;
}
}

View file

@ -0,0 +1,9 @@
namespace Ryujinx.Horizon.Sdk.OsTypes
{
enum TriBool
{
False,
True,
Undefined
}
}

View file

@ -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);
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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
{
}
}

View file

@ -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>());
}
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}

View file

@ -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
}
}

View file

@ -0,0 +1,10 @@
namespace Ryujinx.Horizon.Sdk.Sf.Hipc
{
enum HipcBufferMode
{
Normal = 0,
NonSecure = 1,
Invalid = 2,
NonDevice = 3
}
}

View file

@ -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;
}
}
}

View file

@ -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
};
}
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -0,0 +1,9 @@
namespace Ryujinx.Horizon.Sdk.Sf.Hipc
{
enum ReceiveResult
{
Success,
Closed,
NeedsRetry
}
}

View file

@ -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;
}
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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);
}
}
}

View file

@ -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;
}
}
}

View file

@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace Ryujinx.Horizon.Sdk.Sf
{
interface IServiceObject
{
IReadOnlyDictionary<int, CommandHandler> GetCommandHandlers();
}
}

View file

@ -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;
}
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}
}

View file

@ -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);
}
}
}

View file

@ -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();
}
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -0,0 +1,8 @@
using Ryujinx.Horizon.Sdk.Sf;
namespace Ryujinx.Horizon.Sm
{
partial class ManagerService : IServiceObject
{
}
}

Some files were not shown because too many files have changed in this diff Show more