ServerBase.cs
1 using Ryujinx.Common; 2 using Ryujinx.Common.Logging; 3 using Ryujinx.Common.Memory; 4 using Ryujinx.HLE.HOS.Ipc; 5 using Ryujinx.HLE.HOS.Kernel; 6 using Ryujinx.HLE.HOS.Kernel.Ipc; 7 using Ryujinx.HLE.HOS.Kernel.Process; 8 using Ryujinx.HLE.HOS.Kernel.Threading; 9 using Ryujinx.Horizon; 10 using Ryujinx.Horizon.Common; 11 using System; 12 using System.Buffers; 13 using System.Buffers.Binary; 14 using System.Collections.Generic; 15 using System.IO; 16 using System.Threading; 17 18 namespace Ryujinx.HLE.HOS.Services 19 { 20 class ServerBase : IDisposable 21 { 22 // Must be the maximum value used by services (highest one know is the one used by nvservices = 0x8000). 23 // Having a size that is too low will cause failures as data copy will fail if the receiving buffer is 24 // not large enough. 25 private const int PointerBufferSize = 0x8000; 26 27 private readonly static uint[] _defaultCapabilities = { 28 0x030363F7, 29 0x1FFFFFCF, 30 0x207FFFEF, 31 0x47E0060F, 32 0x0048BFFF, 33 0x01007FFF, 34 }; 35 36 // The amount of time Dispose() will wait to Join() the thread executing the ServerLoop() 37 private static readonly TimeSpan _threadJoinTimeout = TimeSpan.FromSeconds(3); 38 39 private readonly KernelContext _context; 40 private KProcess _selfProcess; 41 private KThread _selfThread; 42 private KEvent _wakeEvent; 43 private int _wakeHandle = 0; 44 45 private readonly ReaderWriterLockSlim _handleLock = new(); 46 private readonly Dictionary<int, IpcService> _sessions = new(); 47 private readonly Dictionary<int, Func<IpcService>> _ports = new(); 48 49 private readonly MemoryStream _requestDataStream; 50 private readonly BinaryReader _requestDataReader; 51 52 private readonly MemoryStream _responseDataStream; 53 private readonly BinaryWriter _responseDataWriter; 54 55 private int _isDisposed = 0; 56 57 public ManualResetEvent InitDone { get; } 58 public string Name { get; } 59 public Func<IpcService> SmObjectFactory { get; } 60 61 public ServerBase(KernelContext context, string name, Func<IpcService> smObjectFactory = null) 62 { 63 _context = context; 64 65 _requestDataStream = MemoryStreamManager.Shared.GetStream(); 66 _requestDataReader = new BinaryReader(_requestDataStream); 67 68 _responseDataStream = MemoryStreamManager.Shared.GetStream(); 69 _responseDataWriter = new BinaryWriter(_responseDataStream); 70 71 InitDone = new ManualResetEvent(false); 72 Name = name; 73 SmObjectFactory = smObjectFactory; 74 75 const ProcessCreationFlags Flags = 76 ProcessCreationFlags.EnableAslr | 77 ProcessCreationFlags.AddressSpace64Bit | 78 ProcessCreationFlags.Is64Bit | 79 ProcessCreationFlags.PoolPartitionSystem; 80 81 ProcessCreationInfo creationInfo = new("Service", 1, 0, 0x8000000, 1, Flags, 0, 0); 82 83 KernelStatic.StartInitialProcess(context, creationInfo, _defaultCapabilities, 44, Main); 84 } 85 86 private void AddPort(int serverPortHandle, Func<IpcService> objectFactory) 87 { 88 bool lockTaken = false; 89 try 90 { 91 lockTaken = _handleLock.TryEnterWriteLock(Timeout.Infinite); 92 93 _ports.Add(serverPortHandle, objectFactory); 94 } 95 finally 96 { 97 if (lockTaken) 98 { 99 _handleLock.ExitWriteLock(); 100 } 101 } 102 } 103 104 public void AddSessionObj(KServerSession serverSession, IpcService obj) 105 { 106 // Ensure that the sever loop is running. 107 InitDone.WaitOne(); 108 109 _selfProcess.HandleTable.GenerateHandle(serverSession, out int serverSessionHandle); 110 111 AddSessionObj(serverSessionHandle, obj); 112 } 113 114 public void AddSessionObj(int serverSessionHandle, IpcService obj) 115 { 116 bool lockTaken = false; 117 try 118 { 119 lockTaken = _handleLock.TryEnterWriteLock(Timeout.Infinite); 120 121 _sessions.Add(serverSessionHandle, obj); 122 } 123 finally 124 { 125 if (lockTaken) 126 { 127 _handleLock.ExitWriteLock(); 128 } 129 } 130 131 _wakeEvent.WritableEvent.Signal(); 132 } 133 134 private IpcService GetSessionObj(int serverSessionHandle) 135 { 136 bool lockTaken = false; 137 try 138 { 139 lockTaken = _handleLock.TryEnterReadLock(Timeout.Infinite); 140 141 return _sessions[serverSessionHandle]; 142 } 143 finally 144 { 145 if (lockTaken) 146 { 147 _handleLock.ExitReadLock(); 148 } 149 } 150 } 151 152 private bool RemoveSessionObj(int serverSessionHandle, out IpcService obj) 153 { 154 bool lockTaken = false; 155 try 156 { 157 lockTaken = _handleLock.TryEnterWriteLock(Timeout.Infinite); 158 159 return _sessions.Remove(serverSessionHandle, out obj); 160 } 161 finally 162 { 163 if (lockTaken) 164 { 165 _handleLock.ExitWriteLock(); 166 } 167 } 168 } 169 170 private void Main() 171 { 172 ServerLoop(); 173 } 174 175 private void ServerLoop() 176 { 177 _selfProcess = KernelStatic.GetCurrentProcess(); 178 _selfThread = KernelStatic.GetCurrentThread(); 179 180 HorizonStatic.Register( 181 default, 182 _context.Syscall, 183 _selfProcess.CpuMemory, 184 _selfThread.ThreadContext, 185 (int)_selfThread.ThreadContext.GetX(1)); 186 187 if (SmObjectFactory != null) 188 { 189 _context.Syscall.ManageNamedPort(out int serverPortHandle, "sm:", 50); 190 191 AddPort(serverPortHandle, SmObjectFactory); 192 } 193 194 _wakeEvent = new KEvent(_context); 195 Result result = _selfProcess.HandleTable.GenerateHandle(_wakeEvent.ReadableEvent, out _wakeHandle); 196 197 InitDone.Set(); 198 199 ulong messagePtr = _selfThread.TlsAddress; 200 _context.Syscall.SetHeapSize(out ulong heapAddr, 0x200000); 201 202 _selfProcess.CpuMemory.Write(messagePtr + 0x0, 0); 203 _selfProcess.CpuMemory.Write(messagePtr + 0x4, 2 << 10); 204 _selfProcess.CpuMemory.Write(messagePtr + 0x8, heapAddr | ((ulong)PointerBufferSize << 48)); 205 int replyTargetHandle = 0; 206 207 while (true) 208 { 209 int portHandleCount; 210 int handleCount; 211 int[] handles; 212 213 bool handleLockTaken = false; 214 try 215 { 216 handleLockTaken = _handleLock.TryEnterReadLock(Timeout.Infinite); 217 218 portHandleCount = _ports.Count; 219 220 handleCount = portHandleCount + _sessions.Count + 1; 221 222 handles = ArrayPool<int>.Shared.Rent(handleCount); 223 224 handles[0] = _wakeHandle; 225 226 _ports.Keys.CopyTo(handles, 1); 227 228 _sessions.Keys.CopyTo(handles, portHandleCount + 1); 229 } 230 finally 231 { 232 if (handleLockTaken) 233 { 234 _handleLock.ExitReadLock(); 235 } 236 } 237 238 var rc = _context.Syscall.ReplyAndReceive(out int signaledIndex, handles.AsSpan(0, handleCount), replyTargetHandle, -1); 239 240 _selfThread.HandlePostSyscall(); 241 242 if (!_selfThread.Context.Running) 243 { 244 break; 245 } 246 247 replyTargetHandle = 0; 248 249 if (rc == Result.Success && signaledIndex >= portHandleCount + 1) 250 { 251 // We got a IPC request, process it, pass to the appropriate service if needed. 252 int signaledHandle = handles[signaledIndex]; 253 254 if (Process(signaledHandle, heapAddr)) 255 { 256 replyTargetHandle = signaledHandle; 257 } 258 } 259 else 260 { 261 if (rc == Result.Success) 262 { 263 if (signaledIndex > 0) 264 { 265 // We got a new connection, accept the session to allow servicing future requests. 266 if (_context.Syscall.AcceptSession(out int serverSessionHandle, handles[signaledIndex]) == Result.Success) 267 { 268 bool handleWriteLockTaken = false; 269 try 270 { 271 handleWriteLockTaken = _handleLock.TryEnterWriteLock(Timeout.Infinite); 272 IpcService obj = _ports[handles[signaledIndex]].Invoke(); 273 _sessions.Add(serverSessionHandle, obj); 274 } 275 finally 276 { 277 if (handleWriteLockTaken) 278 { 279 _handleLock.ExitWriteLock(); 280 } 281 } 282 } 283 } 284 else 285 { 286 // The _wakeEvent signalled, which means we have a new session. 287 _wakeEvent.WritableEvent.Clear(); 288 } 289 } 290 else if (rc == KernelResult.PortRemoteClosed && signaledIndex >= 0 && SmObjectFactory != null) 291 { 292 DestroySession(handles[signaledIndex]); 293 } 294 295 _selfProcess.CpuMemory.Write(messagePtr + 0x0, 0); 296 _selfProcess.CpuMemory.Write(messagePtr + 0x4, 2 << 10); 297 _selfProcess.CpuMemory.Write(messagePtr + 0x8, heapAddr | ((ulong)PointerBufferSize << 48)); 298 } 299 300 ArrayPool<int>.Shared.Return(handles); 301 } 302 303 Dispose(); 304 } 305 306 private void DestroySession(int serverSessionHandle) 307 { 308 _context.Syscall.CloseHandle(serverSessionHandle); 309 310 if (RemoveSessionObj(serverSessionHandle, out var session)) 311 { 312 (session as IDisposable)?.Dispose(); 313 } 314 } 315 316 private bool Process(int serverSessionHandle, ulong recvListAddr) 317 { 318 IpcMessage request = ReadRequest(); 319 320 IpcMessage response = new(); 321 322 ulong tempAddr = recvListAddr; 323 int sizesOffset = request.RawData.Length - ((request.RecvListBuff.Count * 2 + 3) & ~3); 324 325 bool noReceive = true; 326 327 for (int i = 0; i < request.ReceiveBuff.Count; i++) 328 { 329 noReceive &= (request.ReceiveBuff[i].Position == 0); 330 } 331 332 if (noReceive) 333 { 334 response.PtrBuff.EnsureCapacity(request.RecvListBuff.Count); 335 336 for (int i = 0; i < request.RecvListBuff.Count; i++) 337 { 338 ulong size = (ulong)BinaryPrimitives.ReadInt16LittleEndian(request.RawData.AsSpan(sizesOffset + i * 2, 2)); 339 340 response.PtrBuff.Add(new IpcPtrBuffDesc(tempAddr, (uint)i, size)); 341 342 request.RecvListBuff[i] = new IpcRecvListBuffDesc(tempAddr, size); 343 344 tempAddr += size; 345 } 346 } 347 348 bool shouldReply = true; 349 bool isTipcCommunication = false; 350 351 _requestDataStream.SetLength(0); 352 _requestDataStream.Write(request.RawData); 353 _requestDataStream.Position = 0; 354 355 if (request.Type == IpcMessageType.CmifRequest || 356 request.Type == IpcMessageType.CmifRequestWithContext) 357 { 358 response.Type = IpcMessageType.CmifResponse; 359 360 _responseDataStream.SetLength(0); 361 362 ServiceCtx context = new( 363 _context.Device, 364 _selfProcess, 365 _selfProcess.CpuMemory, 366 _selfThread, 367 request, 368 response, 369 _requestDataReader, 370 _responseDataWriter); 371 372 GetSessionObj(serverSessionHandle).CallCmifMethod(context); 373 374 response.RawData = _responseDataStream.ToArray(); 375 } 376 else if (request.Type == IpcMessageType.CmifControl || 377 request.Type == IpcMessageType.CmifControlWithContext) 378 { 379 #pragma warning disable IDE0059 // Remove unnecessary value assignment 380 uint magic = (uint)_requestDataReader.ReadUInt64(); 381 #pragma warning restore IDE0059 382 uint cmdId = (uint)_requestDataReader.ReadUInt64(); 383 384 switch (cmdId) 385 { 386 case 0: 387 FillHipcResponse(response, 0, GetSessionObj(serverSessionHandle).ConvertToDomain()); 388 break; 389 390 case 3: 391 FillHipcResponse(response, 0, PointerBufferSize); 392 break; 393 394 // TODO: Whats the difference between IpcDuplicateSession/Ex? 395 case 2: 396 case 4: 397 { 398 _ = _requestDataReader.ReadInt32(); 399 400 _context.Syscall.CreateSession(out int dupServerSessionHandle, out int dupClientSessionHandle, false, 0); 401 402 bool writeLockTaken = false; 403 try 404 { 405 writeLockTaken = _handleLock.TryEnterWriteLock(Timeout.Infinite); 406 _sessions[dupServerSessionHandle] = _sessions[serverSessionHandle]; 407 } 408 finally 409 { 410 if (writeLockTaken) 411 { 412 _handleLock.ExitWriteLock(); 413 } 414 } 415 416 response.HandleDesc = IpcHandleDesc.MakeMove(dupClientSessionHandle); 417 418 FillHipcResponse(response, 0); 419 420 break; 421 } 422 423 default: 424 throw new NotImplementedException(cmdId.ToString()); 425 } 426 } 427 else if (request.Type == IpcMessageType.CmifCloseSession || request.Type == IpcMessageType.TipcCloseSession) 428 { 429 DestroySession(serverSessionHandle); 430 shouldReply = false; 431 } 432 // If the type is past 0xF, we are using TIPC 433 else if (request.Type > IpcMessageType.TipcCloseSession) 434 { 435 isTipcCommunication = true; 436 437 // Response type is always the same as request on TIPC. 438 response.Type = request.Type; 439 440 _responseDataStream.SetLength(0); 441 442 ServiceCtx context = new( 443 _context.Device, 444 _selfProcess, 445 _selfProcess.CpuMemory, 446 _selfThread, 447 request, 448 response, 449 _requestDataReader, 450 _responseDataWriter); 451 452 GetSessionObj(serverSessionHandle).CallTipcMethod(context); 453 454 response.RawData = _responseDataStream.ToArray(); 455 456 using var responseStream = response.GetStreamTipc(); 457 _selfProcess.CpuMemory.Write(_selfThread.TlsAddress, responseStream.GetReadOnlySequence()); 458 } 459 else 460 { 461 throw new NotImplementedException(request.Type.ToString()); 462 } 463 464 if (!isTipcCommunication) 465 { 466 using var responseStream = response.GetStream((long)_selfThread.TlsAddress, recvListAddr | ((ulong)PointerBufferSize << 48)); 467 _selfProcess.CpuMemory.Write(_selfThread.TlsAddress, responseStream.GetReadOnlySequence()); 468 } 469 470 return shouldReply; 471 } 472 473 private IpcMessage ReadRequest() 474 { 475 const int MessageSize = 0x100; 476 477 using SpanOwner<byte> reqDataOwner = SpanOwner<byte>.Rent(MessageSize); 478 479 Span<byte> reqDataSpan = reqDataOwner.Span; 480 481 _selfProcess.CpuMemory.Read(_selfThread.TlsAddress, reqDataSpan); 482 483 IpcMessage request = new(reqDataSpan, (long)_selfThread.TlsAddress); 484 485 return request; 486 } 487 488 private void FillHipcResponse(IpcMessage response, long result) 489 { 490 FillHipcResponse(response, result, ReadOnlySpan<byte>.Empty); 491 } 492 493 private void FillHipcResponse(IpcMessage response, long result, int value) 494 { 495 Span<byte> span = stackalloc byte[sizeof(int)]; 496 BinaryPrimitives.WriteInt32LittleEndian(span, value); 497 FillHipcResponse(response, result, span); 498 } 499 500 private void FillHipcResponse(IpcMessage response, long result, ReadOnlySpan<byte> data) 501 { 502 response.Type = IpcMessageType.CmifResponse; 503 504 _responseDataStream.SetLength(0); 505 506 _responseDataStream.Write(IpcMagic.Sfco); 507 _responseDataStream.Write(result); 508 509 _responseDataStream.Write(data); 510 511 response.RawData = _responseDataStream.ToArray(); 512 } 513 514 protected virtual void Dispose(bool disposing) 515 { 516 if (disposing && _selfThread != null) 517 { 518 if (_selfThread.HostThread.ManagedThreadId != Environment.CurrentManagedThreadId && _selfThread.HostThread.Join(_threadJoinTimeout) == false) 519 { 520 Logger.Warning?.Print(LogClass.Service, $"The ServerBase thread didn't terminate within {_threadJoinTimeout:g}, waiting longer."); 521 522 _selfThread.HostThread.Join(Timeout.Infinite); 523 } 524 525 if (Interlocked.Exchange(ref _isDisposed, 1) == 0) 526 { 527 _selfProcess.HandleTable.CloseHandle(_wakeHandle); 528 529 foreach (IpcService service in _sessions.Values) 530 { 531 (service as IDisposable)?.Dispose(); 532 533 service.DestroyAtExit(); 534 } 535 536 _sessions.Clear(); 537 _ports.Clear(); 538 _handleLock.Dispose(); 539 540 _requestDataReader.Dispose(); 541 _requestDataStream.Dispose(); 542 _responseDataWriter.Dispose(); 543 _responseDataStream.Dispose(); 544 545 InitDone.Dispose(); 546 } 547 } 548 } 549 550 public void Dispose() 551 { 552 Dispose(true); 553 } 554 } 555 }