diff options
Diffstat (limited to 'Ryujinx.Horizon/Sdk/Sf/Hipc/ServerSessionManager.cs')
-rw-r--r-- | Ryujinx.Horizon/Sdk/Sf/Hipc/ServerSessionManager.cs | 335 |
1 files changed, 335 insertions, 0 deletions
diff --git a/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerSessionManager.cs b/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerSessionManager.cs new file mode 100644 index 00000000..e85892f2 --- /dev/null +++ b/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerSessionManager.cs @@ -0,0 +1,335 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.OsTypes; +using Ryujinx.Horizon.Sdk.Sf.Cmif; +using Ryujinx.Horizon.Sdk.Sm; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Sf.Hipc +{ + class ServerSessionManager + { + public Result AcceptSession(int portHandle, ServiceObjectHolder obj) + { + return AcceptSession(out _, portHandle, obj); + } + + private Result AcceptSession(out ServerSession session, int portHandle, ServiceObjectHolder obj) + { + return AcceptSessionImpl(out session, portHandle, obj); + } + + private Result AcceptSessionImpl(out ServerSession session, int portHandle, ServiceObjectHolder obj) + { + session = null; + + Result result = HorizonStatic.Syscall.AcceptSession(out int sessionHandle, portHandle); + + if (result.IsFailure) + { + return result; + } + + bool succeeded = false; + + try + { + result = RegisterSessionImpl(out session, sessionHandle, obj); + + if (result.IsFailure) + { + return result; + } + + succeeded = true; + } + finally + { + if (!succeeded) + { + HorizonStatic.Syscall.CloseHandle(sessionHandle); + } + } + + return Result.Success; + } + + public Result RegisterSession(int sessionHandle, ServiceObjectHolder obj) + { + return RegisterSession(out _, sessionHandle, obj); + } + + public Result RegisterSession(out ServerSession session, int sessionHandle, ServiceObjectHolder obj) + { + return RegisterSessionImpl(out session, sessionHandle, obj); + } + + private Result RegisterSessionImpl(out ServerSession session, int sessionHandle, ServiceObjectHolder obj) + { + Result result = CreateSessionImpl(out session, sessionHandle, obj); + + if (result.IsFailure) + { + return result; + } + + session.PointerBuffer = GetSessionPointerBuffer(session); + session.SavedMessage = GetSessionSavedMessageBuffer(session); + + RegisterSessionToWaitList(session); + return Result.Success; + } + + protected virtual void RegisterSessionToWaitList(ServerSession session) + { + throw new NotSupportedException(); + } + + private Result CreateSessionImpl(out ServerSession session, int sessionHandle, ServiceObjectHolder obj) + { + session = AllocateSession(sessionHandle, obj); + + if (session == null) + { + return HipcResult.OutOfSessionMemory; + } + + return Result.Success; + } + + protected virtual ServerSession AllocateSession(int sessionHandle, ServiceObjectHolder obj) + { + throw new NotSupportedException(); + } + + protected virtual void FreeSession(ServerSession session) + { + throw new NotSupportedException(); + } + + protected virtual Server AllocateServer( + int portIndex, + int portHandle, + ServiceName name, + bool managed, + ServiceObjectHolder staticHoder) + { + throw new NotSupportedException(); + } + + protected virtual void DestroyServer(Server server) + { + throw new NotSupportedException(); + } + + protected virtual PointerAndSize GetSessionPointerBuffer(ServerSession session) + { + throw new NotSupportedException(); + } + + protected virtual PointerAndSize GetSessionSavedMessageBuffer(ServerSession session) + { + throw new NotSupportedException(); + } + + private void DestroySession(ServerSession session) + { + FreeSession(session); + } + + protected void CloseSessionImpl(ServerSession session) + { + int sessionHandle = session.Handle; + Os.FinalizeMultiWaitHolder(session); + DestroySession(session); + HorizonStatic.Syscall.CloseHandle(sessionHandle).AbortOnFailure(); + } + + private static CommandType GetCmifCommandType(ReadOnlySpan<byte> message) + { + return MemoryMarshal.Cast<byte, Header>(message)[0].Type; + } + + public Result ProcessRequest(ServerSession session, Span<byte> message) + { + if (session.IsClosed || GetCmifCommandType(message) == CommandType.Close) + { + CloseSessionImpl(session); + return Result.Success; + } + else + { + Result result = ProcessRequestImpl(session, message, message); + + if (result.IsSuccess) + { + RegisterSessionToWaitList(session); + return Result.Success; + } + else if (SfResult.RequestContextChanged(result)) + { + return result; + } + else + { + Logger.Warning?.Print(LogClass.KernelIpc, $"Request processing returned error {result}"); + + CloseSessionImpl(session); + return Result.Success; + } + } + } + + private Result ProcessRequestImpl(ServerSession session, Span<byte> inMessage, Span<byte> outMessage) + { + CommandType commandType = GetCmifCommandType(inMessage); + + using var _ = new ScopedInlineContextChange(GetInlineContext(commandType, inMessage)); + + switch (commandType) + { + case CommandType.Request: + case CommandType.RequestWithContext: + return DispatchRequest(session.ServiceObjectHolder, session, inMessage, outMessage); + case CommandType.Control: + case CommandType.ControlWithContext: + return DispatchManagerRequest(session, inMessage, outMessage); + default: + return HipcResult.UnknownCommandType; + } + } + + private static int GetInlineContext(CommandType commandType, ReadOnlySpan<byte> inMessage) + { + switch (commandType) + { + case CommandType.RequestWithContext: + case CommandType.ControlWithContext: + if (inMessage.Length >= 0x10) + { + return MemoryMarshal.Cast<byte, int>(inMessage)[3]; + } + break; + } + + return 0; + } + + protected Result ReceiveRequest(ServerSession session, Span<byte> message) + { + return ReceiveRequestImpl(session, message); + } + + private Result ReceiveRequestImpl(ServerSession session, Span<byte> message) + { + PointerAndSize pointerBuffer = session.PointerBuffer; + + while (true) + { + if (pointerBuffer.Address != 0) + { + HipcMessageData messageData = HipcMessage.WriteMessage(message, new HipcMetadata() + { + Type = (int)CommandType.Invalid, + ReceiveStaticsCount = HipcMessage.AutoReceiveStatic + }); + + messageData.ReceiveList[0] = new HipcReceiveListEntry(pointerBuffer.Address, pointerBuffer.Size); + } + else + { + MemoryMarshal.Cast<byte, Header>(message)[0] = new Header() + { + Type = CommandType.Invalid + }; + } + + Result result = Api.Receive(out ReceiveResult recvResult, session.Handle, message); + + if (result.IsFailure) + { + return result; + } + + switch (recvResult) + { + case ReceiveResult.Success: + session.IsClosed = false; + return Result.Success; + case ReceiveResult.Closed: + session.IsClosed = true; + return Result.Success; + } + } + } + + protected virtual Result DispatchManagerRequest(ServerSession session, Span<byte> inMessage, Span<byte> outMessage) + { + return SfResult.NotSupported; + } + + protected virtual Result DispatchRequest( + ServiceObjectHolder objectHolder, + ServerSession session, + Span<byte> inMessage, + Span<byte> outMessage) + { + HipcMessage request; + + try + { + request = new HipcMessage(inMessage); + } + catch (ArgumentOutOfRangeException) + { + return HipcResult.InvalidRequestSize; + } + + var dispatchCtx = new ServiceDispatchContext() + { + ServiceObject = objectHolder.ServiceObject, + Manager = this, + Session = session, + HandlesToClose = new HandlesToClose(), + PointerBuffer = session.PointerBuffer, + InMessageBuffer = inMessage, + OutMessageBuffer = outMessage, + Request = request + }; + + ReadOnlySpan<byte> inRawData = MemoryMarshal.Cast<uint, byte>(dispatchCtx.Request.Data.DataWords); + + int inRawSize = dispatchCtx.Request.Meta.DataWordsCount * sizeof(uint); + + if (inRawSize < 0x10) + { + return HipcResult.InvalidRequestSize; + } + + Result result = objectHolder.ProcessMessage(ref dispatchCtx, inRawData); + + if (result.IsFailure) + { + return result; + } + + result = Api.Reply(session.SessionHandle, outMessage); + + ref var handlesToClose = ref dispatchCtx.HandlesToClose; + + for (int i = 0; i < handlesToClose.Count; i++) + { + HorizonStatic.Syscall.CloseHandle(handlesToClose[i]).AbortOnFailure(); + } + + return result; + } + + public ServerSessionManager GetSessionManagerByTag(uint tag) + { + // Official FW does not do anything with the tag currently. + return this; + } + } +} |