diff options
| author | TSR Berry <20988865+TSRBerry@users.noreply.github.com> | 2023-04-08 01:22:00 +0200 |
|---|---|---|
| committer | Mary <thog@protonmail.com> | 2023-04-27 23:51:14 +0200 |
| commit | cee712105850ac3385cd0091a923438167433f9f (patch) | |
| tree | 4a5274b21d8b7f938c0d0ce18736d3f2993b11b1 /src/Ryujinx.HLE/HOS/Services/Nv | |
| parent | cd124bda587ef09668a971fa1cac1c3f0cfc9f21 (diff) | |
Move solution and projects to src
Diffstat (limited to 'src/Ryujinx.HLE/HOS/Services/Nv')
67 files changed, 6159 insertions, 0 deletions
diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/Host1xContext.cs b/src/Ryujinx.HLE/HOS/Services/Nv/Host1xContext.cs new file mode 100644 index 00000000..bb609fa4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/Host1xContext.cs @@ -0,0 +1,32 @@ +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.Graphics.Host1x; +using Ryujinx.Graphics.Nvdec; +using Ryujinx.Graphics.Vic; +using System; +using GpuContext = Ryujinx.Graphics.Gpu.GpuContext; + +namespace Ryujinx.HLE.HOS.Services.Nv +{ + class Host1xContext : IDisposable + { + public MemoryManager Smmu { get; } + public NvMemoryAllocator MemoryAllocator { get; } + public Host1xDevice Host1x { get;} + + public Host1xContext(GpuContext gpu, ulong pid) + { + MemoryAllocator = new NvMemoryAllocator(); + Host1x = new Host1xDevice(gpu.Synchronization); + Smmu = gpu.CreateMemoryManager(pid); + var nvdec = new NvdecDevice(Smmu); + var vic = new VicDevice(Smmu); + Host1x.RegisterDevice(ClassId.Nvdec, nvdec); + Host1x.RegisterDevice(ClassId.Vic, vic); + } + + public void Dispose() + { + Host1x.Dispose(); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/INvDrvDebugFSServices.cs b/src/Ryujinx.HLE/HOS/Services/Nv/INvDrvDebugFSServices.cs new file mode 100644 index 00000000..dffe8783 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/INvDrvDebugFSServices.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nv +{ + [Service("nvdrvdbg")] + class INvDrvDebugFSServices : IpcService + { + public INvDrvDebugFSServices(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs b/src/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs new file mode 100644 index 00000000..1d075d43 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs @@ -0,0 +1,598 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Cpu; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostDbgGpu; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostProfGpu; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap; +using Ryujinx.HLE.HOS.Services.Nv.Types; +using Ryujinx.Memory; +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Ryujinx.HLE.HOS.Services.Nv +{ + [Service("nvdrv")] + [Service("nvdrv:a")] + [Service("nvdrv:s")] + [Service("nvdrv:t")] + class INvDrvServices : IpcService + { + private static readonly List<string> _deviceFileDebugRegistry = new List<string>() + { + "/dev/nvhost-dbg-gpu", + "/dev/nvhost-prof-gpu" + }; + + private static readonly Dictionary<string, Type> _deviceFileRegistry = new Dictionary<string, Type>() + { + { "/dev/nvmap", typeof(NvMapDeviceFile) }, + { "/dev/nvhost-ctrl", typeof(NvHostCtrlDeviceFile) }, + { "/dev/nvhost-ctrl-gpu", typeof(NvHostCtrlGpuDeviceFile) }, + { "/dev/nvhost-as-gpu", typeof(NvHostAsGpuDeviceFile) }, + { "/dev/nvhost-gpu", typeof(NvHostGpuDeviceFile) }, + //{ "/dev/nvhost-msenc", typeof(NvHostChannelDeviceFile) }, + { "/dev/nvhost-nvdec", typeof(NvHostChannelDeviceFile) }, + //{ "/dev/nvhost-nvjpg", typeof(NvHostChannelDeviceFile) }, + { "/dev/nvhost-vic", typeof(NvHostChannelDeviceFile) }, + //{ "/dev/nvhost-display", typeof(NvHostChannelDeviceFile) }, + { "/dev/nvhost-dbg-gpu", typeof(NvHostDbgGpuDeviceFile) }, + { "/dev/nvhost-prof-gpu", typeof(NvHostProfGpuDeviceFile) }, + }; + + public static IdDictionary DeviceFileIdRegistry = new IdDictionary(); + + private IVirtualMemoryManager _clientMemory; + private ulong _owner; + + private bool _transferMemInitialized = false; + + // TODO: This should call set:sys::GetDebugModeFlag + private bool _debugModeEnabled = false; + + public INvDrvServices(ServiceCtx context) : base(context.Device.System.NvDrvServer) + { + _owner = 0; + } + + private NvResult Open(ServiceCtx context, string path, out int fd) + { + fd = -1; + + if (!_debugModeEnabled && _deviceFileDebugRegistry.Contains(path)) + { + return NvResult.NotSupported; + } + + if (_deviceFileRegistry.TryGetValue(path, out Type deviceFileClass)) + { + ConstructorInfo constructor = deviceFileClass.GetConstructor(new Type[] { typeof(ServiceCtx), typeof(IVirtualMemoryManager), typeof(ulong) }); + + NvDeviceFile deviceFile = (NvDeviceFile)constructor.Invoke(new object[] { context, _clientMemory, _owner }); + + deviceFile.Path = path; + + fd = DeviceFileIdRegistry.Add(deviceFile); + + return NvResult.Success; + } + + Logger.Warning?.Print(LogClass.ServiceNv, $"Cannot find file device \"{path}\"!"); + + return NvResult.FileOperationFailed; + } + + private NvResult GetIoctlArgument(ServiceCtx context, NvIoctl ioctlCommand, out Span<byte> arguments) + { + (ulong inputDataPosition, ulong inputDataSize) = context.Request.GetBufferType0x21(0); + (ulong outputDataPosition, ulong outputDataSize) = context.Request.GetBufferType0x22(0); + + NvIoctl.Direction ioctlDirection = ioctlCommand.DirectionValue; + uint ioctlSize = ioctlCommand.Size; + + bool isRead = (ioctlDirection & NvIoctl.Direction.Read) != 0; + bool isWrite = (ioctlDirection & NvIoctl.Direction.Write) != 0; + + if ((isWrite && ioctlSize > outputDataSize) || (isRead && ioctlSize > inputDataSize)) + { + arguments = null; + + Logger.Warning?.Print(LogClass.ServiceNv, "Ioctl size inconsistency found!"); + + return NvResult.InvalidSize; + } + + if (isRead && isWrite) + { + if (outputDataSize < inputDataSize) + { + arguments = null; + + Logger.Warning?.Print(LogClass.ServiceNv, "Ioctl size inconsistency found!"); + + return NvResult.InvalidSize; + } + + byte[] outputData = new byte[outputDataSize]; + + byte[] temp = new byte[inputDataSize]; + + context.Memory.Read(inputDataPosition, temp); + + Buffer.BlockCopy(temp, 0, outputData, 0, temp.Length); + + arguments = new Span<byte>(outputData); + } + else if (isWrite) + { + byte[] outputData = new byte[outputDataSize]; + + arguments = new Span<byte>(outputData); + } + else + { + byte[] temp = new byte[inputDataSize]; + + context.Memory.Read(inputDataPosition, temp); + + arguments = new Span<byte>(temp); + } + + return NvResult.Success; + } + + private NvResult GetDeviceFileFromFd(int fd, out NvDeviceFile deviceFile) + { + deviceFile = null; + + if (fd < 0) + { + return NvResult.InvalidParameter; + } + + deviceFile = DeviceFileIdRegistry.GetData<NvDeviceFile>(fd); + + if (deviceFile == null) + { + Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid file descriptor {fd}"); + + return NvResult.NotImplemented; + } + + if (deviceFile.Owner != _owner) + { + return NvResult.AccessDenied; + } + + return NvResult.Success; + } + + private NvResult EnsureInitialized() + { + if (_owner == 0) + { + Logger.Warning?.Print(LogClass.ServiceNv, "INvDrvServices is not initialized!"); + + return NvResult.NotInitialized; + } + + return NvResult.Success; + } + + private static NvResult ConvertInternalErrorCode(NvInternalResult errorCode) + { + switch (errorCode) + { + case NvInternalResult.Success: + return NvResult.Success; + case NvInternalResult.Unknown0x72: + return NvResult.AlreadyAllocated; + case NvInternalResult.TimedOut: + case NvInternalResult.TryAgain: + case NvInternalResult.Interrupted: + return NvResult.Timeout; + case NvInternalResult.InvalidAddress: + return NvResult.InvalidAddress; + case NvInternalResult.NotSupported: + case NvInternalResult.Unknown0x18: + return NvResult.NotSupported; + case NvInternalResult.InvalidState: + return NvResult.InvalidState; + case NvInternalResult.ReadOnlyAttribute: + return NvResult.ReadOnlyAttribute; + case NvInternalResult.NoSpaceLeft: + case NvInternalResult.FileTooBig: + return NvResult.InvalidSize; + case NvInternalResult.FileTableOverflow: + case NvInternalResult.BadFileNumber: + return NvResult.FileOperationFailed; + case NvInternalResult.InvalidInput: + return NvResult.InvalidValue; + case NvInternalResult.NotADirectory: + return NvResult.DirectoryOperationFailed; + case NvInternalResult.Busy: + return NvResult.Busy; + case NvInternalResult.BadAddress: + return NvResult.InvalidAddress; + case NvInternalResult.AccessDenied: + case NvInternalResult.OperationNotPermitted: + return NvResult.AccessDenied; + case NvInternalResult.OutOfMemory: + return NvResult.InsufficientMemory; + case NvInternalResult.DeviceNotFound: + return NvResult.ModuleNotPresent; + case NvInternalResult.IoError: + return NvResult.ResourceError; + default: + return NvResult.IoctlFailed; + } + } + + [CommandCmif(0)] + // Open(buffer<bytes, 5> path) -> (s32 fd, u32 error_code) + public ResultCode Open(ServiceCtx context) + { + NvResult errorCode = EnsureInitialized(); + int fd = -1; + + if (errorCode == NvResult.Success) + { + ulong pathPtr = context.Request.SendBuff[0].Position; + ulong pathSize = context.Request.SendBuff[0].Size; + + string path = MemoryHelper.ReadAsciiString(context.Memory, pathPtr, (long)pathSize); + + errorCode = Open(context, path, out fd); + } + + context.ResponseData.Write(fd); + context.ResponseData.Write((uint)errorCode); + + return ResultCode.Success; + } + + [CommandCmif(1)] + // Ioctl(s32 fd, u32 ioctl_cmd, buffer<bytes, 0x21> in_args) -> (u32 error_code, buffer<bytes, 0x22> out_args) + public ResultCode Ioctl(ServiceCtx context) + { + NvResult errorCode = EnsureInitialized(); + + if (errorCode == NvResult.Success) + { + int fd = context.RequestData.ReadInt32(); + NvIoctl ioctlCommand = context.RequestData.ReadStruct<NvIoctl>(); + + errorCode = GetIoctlArgument(context, ioctlCommand, out Span<byte> arguments); + + if (errorCode == NvResult.Success) + { + errorCode = GetDeviceFileFromFd(fd, out NvDeviceFile deviceFile); + + if (errorCode == NvResult.Success) + { + NvInternalResult internalResult = deviceFile.Ioctl(ioctlCommand, arguments); + + if (internalResult == NvInternalResult.NotImplemented) + { + throw new NvIoctlNotImplementedException(context, deviceFile, ioctlCommand); + } + + errorCode = ConvertInternalErrorCode(internalResult); + + if ((ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0) + { + context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments.ToArray()); + } + } + } + } + + context.ResponseData.Write((uint)errorCode); + + return ResultCode.Success; + } + + [CommandCmif(2)] + // Close(s32 fd) -> u32 error_code + public ResultCode Close(ServiceCtx context) + { + NvResult errorCode = EnsureInitialized(); + + if (errorCode == NvResult.Success) + { + int fd = context.RequestData.ReadInt32(); + + errorCode = GetDeviceFileFromFd(fd, out NvDeviceFile deviceFile); + + if (errorCode == NvResult.Success) + { + deviceFile.Close(); + + DeviceFileIdRegistry.Delete(fd); + } + } + + context.ResponseData.Write((uint)errorCode); + + return ResultCode.Success; + } + + [CommandCmif(3)] + // Initialize(u32 transfer_memory_size, handle<copy, process> current_process, handle<copy, transfer_memory> transfer_memory) -> u32 error_code + public ResultCode Initialize(ServiceCtx context) + { + long transferMemSize = context.RequestData.ReadInt64(); + int transferMemHandle = context.Request.HandleDesc.ToCopy[1]; + + // TODO: When transfer memory will be implemented, this could be removed. + _transferMemInitialized = true; + + int clientHandle = context.Request.HandleDesc.ToCopy[0]; + + _clientMemory = context.Process.HandleTable.GetKProcess(clientHandle).CpuMemory; + + context.Device.System.KernelContext.Syscall.GetProcessId(out _owner, clientHandle); + + context.ResponseData.Write((uint)NvResult.Success); + + // Close the process and transfer memory handles immediately as we don't use them. + context.Device.System.KernelContext.Syscall.CloseHandle(clientHandle); + context.Device.System.KernelContext.Syscall.CloseHandle(transferMemHandle); + + return ResultCode.Success; + } + + [CommandCmif(4)] + // QueryEvent(s32 fd, u32 event_id) -> (u32, handle<copy, event>) + public ResultCode QueryEvent(ServiceCtx context) + { + NvResult errorCode = EnsureInitialized(); + + if (errorCode == NvResult.Success) + { + int fd = context.RequestData.ReadInt32(); + uint eventId = context.RequestData.ReadUInt32(); + + errorCode = GetDeviceFileFromFd(fd, out NvDeviceFile deviceFile); + + if (errorCode == NvResult.Success) + { + NvInternalResult internalResult = deviceFile.QueryEvent(out int eventHandle, eventId); + + if (internalResult == NvInternalResult.NotImplemented) + { + throw new NvQueryEventNotImplementedException(context, deviceFile, eventId); + } + + errorCode = ConvertInternalErrorCode(internalResult); + + if (errorCode == NvResult.Success) + { + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(eventHandle); + } + } + } + + context.ResponseData.Write((uint)errorCode); + + return ResultCode.Success; + } + + [CommandCmif(5)] + // MapSharedMemory(s32 fd, u32 argument, handle<copy, shared_memory>) -> u32 error_code + public ResultCode MapSharedMemory(ServiceCtx context) + { + NvResult errorCode = EnsureInitialized(); + + if (errorCode == NvResult.Success) + { + int fd = context.RequestData.ReadInt32(); + uint argument = context.RequestData.ReadUInt32(); + int sharedMemoryHandle = context.Request.HandleDesc.ToCopy[0]; + + errorCode = GetDeviceFileFromFd(fd, out NvDeviceFile deviceFile); + + if (errorCode == NvResult.Success) + { + errorCode = ConvertInternalErrorCode(deviceFile.MapSharedMemory(sharedMemoryHandle, argument)); + } + } + + context.ResponseData.Write((uint)errorCode); + + return ResultCode.Success; + } + + [CommandCmif(6)] + // GetStatus() -> (unknown<0x20>, u32 error_code) + public ResultCode GetStatus(ServiceCtx context) + { + // TODO: When transfer memory will be implemented, check if it's mapped instead. + if (_transferMemInitialized) + { + // TODO: Populate values when more RE will be done. + NvStatus nvStatus = new NvStatus + { + MemoryValue1 = 0, // GetMemStats(transfer_memory + 0x60, 3) + MemoryValue2 = 0, // GetMemStats(transfer_memory + 0x60, 5) + MemoryValue3 = 0, // transfer_memory + 0x78 + MemoryValue4 = 0 // transfer_memory + 0x80 + }; + + context.ResponseData.WriteStruct(nvStatus); + context.ResponseData.Write((uint)NvResult.Success); + + Logger.Stub?.PrintStub(LogClass.ServiceNv); + } + else + { + context.ResponseData.Write((uint)NvResult.NotInitialized); + } + + return ResultCode.Success; + } + + [CommandCmif(7)] + // ForceSetClientPid(u64) -> u32 error_code + public ResultCode ForceSetClientPid(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(8)] + // SetClientPID(u64, pid) -> u32 error_code + public ResultCode SetClientPid(ServiceCtx context) + { + long pid = context.RequestData.ReadInt64(); + + context.ResponseData.Write(0); + + return ResultCode.Success; + } + + [CommandCmif(9)] + // DumpGraphicsMemoryInfo() + public ResultCode DumpGraphicsMemoryInfo(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + return ResultCode.Success; + } + + [CommandCmif(10)] // 3.0.0+ + // InitializeDevtools(u32, handle<copy>) -> u32 error_code; + public ResultCode InitializeDevtools(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(11)] // 3.0.0+ + // Ioctl2(s32 fd, u32 ioctl_cmd, buffer<bytes, 0x21> in_args, buffer<bytes, 0x21> inline_in_buffer) -> (u32 error_code, buffer<bytes, 0x22> out_args) + public ResultCode Ioctl2(ServiceCtx context) + { + NvResult errorCode = EnsureInitialized(); + + if (errorCode == NvResult.Success) + { + int fd = context.RequestData.ReadInt32(); + NvIoctl ioctlCommand = context.RequestData.ReadStruct<NvIoctl>(); + + (ulong inlineInBufferPosition, ulong inlineInBufferSize) = context.Request.GetBufferType0x21(1); + + errorCode = GetIoctlArgument(context, ioctlCommand, out Span<byte> arguments); + + byte[] temp = new byte[inlineInBufferSize]; + + context.Memory.Read(inlineInBufferPosition, temp); + + Span<byte> inlineInBuffer = new Span<byte>(temp); + + if (errorCode == NvResult.Success) + { + errorCode = GetDeviceFileFromFd(fd, out NvDeviceFile deviceFile); + + if (errorCode == NvResult.Success) + { + NvInternalResult internalResult = deviceFile.Ioctl2(ioctlCommand, arguments, inlineInBuffer); + + if (internalResult == NvInternalResult.NotImplemented) + { + throw new NvIoctlNotImplementedException(context, deviceFile, ioctlCommand); + } + + errorCode = ConvertInternalErrorCode(internalResult); + + if ((ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0) + { + context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments.ToArray()); + } + } + } + } + + context.ResponseData.Write((uint)errorCode); + + return ResultCode.Success; + } + + [CommandCmif(12)] // 3.0.0+ + // Ioctl3(s32 fd, u32 ioctl_cmd, buffer<bytes, 0x21> in_args) -> (u32 error_code, buffer<bytes, 0x22> out_args, buffer<bytes, 0x22> inline_out_buffer) + public ResultCode Ioctl3(ServiceCtx context) + { + NvResult errorCode = EnsureInitialized(); + + if (errorCode == NvResult.Success) + { + int fd = context.RequestData.ReadInt32(); + NvIoctl ioctlCommand = context.RequestData.ReadStruct<NvIoctl>(); + + (ulong inlineOutBufferPosition, ulong inlineOutBufferSize) = context.Request.GetBufferType0x22(1); + + errorCode = GetIoctlArgument(context, ioctlCommand, out Span<byte> arguments); + + byte[] temp = new byte[inlineOutBufferSize]; + + context.Memory.Read(inlineOutBufferPosition, temp); + + Span<byte> inlineOutBuffer = new Span<byte>(temp); + + if (errorCode == NvResult.Success) + { + errorCode = GetDeviceFileFromFd(fd, out NvDeviceFile deviceFile); + + if (errorCode == NvResult.Success) + { + NvInternalResult internalResult = deviceFile.Ioctl3(ioctlCommand, arguments, inlineOutBuffer); + + if (internalResult == NvInternalResult.NotImplemented) + { + throw new NvIoctlNotImplementedException(context, deviceFile, ioctlCommand); + } + + errorCode = ConvertInternalErrorCode(internalResult); + + if ((ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0) + { + context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments.ToArray()); + context.Memory.Write(inlineOutBufferPosition, inlineOutBuffer.ToArray()); + } + } + } + } + + context.ResponseData.Write((uint)errorCode); + + return ResultCode.Success; + } + + [CommandCmif(13)] // 3.0.0+ + // FinishInitialize(unknown<8>) + public ResultCode FinishInitialize(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + return ResultCode.Success; + } + + public static void Destroy() + { + NvHostChannelDeviceFile.Destroy(); + + foreach (object entry in DeviceFileIdRegistry.Values) + { + NvDeviceFile deviceFile = (NvDeviceFile)entry; + + deviceFile.Close(); + } + + DeviceFileIdRegistry.Clear(); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/INvGemControl.cs b/src/Ryujinx.HLE/HOS/Services/Nv/INvGemControl.cs new file mode 100644 index 00000000..7bf99ed1 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/INvGemControl.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nv +{ + [Service("nvgem:c")] + class INvGemControl : IpcService + { + public INvGemControl(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/INvGemCoreDump.cs b/src/Ryujinx.HLE/HOS/Services/Nv/INvGemCoreDump.cs new file mode 100644 index 00000000..ff3774da --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/INvGemCoreDump.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nv +{ + [Service("nvgem:cd")] + class INvGemCoreDump : IpcService + { + public INvGemCoreDump(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvDeviceFile.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvDeviceFile.cs new file mode 100644 index 00000000..9568fc84 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvDeviceFile.cs @@ -0,0 +1,94 @@ +using Ryujinx.Common.Logging; +using System; +using System.Diagnostics; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices +{ + abstract class NvDeviceFile + { + public readonly ServiceCtx Context; + public readonly ulong Owner; + + public string Path; + + public NvDeviceFile(ServiceCtx context, ulong owner) + { + Context = context; + Owner = owner; + } + + public virtual NvInternalResult QueryEvent(out int eventHandle, uint eventId) + { + eventHandle = 0; + + return NvInternalResult.NotImplemented; + } + + public virtual NvInternalResult MapSharedMemory(int sharedMemoryHandle, uint argument) + { + // Close shared memory immediately as we don't use it. + Context.Device.System.KernelContext.Syscall.CloseHandle(sharedMemoryHandle); + + return NvInternalResult.NotImplemented; + } + + public virtual NvInternalResult Ioctl(NvIoctl command, Span<byte> arguments) + { + return NvInternalResult.NotImplemented; + } + + public virtual NvInternalResult Ioctl2(NvIoctl command, Span<byte> arguments, Span<byte> inlineInBuffer) + { + return NvInternalResult.NotImplemented; + } + + public virtual NvInternalResult Ioctl3(NvIoctl command, Span<byte> arguments, Span<byte> inlineOutBuffer) + { + return NvInternalResult.NotImplemented; + } + + protected delegate NvInternalResult IoctlProcessor<T>(ref T arguments); + protected delegate NvInternalResult IoctlProcessorSpan<T>(Span<T> arguments); + protected delegate NvInternalResult IoctlProcessorInline<T, T1>(ref T arguments, ref T1 inlineData); + protected delegate NvInternalResult IoctlProcessorInlineSpan<T, T1>(ref T arguments, Span<T1> inlineData); + + private static NvInternalResult PrintResult(MethodInfo info, NvInternalResult result) + { + Logger.Debug?.Print(LogClass.ServiceNv, $"{info.Name} returned result {result}"); + + return result; + } + + protected static NvInternalResult CallIoctlMethod<T>(IoctlProcessor<T> callback, Span<byte> arguments) where T : struct + { + Debug.Assert(arguments.Length == Unsafe.SizeOf<T>()); + + return PrintResult(callback.Method, callback(ref MemoryMarshal.Cast<byte, T>(arguments)[0])); + } + + protected static NvInternalResult CallIoctlMethod<T, T1>(IoctlProcessorInline<T, T1> callback, Span<byte> arguments, Span<byte> inlineBuffer) where T : struct where T1 : struct + { + Debug.Assert(arguments.Length == Unsafe.SizeOf<T>()); + Debug.Assert(inlineBuffer.Length == Unsafe.SizeOf<T1>()); + + return PrintResult(callback.Method, callback(ref MemoryMarshal.Cast<byte, T>(arguments)[0], ref MemoryMarshal.Cast<byte, T1>(inlineBuffer)[0])); + } + + protected static NvInternalResult CallIoctlMethod<T>(IoctlProcessorSpan<T> callback, Span<byte> arguments) where T : struct + { + return PrintResult(callback.Method, callback(MemoryMarshal.Cast<byte, T>(arguments))); + } + + protected static NvInternalResult CallIoctlMethod<T, T1>(IoctlProcessorInlineSpan<T, T1> callback, Span<byte> arguments, Span<byte> inlineBuffer) where T : struct where T1 : struct + { + Debug.Assert(arguments.Length == Unsafe.SizeOf<T>()); + + return PrintResult(callback.Method, callback(ref MemoryMarshal.Cast<byte, T>(arguments)[0], MemoryMarshal.Cast<byte, T1>(inlineBuffer))); + } + + public abstract void Close(); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/NvHostAsGpuDeviceFile.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/NvHostAsGpuDeviceFile.cs new file mode 100644 index 00000000..0e0fe7f2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/NvHostAsGpuDeviceFile.cs @@ -0,0 +1,401 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap; +using Ryujinx.Memory; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu +{ + class NvHostAsGpuDeviceFile : NvDeviceFile + { + private const uint SmallPageSize = 0x1000; + private const uint BigPageSize = 0x10000; + + private static readonly uint[] _pageSizes = new uint[] { SmallPageSize, BigPageSize }; + + private const ulong SmallRegionLimit = 0x400000000UL; // 16 GiB + private const ulong DefaultUserSize = 1UL << 37; + + private readonly struct VmRegion + { + public ulong Start { get; } + public ulong Limit { get; } + + public VmRegion(ulong start, ulong limit) + { + Start = start; + Limit = limit; + } + } + + private static readonly VmRegion[] _vmRegions = new VmRegion[] + { + new VmRegion((ulong)BigPageSize << 16, SmallRegionLimit), + new VmRegion(SmallRegionLimit, DefaultUserSize) + }; + + private readonly AddressSpaceContext _asContext; + private readonly NvMemoryAllocator _memoryAllocator; + + public NvHostAsGpuDeviceFile(ServiceCtx context, IVirtualMemoryManager memory, ulong owner) : base(context, owner) + { + _asContext = new AddressSpaceContext(context.Device.Gpu.CreateMemoryManager(owner)); + _memoryAllocator = new NvMemoryAllocator(); + } + + public override NvInternalResult Ioctl(NvIoctl command, Span<byte> arguments) + { + NvInternalResult result = NvInternalResult.NotImplemented; + + if (command.Type == NvIoctl.NvGpuAsMagic) + { + switch (command.Number) + { + case 0x01: + result = CallIoctlMethod<BindChannelArguments>(BindChannel, arguments); + break; + case 0x02: + result = CallIoctlMethod<AllocSpaceArguments>(AllocSpace, arguments); + break; + case 0x03: + result = CallIoctlMethod<FreeSpaceArguments>(FreeSpace, arguments); + break; + case 0x05: + result = CallIoctlMethod<UnmapBufferArguments>(UnmapBuffer, arguments); + break; + case 0x06: + result = CallIoctlMethod<MapBufferExArguments>(MapBufferEx, arguments); + break; + case 0x08: + result = CallIoctlMethod<GetVaRegionsArguments>(GetVaRegions, arguments); + break; + case 0x09: + result = CallIoctlMethod<InitializeExArguments>(InitializeEx, arguments); + break; + case 0x14: + result = CallIoctlMethod<RemapArguments>(Remap, arguments); + break; + } + } + + return result; + } + + public override NvInternalResult Ioctl3(NvIoctl command, Span<byte> arguments, Span<byte> inlineOutBuffer) + { + NvInternalResult result = NvInternalResult.NotImplemented; + + if (command.Type == NvIoctl.NvGpuAsMagic) + { + switch (command.Number) + { + case 0x08: + // This is the same as the one in ioctl as inlineOutBuffer is empty. + result = CallIoctlMethod<GetVaRegionsArguments>(GetVaRegions, arguments); + break; + } + } + + return result; + } + + private NvInternalResult BindChannel(ref BindChannelArguments arguments) + { + var channelDeviceFile = INvDrvServices.DeviceFileIdRegistry.GetData<NvHostChannelDeviceFile>(arguments.Fd); + if (channelDeviceFile == null) + { + // TODO: Return invalid Fd error. + } + + channelDeviceFile.Channel.BindMemory(_asContext.Gmm); + + return NvInternalResult.Success; + } + + private NvInternalResult AllocSpace(ref AllocSpaceArguments arguments) + { + ulong size = (ulong)arguments.Pages * (ulong)arguments.PageSize; + + NvInternalResult result = NvInternalResult.Success; + + lock (_asContext) + { + // Note: When the fixed offset flag is not set, + // the Offset field holds the alignment size instead. + if ((arguments.Flags & AddressSpaceFlags.FixedOffset) != 0) + { + bool regionInUse = _memoryAllocator.IsRegionInUse(arguments.Offset, size, out ulong freeAddressStartPosition); + ulong address; + + if (!regionInUse) + { + _memoryAllocator.AllocateRange(arguments.Offset, size, freeAddressStartPosition); + address = freeAddressStartPosition; + } + else + { + address = NvMemoryAllocator.PteUnmapped; + } + + arguments.Offset = address; + } + else + { + ulong address = _memoryAllocator.GetFreeAddress(size, out ulong freeAddressStartPosition, arguments.Offset); + if (address != NvMemoryAllocator.PteUnmapped) + { + _memoryAllocator.AllocateRange(address, size, freeAddressStartPosition); + } + + arguments.Offset = address; + } + + if (arguments.Offset == NvMemoryAllocator.PteUnmapped) + { + arguments.Offset = 0; + + Logger.Warning?.Print(LogClass.ServiceNv, $"Failed to allocate size {size:x16}!"); + + result = NvInternalResult.OutOfMemory; + } + else + { + _asContext.AddReservation(arguments.Offset, size); + } + } + + return result; + } + + private NvInternalResult FreeSpace(ref FreeSpaceArguments arguments) + { + ulong size = (ulong)arguments.Pages * (ulong)arguments.PageSize; + + NvInternalResult result = NvInternalResult.Success; + + lock (_asContext) + { + if (_asContext.RemoveReservation(arguments.Offset)) + { + _memoryAllocator.DeallocateRange(arguments.Offset, size); + _asContext.Gmm.Unmap(arguments.Offset, size); + } + else + { + Logger.Warning?.Print(LogClass.ServiceNv, + $"Failed to free offset 0x{arguments.Offset:x16} size 0x{size:x16}!"); + + result = NvInternalResult.InvalidInput; + } + } + + return result; + } + + private NvInternalResult UnmapBuffer(ref UnmapBufferArguments arguments) + { + lock (_asContext) + { + if (_asContext.RemoveMap(arguments.Offset, out ulong size)) + { + if (size != 0) + { + _memoryAllocator.DeallocateRange(arguments.Offset, size); + _asContext.Gmm.Unmap(arguments.Offset, size); + } + } + else + { + Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid buffer offset {arguments.Offset:x16}!"); + } + } + + return NvInternalResult.Success; + } + + private NvInternalResult MapBufferEx(ref MapBufferExArguments arguments) + { + const string MapErrorMsg = "Failed to map fixed buffer with offset 0x{0:x16}, size 0x{1:x16} and alignment 0x{2:x16}!"; + + ulong physicalAddress; + + if ((arguments.Flags & AddressSpaceFlags.RemapSubRange) != 0) + { + lock (_asContext) + { + if (_asContext.TryGetMapPhysicalAddress(arguments.Offset, out physicalAddress)) + { + ulong virtualAddress = arguments.Offset + arguments.BufferOffset; + + physicalAddress += arguments.BufferOffset; + _asContext.Gmm.Map(physicalAddress, virtualAddress, arguments.MappingSize, (PteKind)arguments.Kind); + + return NvInternalResult.Success; + } + else + { + Logger.Warning?.Print(LogClass.ServiceNv, $"Address 0x{arguments.Offset:x16} not mapped!"); + + return NvInternalResult.InvalidInput; + } + } + } + + NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, arguments.NvMapHandle); + + if (map == null) + { + Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid NvMap handle 0x{arguments.NvMapHandle:x8}!"); + + return NvInternalResult.InvalidInput; + } + + ulong pageSize = (ulong)arguments.PageSize; + + if (pageSize == 0) + { + pageSize = (ulong)map.Align; + } + + physicalAddress = map.Address + arguments.BufferOffset; + + ulong size = arguments.MappingSize; + + if (size == 0) + { + size = (uint)map.Size; + } + + NvInternalResult result = NvInternalResult.Success; + + lock (_asContext) + { + // Note: When the fixed offset flag is not set, + // the Offset field holds the alignment size instead. + bool virtualAddressAllocated = (arguments.Flags & AddressSpaceFlags.FixedOffset) == 0; + + if (!virtualAddressAllocated) + { + if (_asContext.ValidateFixedBuffer(arguments.Offset, size, pageSize)) + { + _asContext.Gmm.Map(physicalAddress, arguments.Offset, size, (PteKind)arguments.Kind); + } + else + { + string message = string.Format(MapErrorMsg, arguments.Offset, size, pageSize); + + Logger.Warning?.Print(LogClass.ServiceNv, message); + + result = NvInternalResult.InvalidInput; + } + } + else + { + ulong va = _memoryAllocator.GetFreeAddress(size, out ulong freeAddressStartPosition, pageSize); + if (va != NvMemoryAllocator.PteUnmapped) + { + _memoryAllocator.AllocateRange(va, size, freeAddressStartPosition); + } + + _asContext.Gmm.Map(physicalAddress, va, size, (PteKind)arguments.Kind); + arguments.Offset = va; + } + + if (arguments.Offset == NvMemoryAllocator.PteUnmapped) + { + arguments.Offset = 0; + + Logger.Warning?.Print(LogClass.ServiceNv, $"Failed to map size 0x{size:x16}!"); + + result = NvInternalResult.InvalidInput; + } + else + { + _asContext.AddMap(arguments.Offset, size, physicalAddress, virtualAddressAllocated); + } + } + + return result; + } + + private NvInternalResult GetVaRegions(ref GetVaRegionsArguments arguments) + { + int vaRegionStructSize = Unsafe.SizeOf<VaRegion>(); + + Debug.Assert(vaRegionStructSize == 0x18); + Debug.Assert(_pageSizes.Length == 2); + + uint writeEntries = (uint)(arguments.BufferSize / vaRegionStructSize); + if (writeEntries > _pageSizes.Length) + { + writeEntries = (uint)_pageSizes.Length; + } + + for (uint i = 0; i < writeEntries; i++) + { + ref var region = ref arguments.Regions[(int)i]; + + var vmRegion = _vmRegions[i]; + uint pageSize = _pageSizes[i]; + + region.PageSize = pageSize; + region.Offset = vmRegion.Start; + region.Pages = (vmRegion.Limit - vmRegion.Start) / pageSize; + region.Padding = 0; + } + + arguments.BufferSize = (uint)(_pageSizes.Length * vaRegionStructSize); + + return NvInternalResult.Success; + } + + private NvInternalResult InitializeEx(ref InitializeExArguments arguments) + { + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult Remap(Span<RemapArguments> arguments) + { + MemoryManager gmm = _asContext.Gmm; + + for (int index = 0; index < arguments.Length; index++) + { + ref RemapArguments argument = ref arguments[index]; + ulong gpuVa = (ulong)argument.GpuOffset << 16; + ulong size = (ulong)argument.Pages << 16; + int nvmapHandle = argument.NvMapHandle; + + if (nvmapHandle == 0) + { + gmm.Unmap(gpuVa, size); + } + else + { + ulong mapOffs = (ulong)argument.MapOffset << 16; + PteKind kind = (PteKind)argument.Kind; + + NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, nvmapHandle); + + if (map == null) + { + Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid NvMap handle 0x{nvmapHandle:x8}!"); + + return NvInternalResult.InvalidInput; + } + + gmm.Map(mapOffs + map.Address, gpuVa, size, kind); + } + } + + return NvInternalResult.Success; + } + + public override void Close() { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceContext.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceContext.cs new file mode 100644 index 00000000..ab9d798e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceContext.cs @@ -0,0 +1,190 @@ +using Ryujinx.Graphics.Gpu.Memory; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types +{ + class AddressSpaceContext + { + private class Range + { + public ulong Start { get; } + public ulong End { get; } + + public Range(ulong address, ulong size) + { + Start = address; + End = size + Start; + } + } + + private class MappedMemory : Range + { + public ulong PhysicalAddress { get; } + public bool VaAllocated { get; } + + public MappedMemory(ulong address, ulong size, ulong physicalAddress, bool vaAllocated) : base(address, size) + { + PhysicalAddress = physicalAddress; + VaAllocated = vaAllocated; + } + } + + public MemoryManager Gmm { get; } + + private readonly SortedList<ulong, Range> _maps; + private readonly SortedList<ulong, Range> _reservations; + + public AddressSpaceContext(MemoryManager gmm) + { + Gmm = gmm; + + _maps = new SortedList<ulong, Range>(); + _reservations = new SortedList<ulong, Range>(); + } + + public bool ValidateFixedBuffer(ulong address, ulong size, ulong alignment) + { + ulong mapEnd = address + size; + + // Check if size is valid (0 is also not allowed). + if (mapEnd <= address) + { + return false; + } + + // Check if address is aligned. + if ((address & (alignment - 1)) != 0) + { + return false; + } + + // Check if region is reserved. + if (BinarySearch(_reservations, address) == null) + { + return false; + } + + // Check for overlap with already mapped buffers. + Range map = BinarySearchLt(_maps, mapEnd); + + if (map != null && map.End > address) + { + return false; + } + + return true; + } + + public void AddMap(ulong gpuVa, ulong size, ulong physicalAddress, bool vaAllocated) + { + _maps.Add(gpuVa, new MappedMemory(gpuVa, size, physicalAddress, vaAllocated)); + } + + public bool RemoveMap(ulong gpuVa, out ulong size) + { + size = 0; + + if (_maps.Remove(gpuVa, out Range value)) + { + MappedMemory map = (MappedMemory)value; + + if (map.VaAllocated) + { + size = (map.End - map.Start); + } + + return true; + } + + return false; + } + + public bool TryGetMapPhysicalAddress(ulong gpuVa, out ulong physicalAddress) + { + Range map = BinarySearch(_maps, gpuVa); + + if (map != null) + { + physicalAddress = ((MappedMemory)map).PhysicalAddress; + return true; + } + + physicalAddress = 0; + return false; + } + + public void AddReservation(ulong gpuVa, ulong size) + { + _reservations.Add(gpuVa, new Range(gpuVa, size)); + } + + public bool RemoveReservation(ulong gpuVa) + { + return _reservations.Remove(gpuVa); + } + + private static Range BinarySearch(SortedList<ulong, Range> list, ulong address) + { + int left = 0; + int right = list.Count - 1; + + while (left <= right) + { + int size = right - left; + + int middle = left + (size >> 1); + + Range rg = list.Values[middle]; + + if (address >= rg.Start && address < rg.End) + { + return rg; + } + + if (address < rg.Start) + { + right = middle - 1; + } + else + { + left = middle + 1; + } + } + + return null; + } + + private static Range BinarySearchLt(SortedList<ulong, Range> list, ulong address) + { + Range ltRg = null; + + int left = 0; + int right = list.Count - 1; + + while (left <= right) + { + int size = right - left; + + int middle = left + (size >> 1); + + Range rg = list.Values[middle]; + + if (address < rg.Start) + { + right = middle - 1; + } + else + { + left = middle + 1; + + if (address > rg.Start) + { + ltRg = rg; + } + } + } + + return ltRg; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceFlags.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceFlags.cs new file mode 100644 index 00000000..611cf78b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceFlags.cs @@ -0,0 +1,11 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types +{ + [Flags] + enum AddressSpaceFlags : uint + { + FixedOffset = 1, + RemapSubRange = 0x100, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AllocSpaceArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AllocSpaceArguments.cs new file mode 100644 index 00000000..d6dbbc26 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AllocSpaceArguments.cs @@ -0,0 +1,14 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct AllocSpaceArguments + { + public uint Pages; + public uint PageSize; + public AddressSpaceFlags Flags; + public uint Padding; + public ulong Offset; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/BindChannelArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/BindChannelArguments.cs new file mode 100644 index 00000000..9c6568a3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/BindChannelArguments.cs @@ -0,0 +1,10 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct BindChannelArguments + { + public int Fd; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/FreeSpaceArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/FreeSpaceArguments.cs new file mode 100644 index 00000000..b25d295a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/FreeSpaceArguments.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct FreeSpaceArguments + { + public ulong Offset; + public uint Pages; + public uint PageSize; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/GetVaRegionsArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/GetVaRegionsArguments.cs new file mode 100644 index 00000000..dcb5b49e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/GetVaRegionsArguments.cs @@ -0,0 +1,23 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct VaRegion + { + public ulong Offset; + public uint PageSize; + public uint Padding; + public ulong Pages; + } + + [StructLayout(LayoutKind.Sequential)] + struct GetVaRegionsArguments + { + public ulong Unused; + public uint BufferSize; + public uint Padding; + public Array2<VaRegion> Regions; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/InitializeExArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/InitializeExArguments.cs new file mode 100644 index 00000000..882bda59 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/InitializeExArguments.cs @@ -0,0 +1,16 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct InitializeExArguments + { + public uint Flags; + public int AsFd; + public uint BigPageSize; + public uint Reserved; + public ulong Unknown0; + public ulong Unknown1; + public ulong Unknown2; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/MapBufferExArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/MapBufferExArguments.cs new file mode 100644 index 00000000..278793a0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/MapBufferExArguments.cs @@ -0,0 +1,16 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct MapBufferExArguments + { + public AddressSpaceFlags Flags; + public int Kind; + public int NvMapHandle; + public int PageSize; + public ulong BufferOffset; + public ulong MappingSize; + public ulong Offset; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/RemapArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/RemapArguments.cs new file mode 100644 index 00000000..bc149d42 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/RemapArguments.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct RemapArguments + { + public ushort Flags; + public ushort Kind; + public int NvMapHandle; + public uint MapOffset; + public uint GpuOffset; + public uint Pages; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/UnmapBufferArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/UnmapBufferArguments.cs new file mode 100644 index 00000000..8fc4646e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/UnmapBufferArguments.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types +{ + struct UnmapBufferArguments + { +#pragma warning disable CS0649 + public ulong Offset; +#pragma warning restore CS0649 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/ChannelInitialization.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/ChannelInitialization.cs new file mode 100644 index 00000000..87a06bd3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/ChannelInitialization.cs @@ -0,0 +1,1361 @@ +using Ryujinx.Graphics.Gpu; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel +{ + static class ChannelInitialization + { + public static void InitializeState(GpuChannel channel) + { + channel.Write(ClassId.Threed, 0x800, 0x0); + channel.Write(ClassId.Threed, 0x840, 0x0); + channel.Write(ClassId.Threed, 0x880, 0x0); + channel.Write(ClassId.Threed, 0x8C0, 0x0); + channel.Write(ClassId.Threed, 0x900, 0x0); + channel.Write(ClassId.Threed, 0x940, 0x0); + channel.Write(ClassId.Threed, 0x980, 0x0); + channel.Write(ClassId.Threed, 0x9C0, 0x0); + channel.Write(ClassId.Threed, 0x804, 0x0); + channel.Write(ClassId.Threed, 0x844, 0x0); + channel.Write(ClassId.Threed, 0x884, 0x0); + channel.Write(ClassId.Threed, 0x8C4, 0x0); + channel.Write(ClassId.Threed, 0x904, 0x0); + channel.Write(ClassId.Threed, 0x944, 0x0); + channel.Write(ClassId.Threed, 0x984, 0x0); + channel.Write(ClassId.Threed, 0x9C4, 0x0); + channel.Write(ClassId.Threed, 0x808, 0x400); + channel.Write(ClassId.Threed, 0x848, 0x400); + channel.Write(ClassId.Threed, 0x888, 0x400); + channel.Write(ClassId.Threed, 0x8C8, 0x400); + channel.Write(ClassId.Threed, 0x908, 0x400); + channel.Write(ClassId.Threed, 0x948, 0x400); + channel.Write(ClassId.Threed, 0x988, 0x400); + channel.Write(ClassId.Threed, 0x9C8, 0x400); + channel.Write(ClassId.Threed, 0x80C, 0x300); + channel.Write(ClassId.Threed, 0x84C, 0x300); + channel.Write(ClassId.Threed, 0x88C, 0x300); + channel.Write(ClassId.Threed, 0x8CC, 0x300); + channel.Write(ClassId.Threed, 0x90C, 0x300); + channel.Write(ClassId.Threed, 0x94C, 0x300); + channel.Write(ClassId.Threed, 0x98C, 0x300); + channel.Write(ClassId.Threed, 0x9CC, 0x300); + channel.Write(ClassId.Threed, 0x810, 0xCF); + channel.Write(ClassId.Threed, 0x850, 0x0); + channel.Write(ClassId.Threed, 0x890, 0x0); + channel.Write(ClassId.Threed, 0x8D0, 0x0); + channel.Write(ClassId.Threed, 0x910, 0x0); + channel.Write(ClassId.Threed, 0x950, 0x0); + channel.Write(ClassId.Threed, 0x990, 0x0); + channel.Write(ClassId.Threed, 0x9D0, 0x0); + channel.Write(ClassId.Threed, 0x814, 0x40); + channel.Write(ClassId.Threed, 0x854, 0x40); + channel.Write(ClassId.Threed, 0x894, 0x40); + channel.Write(ClassId.Threed, 0x8D4, 0x40); + channel.Write(ClassId.Threed, 0x914, 0x40); + channel.Write(ClassId.Threed, 0x954, 0x40); + channel.Write(ClassId.Threed, 0x994, 0x40); + channel.Write(ClassId.Threed, 0x9D4, 0x40); + channel.Write(ClassId.Threed, 0x818, 0x1); + channel.Write(ClassId.Threed, 0x858, 0x1); + channel.Write(ClassId.Threed, 0x898, 0x1); + channel.Write(ClassId.Threed, 0x8D8, 0x1); + channel.Write(ClassId.Threed, 0x918, 0x1); + channel.Write(ClassId.Threed, 0x958, 0x1); + channel.Write(ClassId.Threed, 0x998, 0x1); + channel.Write(ClassId.Threed, 0x9D8, 0x1); + channel.Write(ClassId.Threed, 0x81C, 0x0); + channel.Write(ClassId.Threed, 0x85C, 0x0); + channel.Write(ClassId.Threed, 0x89C, 0x0); + channel.Write(ClassId.Threed, 0x8DC, 0x0); + channel.Write(ClassId.Threed, 0x91C, 0x0); + channel.Write(ClassId.Threed, 0x95C, 0x0); + channel.Write(ClassId.Threed, 0x99C, 0x0); + channel.Write(ClassId.Threed, 0x9DC, 0x0); + channel.Write(ClassId.Threed, 0x820, 0x0); + channel.Write(ClassId.Threed, 0x860, 0x0); + channel.Write(ClassId.Threed, 0x8A0, 0x0); + channel.Write(ClassId.Threed, 0x8E0, 0x0); + channel.Write(ClassId.Threed, 0x920, 0x0); + channel.Write(ClassId.Threed, 0x960, 0x0); + channel.Write(ClassId.Threed, 0x9A0, 0x0); + channel.Write(ClassId.Threed, 0x9E0, 0x0); + channel.Write(ClassId.Threed, 0x1C00, 0x0); + channel.Write(ClassId.Threed, 0x1C10, 0x0); + channel.Write(ClassId.Threed, 0x1C20, 0x0); + channel.Write(ClassId.Threed, 0x1C30, 0x0); + channel.Write(ClassId.Threed, 0x1C40, 0x0); + channel.Write(ClassId.Threed, 0x1C50, 0x0); + channel.Write(ClassId.Threed, 0x1C60, 0x0); + channel.Write(ClassId.Threed, 0x1C70, 0x0); + channel.Write(ClassId.Threed, 0x1C80, 0x0); + channel.Write(ClassId.Threed, 0x1C90, 0x0); + channel.Write(ClassId.Threed, 0x1CA0, 0x0); + channel.Write(ClassId.Threed, 0x1CB0, 0x0); + channel.Write(ClassId.Threed, 0x1CC0, 0x0); + channel.Write(ClassId.Threed, 0x1CD0, 0x0); + channel.Write(ClassId.Threed, 0x1CE0, 0x0); + channel.Write(ClassId.Threed, 0x1CF0, 0x0); + channel.Write(ClassId.Threed, 0x1C04, 0x0); + channel.Write(ClassId.Threed, 0x1C14, 0x0); + channel.Write(ClassId.Threed, 0x1C24, 0x0); + channel.Write(ClassId.Threed, 0x1C34, 0x0); + channel.Write(ClassId.Threed, 0x1C44, 0x0); + channel.Write(ClassId.Threed, 0x1C54, 0x0); + channel.Write(ClassId.Threed, 0x1C64, 0x0); + channel.Write(ClassId.Threed, 0x1C74, 0x0); + channel.Write(ClassId.Threed, 0x1C84, 0x0); + channel.Write(ClassId.Threed, 0x1C94, 0x0); + channel.Write(ClassId.Threed, 0x1CA4, 0x0); + channel.Write(ClassId.Threed, 0x1CB4, 0x0); + channel.Write(ClassId.Threed, 0x1CC4, 0x0); + channel.Write(ClassId.Threed, 0x1CD4, 0x0); + channel.Write(ClassId.Threed, 0x1CE4, 0x0); + channel.Write(ClassId.Threed, 0x1CF4, 0x0); + channel.Write(ClassId.Threed, 0x1C08, 0x0); + channel.Write(ClassId.Threed, 0x1C18, 0x0); + channel.Write(ClassId.Threed, 0x1C28, 0x0); + channel.Write(ClassId.Threed, 0x1C38, 0x0); + channel.Write(ClassId.Threed, 0x1C48, 0x0); + channel.Write(ClassId.Threed, 0x1C58, 0x0); + channel.Write(ClassId.Threed, 0x1C68, 0x0); + channel.Write(ClassId.Threed, 0x1C78, 0x0); + channel.Write(ClassId.Threed, 0x1C88, 0x0); + channel.Write(ClassId.Threed, 0x1C98, 0x0); + channel.Write(ClassId.Threed, 0x1CA8, 0x0); + channel.Write(ClassId.Threed, 0x1CB8, 0x0); + channel.Write(ClassId.Threed, 0x1CC8, 0x0); + channel.Write(ClassId.Threed, 0x1CD8, 0x0); + channel.Write(ClassId.Threed, 0x1CE8, 0x0); + channel.Write(ClassId.Threed, 0x1CF8, 0x0); + channel.Write(ClassId.Threed, 0x1C0C, 0x0); + channel.Write(ClassId.Threed, 0x1C1C, 0x0); + channel.Write(ClassId.Threed, 0x1C2C, 0x0); + channel.Write(ClassId.Threed, 0x1C3C, 0x0); + channel.Write(ClassId.Threed, 0x1C4C, 0x0); + channel.Write(ClassId.Threed, 0x1C5C, 0x0); + channel.Write(ClassId.Threed, 0x1C6C, 0x0); + channel.Write(ClassId.Threed, 0x1C7C, 0x0); + channel.Write(ClassId.Threed, 0x1C8C, 0x0); + channel.Write(ClassId.Threed, 0x1C9C, 0x0); + channel.Write(ClassId.Threed, 0x1CAC, 0x0); + channel.Write(ClassId.Threed, 0x1CBC, 0x0); + channel.Write(ClassId.Threed, 0x1CCC, 0x0); + channel.Write(ClassId.Threed, 0x1CDC, 0x0); + channel.Write(ClassId.Threed, 0x1CEC, 0x0); + channel.Write(ClassId.Threed, 0x1CFC, 0x0); + channel.Write(ClassId.Threed, 0x1D00, 0x0); + channel.Write(ClassId.Threed, 0x1D10, 0x0); + channel.Write(ClassId.Threed, 0x1D20, 0x0); + channel.Write(ClassId.Threed, 0x1D30, 0x0); + channel.Write(ClassId.Threed, 0x1D40, 0x0); + channel.Write(ClassId.Threed, 0x1D50, 0x0); + channel.Write(ClassId.Threed, 0x1D60, 0x0); + channel.Write(ClassId.Threed, 0x1D70, 0x0); + channel.Write(ClassId.Threed, 0x1D80, 0x0); + channel.Write(ClassId.Threed, 0x1D90, 0x0); + channel.Write(ClassId.Threed, 0x1DA0, 0x0); + channel.Write(ClassId.Threed, 0x1DB0, 0x0); + channel.Write(ClassId.Threed, 0x1DC0, 0x0); + channel.Write(ClassId.Threed, 0x1DD0, 0x0); + channel.Write(ClassId.Threed, 0x1DE0, 0x0); + channel.Write(ClassId.Threed, 0x1DF0, 0x0); + channel.Write(ClassId.Threed, 0x1D04, 0x0); + channel.Write(ClassId.Threed, 0x1D14, 0x0); + channel.Write(ClassId.Threed, 0x1D24, 0x0); + channel.Write(ClassId.Threed, 0x1D34, 0x0); + channel.Write(ClassId.Threed, 0x1D44, 0x0); + channel.Write(ClassId.Threed, 0x1D54, 0x0); + channel.Write(ClassId.Threed, 0x1D64, 0x0); + channel.Write(ClassId.Threed, 0x1D74, 0x0); + channel.Write(ClassId.Threed, 0x1D84, 0x0); + channel.Write(ClassId.Threed, 0x1D94, 0x0); + channel.Write(ClassId.Threed, 0x1DA4, 0x0); + channel.Write(ClassId.Threed, 0x1DB4, 0x0); + channel.Write(ClassId.Threed, 0x1DC4, 0x0); + channel.Write(ClassId.Threed, 0x1DD4, 0x0); + channel.Write(ClassId.Threed, 0x1DE4, 0x0); + channel.Write(ClassId.Threed, 0x1DF4, 0x0); + channel.Write(ClassId.Threed, 0x1D08, 0x0); + channel.Write(ClassId.Threed, 0x1D18, 0x0); + channel.Write(ClassId.Threed, 0x1D28, 0x0); + channel.Write(ClassId.Threed, 0x1D38, 0x0); + channel.Write(ClassId.Threed, 0x1D48, 0x0); + channel.Write(ClassId.Threed, 0x1D58, 0x0); + channel.Write(ClassId.Threed, 0x1D68, 0x0); + channel.Write(ClassId.Threed, 0x1D78, 0x0); + channel.Write(ClassId.Threed, 0x1D88, 0x0); + channel.Write(ClassId.Threed, 0x1D98, 0x0); + channel.Write(ClassId.Threed, 0x1DA8, 0x0); + channel.Write(ClassId.Threed, 0x1DB8, 0x0); + channel.Write(ClassId.Threed, 0x1DC8, 0x0); + channel.Write(ClassId.Threed, 0x1DD8, 0x0); + channel.Write(ClassId.Threed, 0x1DE8, 0x0); + channel.Write(ClassId.Threed, 0x1DF8, 0x0); + channel.Write(ClassId.Threed, 0x1D0C, 0x0); + channel.Write(ClassId.Threed, 0x1D1C, 0x0); + channel.Write(ClassId.Threed, 0x1D2C, 0x0); + channel.Write(ClassId.Threed, 0x1D3C, 0x0); + channel.Write(ClassId.Threed, 0x1D4C, 0x0); + channel.Write(ClassId.Threed, 0x1D5C, 0x0); + channel.Write(ClassId.Threed, 0x1D6C, 0x0); + channel.Write(ClassId.Threed, 0x1D7C, 0x0); + channel.Write(ClassId.Threed, 0x1D8C, 0x0); + channel.Write(ClassId.Threed, 0x1D9C, 0x0); + channel.Write(ClassId.Threed, 0x1DAC, 0x0); + channel.Write(ClassId.Threed, 0x1DBC, 0x0); + channel.Write(ClassId.Threed, 0x1DCC, 0x0); + channel.Write(ClassId.Threed, 0x1DDC, 0x0); + channel.Write(ClassId.Threed, 0x1DEC, 0x0); + channel.Write(ClassId.Threed, 0x1DFC, 0x0); + channel.Write(ClassId.Threed, 0x1F00, 0x0); + channel.Write(ClassId.Threed, 0x1F08, 0x0); + channel.Write(ClassId.Threed, 0x1F10, 0x0); + channel.Write(ClassId.Threed, 0x1F18, 0x0); + channel.Write(ClassId.Threed, 0x1F20, 0x0); + channel.Write(ClassId.Threed, 0x1F28, 0x0); + channel.Write(ClassId.Threed, 0x1F30, 0x0); + channel.Write(ClassId.Threed, 0x1F38, 0x0); + channel.Write(ClassId.Threed, 0x1F40, 0x0); + channel.Write(ClassId.Threed, 0x1F48, 0x0); + channel.Write(ClassId.Threed, 0x1F50, 0x0); + channel.Write(ClassId.Threed, 0x1F58, 0x0); + channel.Write(ClassId.Threed, 0x1F60, 0x0); + channel.Write(ClassId.Threed, 0x1F68, 0x0); + channel.Write(ClassId.Threed, 0x1F70, 0x0); + channel.Write(ClassId.Threed, 0x1F78, 0x0); + channel.Write(ClassId.Threed, 0x1F04, 0x0); + channel.Write(ClassId.Threed, 0x1F0C, 0x0); + channel.Write(ClassId.Threed, 0x1F14, 0x0); + channel.Write(ClassId.Threed, 0x1F1C, 0x0); + channel.Write(ClassId.Threed, 0x1F24, 0x0); + channel.Write(ClassId.Threed, 0x1F2C, 0x0); + channel.Write(ClassId.Threed, 0x1F34, 0x0); + channel.Write(ClassId.Threed, 0x1F3C, 0x0); + channel.Write(ClassId.Threed, 0x1F44, 0x0); + channel.Write(ClassId.Threed, 0x1F4C, 0x0); + channel.Write(ClassId.Threed, 0x1F54, 0x0); + channel.Write(ClassId.Threed, 0x1F5C, 0x0); + channel.Write(ClassId.Threed, 0x1F64, 0x0); + channel.Write(ClassId.Threed, 0x1F6C, 0x0); + channel.Write(ClassId.Threed, 0x1F74, 0x0); + channel.Write(ClassId.Threed, 0x1F7C, 0x0); + channel.Write(ClassId.Threed, 0x1F80, 0x0); + channel.Write(ClassId.Threed, 0x1F88, 0x0); + channel.Write(ClassId.Threed, 0x1F90, 0x0); + channel.Write(ClassId.Threed, 0x1F98, 0x0); + channel.Write(ClassId.Threed, 0x1FA0, 0x0); + channel.Write(ClassId.Threed, 0x1FA8, 0x0); + channel.Write(ClassId.Threed, 0x1FB0, 0x0); + channel.Write(ClassId.Threed, 0x1FB8, 0x0); + channel.Write(ClassId.Threed, 0x1FC0, 0x0); + channel.Write(ClassId.Threed, 0x1FC8, 0x0); + channel.Write(ClassId.Threed, 0x1FD0, 0x0); + channel.Write(ClassId.Threed, 0x1FD8, 0x0); + channel.Write(ClassId.Threed, 0x1FE0, 0x0); + channel.Write(ClassId.Threed, 0x1FE8, 0x0); + channel.Write(ClassId.Threed, 0x1FF0, 0x0); + channel.Write(ClassId.Threed, 0x1FF8, 0x0); + channel.Write(ClassId.Threed, 0x1F84, 0x0); + channel.Write(ClassId.Threed, 0x1F8C, 0x0); + channel.Write(ClassId.Threed, 0x1F94, 0x0); + channel.Write(ClassId.Threed, 0x1F9C, 0x0); + channel.Write(ClassId.Threed, 0x1FA4, 0x0); + channel.Write(ClassId.Threed, 0x1FAC, 0x0); + channel.Write(ClassId.Threed, 0x1FB4, 0x0); + channel.Write(ClassId.Threed, 0x1FBC, 0x0); + channel.Write(ClassId.Threed, 0x1FC4, 0x0); + channel.Write(ClassId.Threed, 0x1FCC, 0x0); + channel.Write(ClassId.Threed, 0x1FD4, 0x0); + channel.Write(ClassId.Threed, 0x1FDC, 0x0); + channel.Write(ClassId.Threed, 0x1FE4, 0x0); + channel.Write(ClassId.Threed, 0x1FEC, 0x0); + channel.Write(ClassId.Threed, 0x1FF4, 0x0); + channel.Write(ClassId.Threed, 0x1FFC, 0x0); + channel.Write(ClassId.Threed, 0x2000, 0x0); + channel.Write(ClassId.Threed, 0x2040, 0x11); + channel.Write(ClassId.Threed, 0x2080, 0x20); + channel.Write(ClassId.Threed, 0x20C0, 0x30); + channel.Write(ClassId.Threed, 0x2100, 0x40); + channel.Write(ClassId.Threed, 0x2140, 0x51); + channel.Write(ClassId.Threed, 0x200C, 0x1); + channel.Write(ClassId.Threed, 0x204C, 0x1); + channel.Write(ClassId.Threed, 0x208C, 0x1); + channel.Write(ClassId.Threed, 0x20CC, 0x1); + channel.Write(ClassId.Threed, 0x210C, 0x1); + channel.Write(ClassId.Threed, 0x214C, 0x1); + channel.Write(ClassId.Threed, 0x2010, 0x0); + channel.Write(ClassId.Threed, 0x2050, 0x0); + channel.Write(ClassId.Threed, 0x2090, 0x1); + channel.Write(ClassId.Threed, 0x20D0, 0x2); + channel.Write(ClassId.Threed, 0x2110, 0x3); + channel.Write(ClassId.Threed, 0x2150, 0x4); + channel.Write(ClassId.Threed, 0x380, 0x0); + channel.Write(ClassId.Threed, 0x3A0, 0x0); + channel.Write(ClassId.Threed, 0x3C0, 0x0); + channel.Write(ClassId.Threed, 0x3E0, 0x0); + channel.Write(ClassId.Threed, 0x384, 0x0); + channel.Write(ClassId.Threed, 0x3A4, 0x0); + channel.Write(ClassId.Threed, 0x3C4, 0x0); + channel.Write(ClassId.Threed, 0x3E4, 0x0); + channel.Write(ClassId.Threed, 0x388, 0x0); + channel.Write(ClassId.Threed, 0x3A8, 0x0); + channel.Write(ClassId.Threed, 0x3C8, 0x0); + channel.Write(ClassId.Threed, 0x3E8, 0x0); + channel.Write(ClassId.Threed, 0x38C, 0x0); + channel.Write(ClassId.Threed, 0x3AC, 0x0); + channel.Write(ClassId.Threed, 0x3CC, 0x0); + channel.Write(ClassId.Threed, 0x3EC, 0x0); + channel.Write(ClassId.Threed, 0x700, 0x0); + channel.Write(ClassId.Threed, 0x710, 0x0); + channel.Write(ClassId.Threed, 0x720, 0x0); + channel.Write(ClassId.Threed, 0x730, 0x0); + channel.Write(ClassId.Threed, 0x704, 0x0); + channel.Write(ClassId.Threed, 0x714, 0x0); + channel.Write(ClassId.Threed, 0x724, 0x0); + channel.Write(ClassId.Threed, 0x734, 0x0); + channel.Write(ClassId.Threed, 0x708, 0x0); + channel.Write(ClassId.Threed, 0x718, 0x0); + channel.Write(ClassId.Threed, 0x728, 0x0); + channel.Write(ClassId.Threed, 0x738, 0x0); + channel.Write(ClassId.Threed, 0x2800, 0x0); + channel.Write(ClassId.Threed, 0x2804, 0x0); + channel.Write(ClassId.Threed, 0x2808, 0x0); + channel.Write(ClassId.Threed, 0x280C, 0x0); + channel.Write(ClassId.Threed, 0x2810, 0x0); + channel.Write(ClassId.Threed, 0x2814, 0x0); + channel.Write(ClassId.Threed, 0x2818, 0x0); + channel.Write(ClassId.Threed, 0x281C, 0x0); + channel.Write(ClassId.Threed, 0x2820, 0x0); + channel.Write(ClassId.Threed, 0x2824, 0x0); + channel.Write(ClassId.Threed, 0x2828, 0x0); + channel.Write(ClassId.Threed, 0x282C, 0x0); + channel.Write(ClassId.Threed, 0x2830, 0x0); + channel.Write(ClassId.Threed, 0x2834, 0x0); + channel.Write(ClassId.Threed, 0x2838, 0x0); + channel.Write(ClassId.Threed, 0x283C, 0x0); + channel.Write(ClassId.Threed, 0x2840, 0x0); + channel.Write(ClassId.Threed, 0x2844, 0x0); + channel.Write(ClassId.Threed, 0x2848, 0x0); + channel.Write(ClassId.Threed, 0x284C, 0x0); + channel.Write(ClassId.Threed, 0x2850, 0x0); + channel.Write(ClassId.Threed, 0x2854, 0x0); + channel.Write(ClassId.Threed, 0x2858, 0x0); + channel.Write(ClassId.Threed, 0x285C, 0x0); + channel.Write(ClassId.Threed, 0x2860, 0x0); + channel.Write(ClassId.Threed, 0x2864, 0x0); + channel.Write(ClassId.Threed, 0x2868, 0x0); + channel.Write(ClassId.Threed, 0x286C, 0x0); + channel.Write(ClassId.Threed, 0x2870, 0x0); + channel.Write(ClassId.Threed, 0x2874, 0x0); + channel.Write(ClassId.Threed, 0x2878, 0x0); + channel.Write(ClassId.Threed, 0x287C, 0x0); + channel.Write(ClassId.Threed, 0x2880, 0x0); + channel.Write(ClassId.Threed, 0x2884, 0x0); + channel.Write(ClassId.Threed, 0x2888, 0x0); + channel.Write(ClassId.Threed, 0x288C, 0x0); + channel.Write(ClassId.Threed, 0x2890, 0x0); + channel.Write(ClassId.Threed, 0x2894, 0x0); + channel.Write(ClassId.Threed, 0x2898, 0x0); + channel.Write(ClassId.Threed, 0x289C, 0x0); + channel.Write(ClassId.Threed, 0x28A0, 0x0); + channel.Write(ClassId.Threed, 0x28A4, 0x0); + channel.Write(ClassId.Threed, 0x28A8, 0x0); + channel.Write(ClassId.Threed, 0x28AC, 0x0); + channel.Write(ClassId.Threed, 0x28B0, 0x0); + channel.Write(ClassId.Threed, 0x28B4, 0x0); + channel.Write(ClassId.Threed, 0x28B8, 0x0); + channel.Write(ClassId.Threed, 0x28BC, 0x0); + channel.Write(ClassId.Threed, 0x28C0, 0x0); + channel.Write(ClassId.Threed, 0x28C4, 0x0); + channel.Write(ClassId.Threed, 0x28C8, 0x0); + channel.Write(ClassId.Threed, 0x28CC, 0x0); + channel.Write(ClassId.Threed, 0x28D0, 0x0); + channel.Write(ClassId.Threed, 0x28D4, 0x0); + channel.Write(ClassId.Threed, 0x28D8, 0x0); + channel.Write(ClassId.Threed, 0x28DC, 0x0); + channel.Write(ClassId.Threed, 0x28E0, 0x0); + channel.Write(ClassId.Threed, 0x28E4, 0x0); + channel.Write(ClassId.Threed, 0x28E8, 0x0); + channel.Write(ClassId.Threed, 0x28EC, 0x0); + channel.Write(ClassId.Threed, 0x28F0, 0x0); + channel.Write(ClassId.Threed, 0x28F4, 0x0); + channel.Write(ClassId.Threed, 0x28F8, 0x0); + channel.Write(ClassId.Threed, 0x28FC, 0x0); + channel.Write(ClassId.Threed, 0x2900, 0x0); + channel.Write(ClassId.Threed, 0x2904, 0x0); + channel.Write(ClassId.Threed, 0x2908, 0x0); + channel.Write(ClassId.Threed, 0x290C, 0x0); + channel.Write(ClassId.Threed, 0x2910, 0x0); + channel.Write(ClassId.Threed, 0x2914, 0x0); + channel.Write(ClassId.Threed, 0x2918, 0x0); + channel.Write(ClassId.Threed, 0x291C, 0x0); + channel.Write(ClassId.Threed, 0x2920, 0x0); + channel.Write(ClassId.Threed, 0x2924, 0x0); + channel.Write(ClassId.Threed, 0x2928, 0x0); + channel.Write(ClassId.Threed, 0x292C, 0x0); + channel.Write(ClassId.Threed, 0x2930, 0x0); + channel.Write(ClassId.Threed, 0x2934, 0x0); + channel.Write(ClassId.Threed, 0x2938, 0x0); + channel.Write(ClassId.Threed, 0x293C, 0x0); + channel.Write(ClassId.Threed, 0x2940, 0x0); + channel.Write(ClassId.Threed, 0x2944, 0x0); + channel.Write(ClassId.Threed, 0x2948, 0x0); + channel.Write(ClassId.Threed, 0x294C, 0x0); + channel.Write(ClassId.Threed, 0x2950, 0x0); + channel.Write(ClassId.Threed, 0x2954, 0x0); + channel.Write(ClassId.Threed, 0x2958, 0x0); + channel.Write(ClassId.Threed, 0x295C, 0x0); + channel.Write(ClassId.Threed, 0x2960, 0x0); + channel.Write(ClassId.Threed, 0x2964, 0x0); + channel.Write(ClassId.Threed, 0x2968, 0x0); + channel.Write(ClassId.Threed, 0x296C, 0x0); + channel.Write(ClassId.Threed, 0x2970, 0x0); + channel.Write(ClassId.Threed, 0x2974, 0x0); + channel.Write(ClassId.Threed, 0x2978, 0x0); + channel.Write(ClassId.Threed, 0x297C, 0x0); + channel.Write(ClassId.Threed, 0x2980, 0x0); + channel.Write(ClassId.Threed, 0x2984, 0x0); + channel.Write(ClassId.Threed, 0x2988, 0x0); + channel.Write(ClassId.Threed, 0x298C, 0x0); + channel.Write(ClassId.Threed, 0x2990, 0x0); + channel.Write(ClassId.Threed, 0x2994, 0x0); + channel.Write(ClassId.Threed, 0x2998, 0x0); + channel.Write(ClassId.Threed, 0x299C, 0x0); + channel.Write(ClassId.Threed, 0x29A0, 0x0); + channel.Write(ClassId.Threed, 0x29A4, 0x0); + channel.Write(ClassId.Threed, 0x29A8, 0x0); + channel.Write(ClassId.Threed, 0x29AC, 0x0); + channel.Write(ClassId.Threed, 0x29B0, 0x0); + channel.Write(ClassId.Threed, 0x29B4, 0x0); + channel.Write(ClassId.Threed, 0x29B8, 0x0); + channel.Write(ClassId.Threed, 0x29BC, 0x0); + channel.Write(ClassId.Threed, 0x29C0, 0x0); + channel.Write(ClassId.Threed, 0x29C4, 0x0); + channel.Write(ClassId.Threed, 0x29C8, 0x0); + channel.Write(ClassId.Threed, 0x29CC, 0x0); + channel.Write(ClassId.Threed, 0x29D0, 0x0); + channel.Write(ClassId.Threed, 0x29D4, 0x0); + channel.Write(ClassId.Threed, 0x29D8, 0x0); + channel.Write(ClassId.Threed, 0x29DC, 0x0); + channel.Write(ClassId.Threed, 0x29E0, 0x0); + channel.Write(ClassId.Threed, 0x29E4, 0x0); + channel.Write(ClassId.Threed, 0x29E8, 0x0); + channel.Write(ClassId.Threed, 0x29EC, 0x0); + channel.Write(ClassId.Threed, 0x29F0, 0x0); + channel.Write(ClassId.Threed, 0x29F4, 0x0); + channel.Write(ClassId.Threed, 0x29F8, 0x0); + channel.Write(ClassId.Threed, 0x29FC, 0x0); + channel.Write(ClassId.Threed, 0xA00, 0x0); + channel.Write(ClassId.Threed, 0xA20, 0x0); + channel.Write(ClassId.Threed, 0xA40, 0x0); + channel.Write(ClassId.Threed, 0xA60, 0x0); + channel.Write(ClassId.Threed, 0xA80, 0x0); + channel.Write(ClassId.Threed, 0xAA0, 0x0); + channel.Write(ClassId.Threed, 0xAC0, 0x0); + channel.Write(ClassId.Threed, 0xAE0, 0x0); + channel.Write(ClassId.Threed, 0xB00, 0x0); + channel.Write(ClassId.Threed, 0xB20, 0x0); + channel.Write(ClassId.Threed, 0xB40, 0x0); + channel.Write(ClassId.Threed, 0xB60, 0x0); + channel.Write(ClassId.Threed, 0xB80, 0x0); + channel.Write(ClassId.Threed, 0xBA0, 0x0); + channel.Write(ClassId.Threed, 0xBC0, 0x0); + channel.Write(ClassId.Threed, 0xBE0, 0x0); + channel.Write(ClassId.Threed, 0xA04, 0x0); + channel.Write(ClassId.Threed, 0xA24, 0x0); + channel.Write(ClassId.Threed, 0xA44, 0x0); + channel.Write(ClassId.Threed, 0xA64, 0x0); + channel.Write(ClassId.Threed, 0xA84, 0x0); + channel.Write(ClassId.Threed, 0xAA4, 0x0); + channel.Write(ClassId.Threed, 0xAC4, 0x0); + channel.Write(ClassId.Threed, 0xAE4, 0x0); + channel.Write(ClassId.Threed, 0xB04, 0x0); + channel.Write(ClassId.Threed, 0xB24, 0x0); + channel.Write(ClassId.Threed, 0xB44, 0x0); + channel.Write(ClassId.Threed, 0xB64, 0x0); + channel.Write(ClassId.Threed, 0xB84, 0x0); + channel.Write(ClassId.Threed, 0xBA4, 0x0); + channel.Write(ClassId.Threed, 0xBC4, 0x0); + channel.Write(ClassId.Threed, 0xBE4, 0x0); + channel.Write(ClassId.Threed, 0xA08, 0x0); + channel.Write(ClassId.Threed, 0xA28, 0x0); + channel.Write(ClassId.Threed, 0xA48, 0x0); + channel.Write(ClassId.Threed, 0xA68, 0x0); + channel.Write(ClassId.Threed, 0xA88, 0x0); + channel.Write(ClassId.Threed, 0xAA8, 0x0); + channel.Write(ClassId.Threed, 0xAC8, 0x0); + channel.Write(ClassId.Threed, 0xAE8, 0x0); + channel.Write(ClassId.Threed, 0xB08, 0x0); + channel.Write(ClassId.Threed, 0xB28, 0x0); + channel.Write(ClassId.Threed, 0xB48, 0x0); + channel.Write(ClassId.Threed, 0xB68, 0x0); + channel.Write(ClassId.Threed, 0xB88, 0x0); + channel.Write(ClassId.Threed, 0xBA8, 0x0); + channel.Write(ClassId.Threed, 0xBC8, 0x0); + channel.Write(ClassId.Threed, 0xBE8, 0x0); + channel.Write(ClassId.Threed, 0xA0C, 0x0); + channel.Write(ClassId.Threed, 0xA2C, 0x0); + channel.Write(ClassId.Threed, 0xA4C, 0x0); + channel.Write(ClassId.Threed, 0xA6C, 0x0); + channel.Write(ClassId.Threed, 0xA8C, 0x0); + channel.Write(ClassId.Threed, 0xAAC, 0x0); + channel.Write(ClassId.Threed, 0xACC, 0x0); + channel.Write(ClassId.Threed, 0xAEC, 0x0); + channel.Write(ClassId.Threed, 0xB0C, 0x0); + channel.Write(ClassId.Threed, 0xB2C, 0x0); + channel.Write(ClassId.Threed, 0xB4C, 0x0); + channel.Write(ClassId.Threed, 0xB6C, 0x0); + channel.Write(ClassId.Threed, 0xB8C, 0x0); + channel.Write(ClassId.Threed, 0xBAC, 0x0); + channel.Write(ClassId.Threed, 0xBCC, 0x0); + channel.Write(ClassId.Threed, 0xBEC, 0x0); + channel.Write(ClassId.Threed, 0xA10, 0x0); + channel.Write(ClassId.Threed, 0xA30, 0x0); + channel.Write(ClassId.Threed, 0xA50, 0x0); + channel.Write(ClassId.Threed, 0xA70, 0x0); + channel.Write(ClassId.Threed, 0xA90, 0x0); + channel.Write(ClassId.Threed, 0xAB0, 0x0); + channel.Write(ClassId.Threed, 0xAD0, 0x0); + channel.Write(ClassId.Threed, 0xAF0, 0x0); + channel.Write(ClassId.Threed, 0xB10, 0x0); + channel.Write(ClassId.Threed, 0xB30, 0x0); + channel.Write(ClassId.Threed, 0xB50, 0x0); + channel.Write(ClassId.Threed, 0xB70, 0x0); + channel.Write(ClassId.Threed, 0xB90, 0x0); + channel.Write(ClassId.Threed, 0xBB0, 0x0); + channel.Write(ClassId.Threed, 0xBD0, 0x0); + channel.Write(ClassId.Threed, 0xBF0, 0x0); + channel.Write(ClassId.Threed, 0xA14, 0x0); + channel.Write(ClassId.Threed, 0xA34, 0x0); + channel.Write(ClassId.Threed, 0xA54, 0x0); + channel.Write(ClassId.Threed, 0xA74, 0x0); + channel.Write(ClassId.Threed, 0xA94, 0x0); + channel.Write(ClassId.Threed, 0xAB4, 0x0); + channel.Write(ClassId.Threed, 0xAD4, 0x0); + channel.Write(ClassId.Threed, 0xAF4, 0x0); + channel.Write(ClassId.Threed, 0xB14, 0x0); + channel.Write(ClassId.Threed, 0xB34, 0x0); + channel.Write(ClassId.Threed, 0xB54, 0x0); + channel.Write(ClassId.Threed, 0xB74, 0x0); + channel.Write(ClassId.Threed, 0xB94, 0x0); + channel.Write(ClassId.Threed, 0xBB4, 0x0); + channel.Write(ClassId.Threed, 0xBD4, 0x0); + channel.Write(ClassId.Threed, 0xBF4, 0x0); + channel.Write(ClassId.Threed, 0xA18, 0x6420); + channel.Write(ClassId.Threed, 0xA38, 0x6420); + channel.Write(ClassId.Threed, 0xA58, 0x6420); + channel.Write(ClassId.Threed, 0xA78, 0x6420); + channel.Write(ClassId.Threed, 0xA98, 0x6420); + channel.Write(ClassId.Threed, 0xAB8, 0x6420); + channel.Write(ClassId.Threed, 0xAD8, 0x6420); + channel.Write(ClassId.Threed, 0xAF8, 0x6420); + channel.Write(ClassId.Threed, 0xB18, 0x6420); + channel.Write(ClassId.Threed, 0xB38, 0x6420); + channel.Write(ClassId.Threed, 0xB58, 0x6420); + channel.Write(ClassId.Threed, 0xB78, 0x6420); + channel.Write(ClassId.Threed, 0xB98, 0x6420); + channel.Write(ClassId.Threed, 0xBB8, 0x6420); + channel.Write(ClassId.Threed, 0xBD8, 0x6420); + channel.Write(ClassId.Threed, 0xBF8, 0x6420); + channel.Write(ClassId.Threed, 0xA1C, 0x0); + channel.Write(ClassId.Threed, 0xA3C, 0x0); + channel.Write(ClassId.Threed, 0xA5C, 0x0); + channel.Write(ClassId.Threed, 0xA7C, 0x0); + channel.Write(ClassId.Threed, 0xA9C, 0x0); + channel.Write(ClassId.Threed, 0xABC, 0x0); + channel.Write(ClassId.Threed, 0xADC, 0x0); + channel.Write(ClassId.Threed, 0xAFC, 0x0); + channel.Write(ClassId.Threed, 0xB1C, 0x0); + channel.Write(ClassId.Threed, 0xB3C, 0x0); + channel.Write(ClassId.Threed, 0xB5C, 0x0); + channel.Write(ClassId.Threed, 0xB7C, 0x0); + channel.Write(ClassId.Threed, 0xB9C, 0x0); + channel.Write(ClassId.Threed, 0xBBC, 0x0); + channel.Write(ClassId.Threed, 0xBDC, 0x0); + channel.Write(ClassId.Threed, 0xBFC, 0x0); + channel.Write(ClassId.Threed, 0xC00, 0x0); + channel.Write(ClassId.Threed, 0xC10, 0x0); + channel.Write(ClassId.Threed, 0xC20, 0x0); + channel.Write(ClassId.Threed, 0xC30, 0x0); + channel.Write(ClassId.Threed, 0xC40, 0x0); + channel.Write(ClassId.Threed, 0xC50, 0x0); + channel.Write(ClassId.Threed, 0xC60, 0x0); + channel.Write(ClassId.Threed, 0xC70, 0x0); + channel.Write(ClassId.Threed, 0xC80, 0x0); + channel.Write(ClassId.Threed, 0xC90, 0x0); + channel.Write(ClassId.Threed, 0xCA0, 0x0); + channel.Write(ClassId.Threed, 0xCB0, 0x0); + channel.Write(ClassId.Threed, 0xCC0, 0x0); + channel.Write(ClassId.Threed, 0xCD0, 0x0); + channel.Write(ClassId.Threed, 0xCE0, 0x0); + channel.Write(ClassId.Threed, 0xCF0, 0x0); + channel.Write(ClassId.Threed, 0xC04, 0x0); + channel.Write(ClassId.Threed, 0xC14, 0x0); + channel.Write(ClassId.Threed, 0xC24, 0x0); + channel.Write(ClassId.Threed, 0xC34, 0x0); + channel.Write(ClassId.Threed, 0xC44, 0x0); + channel.Write(ClassId.Threed, 0xC54, 0x0); + channel.Write(ClassId.Threed, 0xC64, 0x0); + channel.Write(ClassId.Threed, 0xC74, 0x0); + channel.Write(ClassId.Threed, 0xC84, 0x0); + channel.Write(ClassId.Threed, 0xC94, 0x0); + channel.Write(ClassId.Threed, 0xCA4, 0x0); + channel.Write(ClassId.Threed, 0xCB4, 0x0); + channel.Write(ClassId.Threed, 0xCC4, 0x0); + channel.Write(ClassId.Threed, 0xCD4, 0x0); + channel.Write(ClassId.Threed, 0xCE4, 0x0); + channel.Write(ClassId.Threed, 0xCF4, 0x0); + channel.Write(ClassId.Threed, 0xC08, 0x0); + channel.Write(ClassId.Threed, 0xC18, 0x0); + channel.Write(ClassId.Threed, 0xC28, 0x0); + channel.Write(ClassId.Threed, 0xC38, 0x0); + channel.Write(ClassId.Threed, 0xC48, 0x0); + channel.Write(ClassId.Threed, 0xC58, 0x0); + channel.Write(ClassId.Threed, 0xC68, 0x0); + channel.Write(ClassId.Threed, 0xC78, 0x0); + channel.Write(ClassId.Threed, 0xC88, 0x0); + channel.Write(ClassId.Threed, 0xC98, 0x0); + channel.Write(ClassId.Threed, 0xCA8, 0x0); + channel.Write(ClassId.Threed, 0xCB8, 0x0); + channel.Write(ClassId.Threed, 0xCC8, 0x0); + channel.Write(ClassId.Threed, 0xCD8, 0x0); + channel.Write(ClassId.Threed, 0xCE8, 0x0); + channel.Write(ClassId.Threed, 0xCF8, 0x0); + channel.Write(ClassId.Threed, 0xC0C, 0x3F800000); + channel.Write(ClassId.Threed, 0xC1C, 0x3F800000); + channel.Write(ClassId.Threed, 0xC2C, 0x3F800000); + channel.Write(ClassId.Threed, 0xC3C, 0x3F800000); + channel.Write(ClassId.Threed, 0xC4C, 0x3F800000); + channel.Write(ClassId.Threed, 0xC5C, 0x3F800000); + channel.Write(ClassId.Threed, 0xC6C, 0x3F800000); + channel.Write(ClassId.Threed, 0xC7C, 0x3F800000); + channel.Write(ClassId.Threed, 0xC8C, 0x3F800000); + channel.Write(ClassId.Threed, 0xC9C, 0x3F800000); + channel.Write(ClassId.Threed, 0xCAC, 0x3F800000); + channel.Write(ClassId.Threed, 0xCBC, 0x3F800000); + channel.Write(ClassId.Threed, 0xCCC, 0x3F800000); + channel.Write(ClassId.Threed, 0xCDC, 0x3F800000); + channel.Write(ClassId.Threed, 0xCEC, 0x3F800000); + channel.Write(ClassId.Threed, 0xCFC, 0x3F800000); + channel.Write(ClassId.Threed, 0xD00, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD08, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD10, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD18, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD20, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD28, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD30, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD38, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD04, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD0C, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD14, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD1C, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD24, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD2C, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD34, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD3C, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE00, 0x0); + channel.Write(ClassId.Threed, 0xE10, 0x0); + channel.Write(ClassId.Threed, 0xE20, 0x0); + channel.Write(ClassId.Threed, 0xE30, 0x0); + channel.Write(ClassId.Threed, 0xE40, 0x0); + channel.Write(ClassId.Threed, 0xE50, 0x0); + channel.Write(ClassId.Threed, 0xE60, 0x0); + channel.Write(ClassId.Threed, 0xE70, 0x0); + channel.Write(ClassId.Threed, 0xE80, 0x0); + channel.Write(ClassId.Threed, 0xE90, 0x0); + channel.Write(ClassId.Threed, 0xEA0, 0x0); + channel.Write(ClassId.Threed, 0xEB0, 0x0); + channel.Write(ClassId.Threed, 0xEC0, 0x0); + channel.Write(ClassId.Threed, 0xED0, 0x0); + channel.Write(ClassId.Threed, 0xEE0, 0x0); + channel.Write(ClassId.Threed, 0xEF0, 0x0); + channel.Write(ClassId.Threed, 0xE04, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE14, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE24, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE34, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE44, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE54, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE64, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE74, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE84, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE94, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xEA4, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xEB4, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xEC4, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xED4, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xEE4, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xEF4, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE08, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE18, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE28, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE38, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE48, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE58, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE68, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE78, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE88, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE98, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xEA8, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xEB8, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xEC8, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xED8, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xEE8, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xEF8, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD40, 0x0); + channel.Write(ClassId.Threed, 0xD48, 0x0); + channel.Write(ClassId.Threed, 0xD50, 0x0); + channel.Write(ClassId.Threed, 0xD58, 0x0); + channel.Write(ClassId.Threed, 0xD44, 0x0); + channel.Write(ClassId.Threed, 0xD4C, 0x0); + channel.Write(ClassId.Threed, 0xD54, 0x0); + channel.Write(ClassId.Threed, 0xD5C, 0x0); + channel.Write(ClassId.Threed, 0x1E00, 0x1); + channel.Write(ClassId.Threed, 0x1E20, 0x1); + channel.Write(ClassId.Threed, 0x1E40, 0x1); + channel.Write(ClassId.Threed, 0x1E60, 0x1); + channel.Write(ClassId.Threed, 0x1E80, 0x1); + channel.Write(ClassId.Threed, 0x1EA0, 0x1); + channel.Write(ClassId.Threed, 0x1EC0, 0x1); + channel.Write(ClassId.Threed, 0x1EE0, 0x1); + channel.Write(ClassId.Threed, 0x1E04, 0x1); + channel.Write(ClassId.Threed, 0x1E24, 0x1); + channel.Write(ClassId.Threed, 0x1E44, 0x1); + channel.Write(ClassId.Threed, 0x1E64, 0x1); + channel.Write(ClassId.Threed, 0x1E84, 0x1); + channel.Write(ClassId.Threed, 0x1EA4, 0x1); + channel.Write(ClassId.Threed, 0x1EC4, 0x1); + channel.Write(ClassId.Threed, 0x1EE4, 0x1); + channel.Write(ClassId.Threed, 0x1E08, 0x2); + channel.Write(ClassId.Threed, 0x1E28, 0x2); + channel.Write(ClassId.Threed, 0x1E48, 0x2); + channel.Write(ClassId.Threed, 0x1E68, 0x2); + channel.Write(ClassId.Threed, 0x1E88, 0x2); + channel.Write(ClassId.Threed, 0x1EA8, 0x2); + channel.Write(ClassId.Threed, 0x1EC8, 0x2); + channel.Write(ClassId.Threed, 0x1EE8, 0x2); + channel.Write(ClassId.Threed, 0x1E0C, 0x1); + channel.Write(ClassId.Threed, 0x1E2C, 0x1); + channel.Write(ClassId.Threed, 0x1E4C, 0x1); + channel.Write(ClassId.Threed, 0x1E6C, 0x1); + channel.Write(ClassId.Threed, 0x1E8C, 0x1); + channel.Write(ClassId.Threed, 0x1EAC, 0x1); + channel.Write(ClassId.Threed, 0x1ECC, 0x1); + channel.Write(ClassId.Threed, 0x1EEC, 0x1); + channel.Write(ClassId.Threed, 0x1E10, 0x1); + channel.Write(ClassId.Threed, 0x1E30, 0x1); + channel.Write(ClassId.Threed, 0x1E50, 0x1); + channel.Write(ClassId.Threed, 0x1E70, 0x1); + channel.Write(ClassId.Threed, 0x1E90, 0x1); + channel.Write(ClassId.Threed, 0x1EB0, 0x1); + channel.Write(ClassId.Threed, 0x1ED0, 0x1); + channel.Write(ClassId.Threed, 0x1EF0, 0x1); + channel.Write(ClassId.Threed, 0x1E14, 0x2); + channel.Write(ClassId.Threed, 0x1E34, 0x2); + channel.Write(ClassId.Threed, 0x1E54, 0x2); + channel.Write(ClassId.Threed, 0x1E74, 0x2); + channel.Write(ClassId.Threed, 0x1E94, 0x2); + channel.Write(ClassId.Threed, 0x1EB4, 0x2); + channel.Write(ClassId.Threed, 0x1ED4, 0x2); + channel.Write(ClassId.Threed, 0x1EF4, 0x2); + channel.Write(ClassId.Threed, 0x1E18, 0x1); + channel.Write(ClassId.Threed, 0x1E38, 0x1); + channel.Write(ClassId.Threed, 0x1E58, 0x1); + channel.Write(ClassId.Threed, 0x1E78, 0x1); + channel.Write(ClassId.Threed, 0x1E98, 0x1); + channel.Write(ClassId.Threed, 0x1EB8, 0x1); + channel.Write(ClassId.Threed, 0x1ED8, 0x1); + channel.Write(ClassId.Threed, 0x1EF8, 0x1); + channel.Write(ClassId.Threed, 0x1480, 0x0); + channel.Write(ClassId.Threed, 0x1490, 0x0); + channel.Write(ClassId.Threed, 0x14A0, 0x0); + channel.Write(ClassId.Threed, 0x14B0, 0x0); + channel.Write(ClassId.Threed, 0x14C0, 0x0); + channel.Write(ClassId.Threed, 0x14D0, 0x0); + channel.Write(ClassId.Threed, 0x14E0, 0x0); + channel.Write(ClassId.Threed, 0x14F0, 0x0); + channel.Write(ClassId.Threed, 0x1484, 0x0); + channel.Write(ClassId.Threed, 0x1494, 0x0); + channel.Write(ClassId.Threed, 0x14A4, 0x0); + channel.Write(ClassId.Threed, 0x14B4, 0x0); + channel.Write(ClassId.Threed, 0x14C4, 0x0); + channel.Write(ClassId.Threed, 0x14D4, 0x0); + channel.Write(ClassId.Threed, 0x14E4, 0x0); + channel.Write(ClassId.Threed, 0x14F4, 0x0); + channel.Write(ClassId.Threed, 0x1488, 0x0); + channel.Write(ClassId.Threed, 0x1498, 0x0); + channel.Write(ClassId.Threed, 0x14A8, 0x0); + channel.Write(ClassId.Threed, 0x14B8, 0x0); + channel.Write(ClassId.Threed, 0x14C8, 0x0); + channel.Write(ClassId.Threed, 0x14D8, 0x0); + channel.Write(ClassId.Threed, 0x14E8, 0x0); + channel.Write(ClassId.Threed, 0x14F8, 0x0); + channel.Write(ClassId.Threed, 0x3400, 0x0); + channel.Write(ClassId.Threed, 0x3404, 0x0); + channel.Write(ClassId.Threed, 0x3408, 0x0); + channel.Write(ClassId.Threed, 0x340C, 0x0); + channel.Write(ClassId.Threed, 0x3410, 0x0); + channel.Write(ClassId.Threed, 0x3414, 0x0); + channel.Write(ClassId.Threed, 0x3418, 0x0); + channel.Write(ClassId.Threed, 0x341C, 0x0); + channel.Write(ClassId.Threed, 0x3420, 0x0); + channel.Write(ClassId.Threed, 0x3424, 0x0); + channel.Write(ClassId.Threed, 0x3428, 0x0); + channel.Write(ClassId.Threed, 0x342C, 0x0); + channel.Write(ClassId.Threed, 0x3430, 0x0); + channel.Write(ClassId.Threed, 0x3434, 0x0); + channel.Write(ClassId.Threed, 0x3438, 0x0); + channel.Write(ClassId.Threed, 0x343C, 0x0); + channel.Write(ClassId.Threed, 0x3440, 0x0); + channel.Write(ClassId.Threed, 0x3444, 0x0); + channel.Write(ClassId.Threed, 0x3448, 0x0); + channel.Write(ClassId.Threed, 0x344C, 0x0); + channel.Write(ClassId.Threed, 0x3450, 0x0); + channel.Write(ClassId.Threed, 0x3454, 0x0); + channel.Write(ClassId.Threed, 0x3458, 0x0); + channel.Write(ClassId.Threed, 0x345C, 0x0); + channel.Write(ClassId.Threed, 0x3460, 0x0); + channel.Write(ClassId.Threed, 0x3464, 0x0); + channel.Write(ClassId.Threed, 0x3468, 0x0); + channel.Write(ClassId.Threed, 0x346C, 0x0); + channel.Write(ClassId.Threed, 0x3470, 0x0); + channel.Write(ClassId.Threed, 0x3474, 0x0); + channel.Write(ClassId.Threed, 0x3478, 0x0); + channel.Write(ClassId.Threed, 0x347C, 0x0); + channel.Write(ClassId.Threed, 0x3480, 0x0); + channel.Write(ClassId.Threed, 0x3484, 0x0); + channel.Write(ClassId.Threed, 0x3488, 0x0); + channel.Write(ClassId.Threed, 0x348C, 0x0); + channel.Write(ClassId.Threed, 0x3490, 0x0); + channel.Write(ClassId.Threed, 0x3494, 0x0); + channel.Write(ClassId.Threed, 0x3498, 0x0); + channel.Write(ClassId.Threed, 0x349C, 0x0); + channel.Write(ClassId.Threed, 0x34A0, 0x0); + channel.Write(ClassId.Threed, 0x34A4, 0x0); + channel.Write(ClassId.Threed, 0x34A8, 0x0); + channel.Write(ClassId.Threed, 0x34AC, 0x0); + channel.Write(ClassId.Threed, 0x34B0, 0x0); + channel.Write(ClassId.Threed, 0x34B4, 0x0); + channel.Write(ClassId.Threed, 0x34B8, 0x0); + channel.Write(ClassId.Threed, 0x34BC, 0x0); + channel.Write(ClassId.Threed, 0x34C0, 0x0); + channel.Write(ClassId.Threed, 0x34C4, 0x0); + channel.Write(ClassId.Threed, 0x34C8, 0x0); + channel.Write(ClassId.Threed, 0x34CC, 0x0); + channel.Write(ClassId.Threed, 0x34D0, 0x0); + channel.Write(ClassId.Threed, 0x34D4, 0x0); + channel.Write(ClassId.Threed, 0x34D8, 0x0); + channel.Write(ClassId.Threed, 0x34DC, 0x0); + channel.Write(ClassId.Threed, 0x34E0, 0x0); + channel.Write(ClassId.Threed, 0x34E4, 0x0); + channel.Write(ClassId.Threed, 0x34E8, 0x0); + channel.Write(ClassId.Threed, 0x34EC, 0x0); + channel.Write(ClassId.Threed, 0x34F0, 0x0); + channel.Write(ClassId.Threed, 0x34F4, 0x0); + channel.Write(ClassId.Threed, 0x34F8, 0x0); + channel.Write(ClassId.Threed, 0x34FC, 0x0); + channel.Write(ClassId.Threed, 0x3500, 0x0); + channel.Write(ClassId.Threed, 0x3504, 0x0); + channel.Write(ClassId.Threed, 0x3508, 0x0); + channel.Write(ClassId.Threed, 0x350C, 0x0); + channel.Write(ClassId.Threed, 0x3510, 0x0); + channel.Write(ClassId.Threed, 0x3514, 0x0); + channel.Write(ClassId.Threed, 0x3518, 0x0); + channel.Write(ClassId.Threed, 0x351C, 0x0); + channel.Write(ClassId.Threed, 0x3520, 0x0); + channel.Write(ClassId.Threed, 0x3524, 0x0); + channel.Write(ClassId.Threed, 0x3528, 0x0); + channel.Write(ClassId.Threed, 0x352C, 0x0); + channel.Write(ClassId.Threed, 0x3530, 0x0); + channel.Write(ClassId.Threed, 0x3534, 0x0); + channel.Write(ClassId.Threed, 0x3538, 0x0); + channel.Write(ClassId.Threed, 0x353C, 0x0); + channel.Write(ClassId.Threed, 0x3540, 0x0); + channel.Write(ClassId.Threed, 0x3544, 0x0); + channel.Write(ClassId.Threed, 0x3548, 0x0); + channel.Write(ClassId.Threed, 0x354C, 0x0); + channel.Write(ClassId.Threed, 0x3550, 0x0); + channel.Write(ClassId.Threed, 0x3554, 0x0); + channel.Write(ClassId.Threed, 0x3558, 0x0); + channel.Write(ClassId.Threed, 0x355C, 0x0); + channel.Write(ClassId.Threed, 0x3560, 0x0); + channel.Write(ClassId.Threed, 0x3564, 0x0); + channel.Write(ClassId.Threed, 0x3568, 0x0); + channel.Write(ClassId.Threed, 0x356C, 0x0); + channel.Write(ClassId.Threed, 0x3570, 0x0); + channel.Write(ClassId.Threed, 0x3574, 0x0); + channel.Write(ClassId.Threed, 0x3578, 0x0); + channel.Write(ClassId.Threed, 0x357C, 0x0); + channel.Write(ClassId.Threed, 0x3580, 0x0); + channel.Write(ClassId.Threed, 0x3584, 0x0); + channel.Write(ClassId.Threed, 0x3588, 0x0); + channel.Write(ClassId.Threed, 0x358C, 0x0); + channel.Write(ClassId.Threed, 0x3590, 0x0); + channel.Write(ClassId.Threed, 0x3594, 0x0); + channel.Write(ClassId.Threed, 0x3598, 0x0); + channel.Write(ClassId.Threed, 0x359C, 0x0); + channel.Write(ClassId.Threed, 0x35A0, 0x0); + channel.Write(ClassId.Threed, 0x35A4, 0x0); + channel.Write(ClassId.Threed, 0x35A8, 0x0); + channel.Write(ClassId.Threed, 0x35AC, 0x0); + channel.Write(ClassId.Threed, 0x35B0, 0x0); + channel.Write(ClassId.Threed, 0x35B4, 0x0); + channel.Write(ClassId.Threed, 0x35B8, 0x0); + channel.Write(ClassId.Threed, 0x35BC, 0x0); + channel.Write(ClassId.Threed, 0x35C0, 0x0); + channel.Write(ClassId.Threed, 0x35C4, 0x0); + channel.Write(ClassId.Threed, 0x35C8, 0x0); + channel.Write(ClassId.Threed, 0x35CC, 0x0); + channel.Write(ClassId.Threed, 0x35D0, 0x0); + channel.Write(ClassId.Threed, 0x35D4, 0x0); + channel.Write(ClassId.Threed, 0x35D8, 0x0); + channel.Write(ClassId.Threed, 0x35DC, 0x0); + channel.Write(ClassId.Threed, 0x35E0, 0x0); + channel.Write(ClassId.Threed, 0x35E4, 0x0); + channel.Write(ClassId.Threed, 0x35E8, 0x0); + channel.Write(ClassId.Threed, 0x35EC, 0x0); + channel.Write(ClassId.Threed, 0x35F0, 0x0); + channel.Write(ClassId.Threed, 0x35F4, 0x0); + channel.Write(ClassId.Threed, 0x35F8, 0x0); + channel.Write(ClassId.Threed, 0x35FC, 0x0); + channel.Write(ClassId.Threed, 0x30C, 0x1); + channel.Write(ClassId.Threed, 0x1944, 0x0); + channel.Write(ClassId.Threed, 0x1514, 0x0); + channel.Write(ClassId.Threed, 0xD68, 0xFFFF); + channel.Write(ClassId.Threed, 0x121C, 0xFAC6881); + channel.Write(ClassId.Threed, 0xFAC, 0x1); + channel.Write(ClassId.Threed, 0x1538, 0x1); + channel.Write(ClassId.Threed, 0xFE0, 0x0); + channel.Write(ClassId.Threed, 0xFE4, 0x0); + channel.Write(ClassId.Threed, 0xFE8, 0x14); + channel.Write(ClassId.Threed, 0xFEC, 0x40); + channel.Write(ClassId.Threed, 0xFF0, 0x0); + channel.Write(ClassId.Threed, 0x179C, 0x0); + channel.Write(ClassId.Threed, 0x1228, 0x400); + channel.Write(ClassId.Threed, 0x122C, 0x300); + channel.Write(ClassId.Threed, 0x1230, 0x10001); + channel.Write(ClassId.Threed, 0x7F8, 0x0); + channel.Write(ClassId.Threed, 0x1208, 0x0); + channel.Write(ClassId.Threed, 0x15B4, 0x1); + channel.Write(ClassId.Threed, 0x15CC, 0x0); + channel.Write(ClassId.Threed, 0x1534, 0x0); + channel.Write(ClassId.Threed, 0x754, 0x1); + channel.Write(ClassId.Threed, 0xFB0, 0x0); + channel.Write(ClassId.Threed, 0x15D0, 0x0); + channel.Write(ClassId.Threed, 0x11E0, 0x88888888); + channel.Write(ClassId.Threed, 0x11E4, 0x88888888); + channel.Write(ClassId.Threed, 0x11E8, 0x88888888); + channel.Write(ClassId.Threed, 0x11EC, 0x88888888); + channel.Write(ClassId.Threed, 0x153C, 0x0); + channel.Write(ClassId.Threed, 0x16B4, 0x3); + channel.Write(ClassId.Threed, 0xFA4, 0x1); + channel.Write(ClassId.Threed, 0xFBC, 0xFFFF); + channel.Write(ClassId.Threed, 0xFC0, 0xFFFF); + channel.Write(ClassId.Threed, 0xFC4, 0xFFFF); + channel.Write(ClassId.Threed, 0xFC8, 0xFFFF); + channel.Write(ClassId.Threed, 0xFA8, 0xFFFF); + channel.Write(ClassId.Threed, 0xDF8, 0x0); + channel.Write(ClassId.Threed, 0xDFC, 0x0); + channel.Write(ClassId.Threed, 0x1948, 0x0); + channel.Write(ClassId.Threed, 0x1970, 0x1); + channel.Write(ClassId.Threed, 0x161C, 0x9F0); + channel.Write(ClassId.Threed, 0xDCC, 0x10); + channel.Write(ClassId.Threed, 0x15E4, 0x0); + channel.Write(ClassId.Threed, 0x1160, 0x25E00040); + channel.Write(ClassId.Threed, 0x1164, 0x25E00040); + channel.Write(ClassId.Threed, 0x1168, 0x25E00040); + channel.Write(ClassId.Threed, 0x116C, 0x25E00040); + channel.Write(ClassId.Threed, 0x1170, 0x25E00040); + channel.Write(ClassId.Threed, 0x1174, 0x25E00040); + channel.Write(ClassId.Threed, 0x1178, 0x25E00040); + channel.Write(ClassId.Threed, 0x117C, 0x25E00040); + channel.Write(ClassId.Threed, 0x1180, 0x25E00040); + channel.Write(ClassId.Threed, 0x1184, 0x25E00040); + channel.Write(ClassId.Threed, 0x1188, 0x25E00040); + channel.Write(ClassId.Threed, 0x118C, 0x25E00040); + channel.Write(ClassId.Threed, 0x1190, 0x25E00040); + channel.Write(ClassId.Threed, 0x1194, 0x25E00040); + channel.Write(ClassId.Threed, 0x1198, 0x25E00040); + channel.Write(ClassId.Threed, 0x119C, 0x25E00040); + channel.Write(ClassId.Threed, 0x11A0, 0x25E00040); + channel.Write(ClassId.Threed, 0x11A4, 0x25E00040); + channel.Write(ClassId.Threed, 0x11A8, 0x25E00040); + channel.Write(ClassId.Threed, 0x11AC, 0x25E00040); + channel.Write(ClassId.Threed, 0x11B0, 0x25E00040); + channel.Write(ClassId.Threed, 0x11B4, 0x25E00040); + channel.Write(ClassId.Threed, 0x11B8, 0x25E00040); + channel.Write(ClassId.Threed, 0x11BC, 0x25E00040); + channel.Write(ClassId.Threed, 0x11C0, 0x25E00040); + channel.Write(ClassId.Threed, 0x11C4, 0x25E00040); + channel.Write(ClassId.Threed, 0x11C8, 0x25E00040); + channel.Write(ClassId.Threed, 0x11CC, 0x25E00040); + channel.Write(ClassId.Threed, 0x11D0, 0x25E00040); + channel.Write(ClassId.Threed, 0x11D4, 0x25E00040); + channel.Write(ClassId.Threed, 0x11D8, 0x25E00040); + channel.Write(ClassId.Threed, 0x11DC, 0x25E00040); + channel.Write(ClassId.Threed, 0x1880, 0x0); + channel.Write(ClassId.Threed, 0x1884, 0x0); + channel.Write(ClassId.Threed, 0x1888, 0x0); + channel.Write(ClassId.Threed, 0x188C, 0x0); + channel.Write(ClassId.Threed, 0x1890, 0x0); + channel.Write(ClassId.Threed, 0x1894, 0x0); + channel.Write(ClassId.Threed, 0x1898, 0x0); + channel.Write(ClassId.Threed, 0x189C, 0x0); + channel.Write(ClassId.Threed, 0x18A0, 0x0); + channel.Write(ClassId.Threed, 0x18A4, 0x0); + channel.Write(ClassId.Threed, 0x18A8, 0x0); + channel.Write(ClassId.Threed, 0x18AC, 0x0); + channel.Write(ClassId.Threed, 0x18B0, 0x0); + channel.Write(ClassId.Threed, 0x18B4, 0x0); + channel.Write(ClassId.Threed, 0x18B8, 0x0); + channel.Write(ClassId.Threed, 0x18BC, 0x0); + channel.Write(ClassId.Threed, 0x18C0, 0x0); + channel.Write(ClassId.Threed, 0x18C4, 0x0); + channel.Write(ClassId.Threed, 0x18C8, 0x0); + channel.Write(ClassId.Threed, 0x18CC, 0x0); + channel.Write(ClassId.Threed, 0x18D0, 0x0); + channel.Write(ClassId.Threed, 0x18D4, 0x0); + channel.Write(ClassId.Threed, 0x18D8, 0x0); + channel.Write(ClassId.Threed, 0x18DC, 0x0); + channel.Write(ClassId.Threed, 0x18E0, 0x0); + channel.Write(ClassId.Threed, 0x18E4, 0x0); + channel.Write(ClassId.Threed, 0x18E8, 0x0); + channel.Write(ClassId.Threed, 0x18EC, 0x0); + channel.Write(ClassId.Threed, 0x18F0, 0x0); + channel.Write(ClassId.Threed, 0x18F4, 0x0); + channel.Write(ClassId.Threed, 0x18F8, 0x0); + channel.Write(ClassId.Threed, 0x18FC, 0x0); + channel.Write(ClassId.Threed, 0xF84, 0x0); + channel.Write(ClassId.Threed, 0xF88, 0x0); + channel.Write(ClassId.Threed, 0x17C8, 0x0); + channel.Write(ClassId.Threed, 0x17CC, 0x0); + channel.Write(ClassId.Threed, 0x17D0, 0xFF); + channel.Write(ClassId.Threed, 0x17D4, 0xFFFFFFFF); + channel.Write(ClassId.Threed, 0x17D8, 0x2); + channel.Write(ClassId.Threed, 0x17DC, 0x0); + channel.Write(ClassId.Threed, 0x15F4, 0x0); + channel.Write(ClassId.Threed, 0x15F8, 0x0); + channel.Write(ClassId.Threed, 0x1434, 0x0); + channel.Write(ClassId.Threed, 0x1438, 0x0); + channel.Write(ClassId.Threed, 0xD74, 0x0); + channel.Write(ClassId.Threed, 0x13A4, 0x0); + channel.Write(ClassId.Threed, 0x1318, 0x1); + channel.Write(ClassId.Threed, 0x1080, 0x0); + channel.Write(ClassId.Threed, 0x1084, 0x0); + channel.Write(ClassId.Threed, 0x1088, 0x1); + channel.Write(ClassId.Threed, 0x108C, 0x1); + channel.Write(ClassId.Threed, 0x1090, 0x0); + channel.Write(ClassId.Threed, 0x1094, 0x1); + channel.Write(ClassId.Threed, 0x1098, 0x0); + channel.Write(ClassId.Threed, 0x109C, 0x1); + channel.Write(ClassId.Threed, 0x10A0, 0x0); + channel.Write(ClassId.Threed, 0x10A4, 0x0); + channel.Write(ClassId.Threed, 0x1644, 0x0); + channel.Write(ClassId.Threed, 0x748, 0x0); + channel.Write(ClassId.Threed, 0xDE8, 0x0); + channel.Write(ClassId.Threed, 0x1648, 0x0); + channel.Write(ClassId.Threed, 0x12A4, 0x0); + channel.Write(ClassId.Threed, 0x1120, 0x0); + channel.Write(ClassId.Threed, 0x1124, 0x0); + channel.Write(ClassId.Threed, 0x1128, 0x0); + channel.Write(ClassId.Threed, 0x112C, 0x0); + channel.Write(ClassId.Threed, 0x1118, 0x0); + channel.Write(ClassId.Threed, 0x164C, 0x0); + channel.Write(ClassId.Threed, 0x1658, 0x0); + channel.Write(ClassId.Threed, 0x1910, 0x290); + channel.Write(ClassId.Threed, 0x1518, 0x0); + channel.Write(ClassId.Threed, 0x165C, 0x1); + channel.Write(ClassId.Threed, 0x1520, 0x0); + channel.Write(ClassId.Threed, 0x1604, 0x0); + channel.Write(ClassId.Threed, 0x1570, 0x0); + channel.Write(ClassId.Threed, 0x13B0, 0x3F800000); + channel.Write(ClassId.Threed, 0x13B4, 0x3F800000); + channel.Write(ClassId.Threed, 0x20C, 0x0); + channel.Write(ClassId.Threed, 0x1670, 0x30201000); + channel.Write(ClassId.Threed, 0x1674, 0x70605040); + channel.Write(ClassId.Threed, 0x1678, 0xB8A89888); + channel.Write(ClassId.Threed, 0x167C, 0xF8E8D8C8); + channel.Write(ClassId.Threed, 0x166C, 0x0); + channel.Write(ClassId.Threed, 0x1680, 0xFFFF00); + channel.Write(ClassId.Threed, 0x12D0, 0x3); + channel.Write(ClassId.Threed, 0x113C, 0x0); + channel.Write(ClassId.Threed, 0x12D4, 0x2); + channel.Write(ClassId.Threed, 0x1684, 0x0); + channel.Write(ClassId.Threed, 0x1688, 0x0); + channel.Write(ClassId.Threed, 0xDAC, 0x1B02); + channel.Write(ClassId.Threed, 0xDB0, 0x1B02); + channel.Write(ClassId.Threed, 0xDB4, 0x0); + channel.Write(ClassId.Threed, 0x168C, 0x0); + channel.Write(ClassId.Threed, 0x15BC, 0x0); + channel.Write(ClassId.Threed, 0x156C, 0x0); + channel.Write(ClassId.Threed, 0x187C, 0x0); + channel.Write(ClassId.Threed, 0x1110, 0x1); + channel.Write(ClassId.Threed, 0xDC0, 0x0); + channel.Write(ClassId.Threed, 0xDC4, 0x0); + channel.Write(ClassId.Threed, 0xDC8, 0x0); + channel.Write(ClassId.Threed, 0xF40, 0x0); + channel.Write(ClassId.Threed, 0xF44, 0x0); + channel.Write(ClassId.Threed, 0xF48, 0x0); + channel.Write(ClassId.Threed, 0xF4C, 0x0); + channel.Write(ClassId.Threed, 0xF50, 0x0); + channel.Write(ClassId.Threed, 0x1234, 0x0); + channel.Write(ClassId.Threed, 0x1690, 0x0); + channel.Write(ClassId.Threed, 0x790, 0x0); + channel.Write(ClassId.Threed, 0x794, 0x0); + channel.Write(ClassId.Threed, 0x798, 0x0); + channel.Write(ClassId.Threed, 0x79C, 0x0); + channel.Write(ClassId.Threed, 0x7A0, 0x0); + channel.Write(ClassId.Threed, 0x77C, 0x0); + channel.Write(ClassId.Threed, 0x1000, 0x10); + channel.Write(ClassId.Threed, 0x10FC, 0x0); + channel.Write(ClassId.Threed, 0x1290, 0x0); + channel.Write(ClassId.Threed, 0x218, 0x10); + channel.Write(ClassId.Threed, 0x12D8, 0x0); + channel.Write(ClassId.Threed, 0x12DC, 0x10); + channel.Write(ClassId.Threed, 0xD94, 0x1); + channel.Write(ClassId.Threed, 0x155C, 0x0); + channel.Write(ClassId.Threed, 0x1560, 0x0); + channel.Write(ClassId.Threed, 0x1564, 0xFFF); + channel.Write(ClassId.Threed, 0x1574, 0x0); + channel.Write(ClassId.Threed, 0x1578, 0x0); + channel.Write(ClassId.Threed, 0x157C, 0xFFFFF); + channel.Write(ClassId.Threed, 0x1354, 0x0); + channel.Write(ClassId.Threed, 0x1610, 0x12); + channel.Write(ClassId.Threed, 0x1608, 0x0); + channel.Write(ClassId.Threed, 0x160C, 0x0); + channel.Write(ClassId.Threed, 0x260C, 0x0); + channel.Write(ClassId.Threed, 0x7AC, 0x0); + channel.Write(ClassId.Threed, 0x162C, 0x3); + channel.Write(ClassId.Threed, 0x210, 0x0); + channel.Write(ClassId.Threed, 0x320, 0x0); + channel.Write(ClassId.Threed, 0x324, 0x3F800000); + channel.Write(ClassId.Threed, 0x328, 0x3F800000); + channel.Write(ClassId.Threed, 0x32C, 0x3F800000); + channel.Write(ClassId.Threed, 0x330, 0x3F800000); + channel.Write(ClassId.Threed, 0x334, 0x3F800000); + channel.Write(ClassId.Threed, 0x338, 0x3F800000); + channel.Write(ClassId.Threed, 0x750, 0x0); + channel.Write(ClassId.Threed, 0x760, 0x39291909); + channel.Write(ClassId.Threed, 0x764, 0x79695949); + channel.Write(ClassId.Threed, 0x768, 0xB9A99989); + channel.Write(ClassId.Threed, 0x76C, 0xF9E9D9C9); + channel.Write(ClassId.Threed, 0x770, 0x30201000); + channel.Write(ClassId.Threed, 0x774, 0x70605040); + channel.Write(ClassId.Threed, 0x778, 0x9080); + channel.Write(ClassId.Threed, 0x780, 0x39291909); + channel.Write(ClassId.Threed, 0x784, 0x79695949); + channel.Write(ClassId.Threed, 0x788, 0xB9A99989); + channel.Write(ClassId.Threed, 0x78C, 0xF9E9D9C9); + channel.Write(ClassId.Threed, 0x7D0, 0x30201000); + channel.Write(ClassId.Threed, 0x7D4, 0x70605040); + channel.Write(ClassId.Threed, 0x7D8, 0x9080); + channel.Write(ClassId.Threed, 0x1004, 0x0); + channel.Write(ClassId.Threed, 0x1240, 0x0); + channel.Write(ClassId.Threed, 0x1244, 0x0); + channel.Write(ClassId.Threed, 0x1248, 0x0); + channel.Write(ClassId.Threed, 0x124C, 0x0); + channel.Write(ClassId.Threed, 0x1250, 0x0); + channel.Write(ClassId.Threed, 0x1254, 0x0); + channel.Write(ClassId.Threed, 0x1258, 0x0); + channel.Write(ClassId.Threed, 0x125C, 0x0); + channel.Write(ClassId.Threed, 0x37C, 0x1); + channel.Write(ClassId.Threed, 0x740, 0x0); + channel.Write(ClassId.Threed, 0x1148, 0x0); + channel.Write(ClassId.Threed, 0xFB4, 0x0); + channel.Write(ClassId.Threed, 0xFB8, 0x2); + channel.Write(ClassId.Threed, 0x1130, 0x2); + channel.Write(ClassId.Threed, 0xFD4, 0x0); + channel.Write(ClassId.Threed, 0xFD8, 0x0); + channel.Write(ClassId.Threed, 0x1030, 0x20181008); + channel.Write(ClassId.Threed, 0x1034, 0x40383028); + channel.Write(ClassId.Threed, 0x1038, 0x60585048); + channel.Write(ClassId.Threed, 0x103C, 0x80787068); + channel.Write(ClassId.Threed, 0x744, 0x0); + channel.Write(ClassId.Threed, 0x2600, 0x0); + channel.Write(ClassId.Threed, 0x1918, 0x0); + channel.Write(ClassId.Threed, 0x191C, 0x900); + channel.Write(ClassId.Threed, 0x1920, 0x405); + channel.Write(ClassId.Threed, 0x1308, 0x1); + channel.Write(ClassId.Threed, 0x1924, 0x0); + channel.Write(ClassId.Threed, 0x13AC, 0x0); + channel.Write(ClassId.Threed, 0x192C, 0x1); + channel.Write(ClassId.Threed, 0x193C, 0x2C1C); + channel.Write(ClassId.Threed, 0xD7C, 0x0); + channel.Write(ClassId.Threed, 0xF8C, 0x0); + channel.Write(ClassId.Threed, 0x2C0, 0x1); + channel.Write(ClassId.Threed, 0x1510, 0x0); + channel.Write(ClassId.Threed, 0x1940, 0x0); + channel.Write(ClassId.Threed, 0xFF4, 0x0); + channel.Write(ClassId.Threed, 0xFF8, 0x0); + channel.Write(ClassId.Threed, 0x194C, 0x0); + channel.Write(ClassId.Threed, 0x1950, 0x0); + channel.Write(ClassId.Threed, 0x1968, 0x0); + channel.Write(ClassId.Threed, 0x1590, 0x3F); + channel.Write(ClassId.Threed, 0x7E8, 0x0); + channel.Write(ClassId.Threed, 0x7EC, 0x0); + channel.Write(ClassId.Threed, 0x7F0, 0x0); + channel.Write(ClassId.Threed, 0x7F4, 0x0); + channel.Write(ClassId.Threed, 0x196C, 0x11); + channel.Write(ClassId.Threed, 0x2E4, 0xB001); + channel.Write(ClassId.Threed, 0x36C, 0x0); + channel.Write(ClassId.Threed, 0x370, 0x0); + channel.Write(ClassId.Threed, 0x197C, 0x0); + channel.Write(ClassId.Threed, 0xFCC, 0x0); + channel.Write(ClassId.Threed, 0xFD0, 0x0); + channel.Write(ClassId.Threed, 0x2D8, 0x40); + channel.Write(ClassId.Threed, 0x1980, 0x80); + channel.Write(ClassId.Threed, 0x1504, 0x80); + channel.Write(ClassId.Threed, 0x1984, 0x0); + channel.Write(ClassId.Threed, 0xF60, 0x0); + channel.Write(ClassId.Threed, 0xF64, 0x400040); + channel.Write(ClassId.Threed, 0xF68, 0x2212); + channel.Write(ClassId.Threed, 0xF6C, 0x8080203); + channel.Write(ClassId.Threed, 0x1108, 0x8); + channel.Write(ClassId.Threed, 0xF70, 0x80001); + channel.Write(ClassId.Threed, 0xFFC, 0x0); + channel.Write(ClassId.Threed, 0x1134, 0x0); + channel.Write(ClassId.Threed, 0xF1C, 0x0); + channel.Write(ClassId.Threed, 0x11F8, 0x0); + channel.Write(ClassId.Threed, 0x1138, 0x1); + channel.Write(ClassId.Threed, 0x300, 0x1); + channel.Write(ClassId.Threed, 0x13A8, 0x0); + channel.Write(ClassId.Threed, 0x1224, 0x0); + channel.Write(ClassId.Threed, 0x12EC, 0x0); + channel.Write(ClassId.Threed, 0x1310, 0x0); + channel.Write(ClassId.Threed, 0x1314, 0x1); + channel.Write(ClassId.Threed, 0x1380, 0x0); + channel.Write(ClassId.Threed, 0x1384, 0x1); + channel.Write(ClassId.Threed, 0x1388, 0x1); + channel.Write(ClassId.Threed, 0x138C, 0x1); + channel.Write(ClassId.Threed, 0x1390, 0x1); + channel.Write(ClassId.Threed, 0x1394, 0x0); + channel.Write(ClassId.Threed, 0x139C, 0x0); + channel.Write(ClassId.Threed, 0x1398, 0x0); + channel.Write(ClassId.Threed, 0x1594, 0x0); + channel.Write(ClassId.Threed, 0x1598, 0x1); + channel.Write(ClassId.Threed, 0x159C, 0x1); + channel.Write(ClassId.Threed, 0x15A0, 0x1); + channel.Write(ClassId.Threed, 0x15A4, 0x1); + channel.Write(ClassId.Threed, 0xF54, 0x0); + channel.Write(ClassId.Threed, 0xF58, 0x0); + channel.Write(ClassId.Threed, 0xF5C, 0x0); + channel.Write(ClassId.Threed, 0x19BC, 0x0); + channel.Write(ClassId.Threed, 0xF9C, 0x0); + channel.Write(ClassId.Threed, 0xFA0, 0x0); + channel.Write(ClassId.Threed, 0x12CC, 0x0); + channel.Write(ClassId.Threed, 0x12E8, 0x0); + channel.Write(ClassId.Threed, 0x130C, 0x1); + channel.Write(ClassId.Threed, 0x1360, 0x0); + channel.Write(ClassId.Threed, 0x1364, 0x0); + channel.Write(ClassId.Threed, 0x1368, 0x0); + channel.Write(ClassId.Threed, 0x136C, 0x0); + channel.Write(ClassId.Threed, 0x1370, 0x0); + channel.Write(ClassId.Threed, 0x1374, 0x0); + channel.Write(ClassId.Threed, 0x1378, 0x0); + channel.Write(ClassId.Threed, 0x137C, 0x0); + channel.Write(ClassId.Threed, 0x133C, 0x1); + channel.Write(ClassId.Threed, 0x1340, 0x1); + channel.Write(ClassId.Threed, 0x1344, 0x2); + channel.Write(ClassId.Threed, 0x1348, 0x1); + channel.Write(ClassId.Threed, 0x134C, 0x1); + channel.Write(ClassId.Threed, 0x1350, 0x2); + channel.Write(ClassId.Threed, 0x1358, 0x1); + channel.Write(ClassId.Threed, 0x12E4, 0x0); + channel.Write(ClassId.Threed, 0x131C, 0x0); + channel.Write(ClassId.Threed, 0x1320, 0x0); + channel.Write(ClassId.Threed, 0x1324, 0x0); + channel.Write(ClassId.Threed, 0x1328, 0x0); + channel.Write(ClassId.Threed, 0x19C0, 0x0); + channel.Write(ClassId.Threed, 0x1140, 0x0); + channel.Write(ClassId.Threed, 0xDD0, 0x0); + channel.Write(ClassId.Threed, 0xDD4, 0x1); + channel.Write(ClassId.Threed, 0x2F4, 0x0); + channel.Write(ClassId.Threed, 0x19C4, 0x0); + channel.Write(ClassId.Threed, 0x19C8, 0x1500); + channel.Write(ClassId.Threed, 0x135C, 0x0); + channel.Write(ClassId.Threed, 0xF90, 0x0); + channel.Write(ClassId.Threed, 0x19E0, 0x1); + channel.Write(ClassId.Threed, 0x19E4, 0x1); + channel.Write(ClassId.Threed, 0x19E8, 0x1); + channel.Write(ClassId.Threed, 0x19EC, 0x1); + channel.Write(ClassId.Threed, 0x19F0, 0x1); + channel.Write(ClassId.Threed, 0x19F4, 0x1); + channel.Write(ClassId.Threed, 0x19F8, 0x1); + channel.Write(ClassId.Threed, 0x19FC, 0x1); + channel.Write(ClassId.Threed, 0x19CC, 0x1); + channel.Write(ClassId.Threed, 0x111C, 0x1); + channel.Write(ClassId.Threed, 0x15B8, 0x0); + channel.Write(ClassId.Threed, 0x1A00, 0x1111); + channel.Write(ClassId.Threed, 0x1A04, 0x0); + channel.Write(ClassId.Threed, 0x1A08, 0x0); + channel.Write(ClassId.Threed, 0x1A0C, 0x0); + channel.Write(ClassId.Threed, 0x1A10, 0x0); + channel.Write(ClassId.Threed, 0x1A14, 0x0); + channel.Write(ClassId.Threed, 0x1A18, 0x0); + channel.Write(ClassId.Threed, 0x1A1C, 0x0); + channel.Write(ClassId.Threed, 0xD6C, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD70, 0xFFFF0000); + channel.Write(ClassId.Threed, 0x10F8, 0x1010); + channel.Write(ClassId.Threed, 0xD80, 0x0); + channel.Write(ClassId.Threed, 0xD84, 0x0); + channel.Write(ClassId.Threed, 0xD88, 0x0); + channel.Write(ClassId.Threed, 0xD8C, 0x0); + channel.Write(ClassId.Threed, 0xD90, 0x0); + channel.Write(ClassId.Threed, 0xDA0, 0x0); + channel.Write(ClassId.Threed, 0x7A4, 0x0); + channel.Write(ClassId.Threed, 0x7A8, 0x0); + channel.Write(ClassId.Threed, 0x1508, 0x80000000); + channel.Write(ClassId.Threed, 0x150C, 0x40000000); + channel.Write(ClassId.Threed, 0x1668, 0x0); + channel.Write(ClassId.Threed, 0x318, 0x8); + channel.Write(ClassId.Threed, 0x31C, 0x8); + channel.Write(ClassId.Threed, 0xD9C, 0x1); + channel.Write(ClassId.Threed, 0xF14, 0x0); + channel.Write(ClassId.Threed, 0x374, 0x0); + channel.Write(ClassId.Threed, 0x378, 0xC); + channel.Write(ClassId.Threed, 0x7DC, 0x0); + channel.Write(ClassId.Threed, 0x74C, 0x55); + channel.Write(ClassId.Threed, 0x1420, 0x3); + channel.Write(ClassId.Threed, 0x1008, 0x8); + channel.Write(ClassId.Threed, 0x100C, 0x40); + channel.Write(ClassId.Threed, 0x1010, 0x12C); + channel.Write(ClassId.Threed, 0xD60, 0x40); + channel.Write(ClassId.Threed, 0x1018, 0x20); + channel.Write(ClassId.Threed, 0x101C, 0x1); + channel.Write(ClassId.Threed, 0x1020, 0x20); + channel.Write(ClassId.Threed, 0x1024, 0x1); + channel.Write(ClassId.Threed, 0x1444, 0x0); + channel.Write(ClassId.Threed, 0x1448, 0x0); + channel.Write(ClassId.Threed, 0x144C, 0x0); + channel.Write(ClassId.Threed, 0x360, 0x20164010); + channel.Write(ClassId.Threed, 0x364, 0x20); + channel.Write(ClassId.Threed, 0x368, 0x0); + channel.Write(ClassId.Threed, 0xDA8, 0x30); + channel.Write(ClassId.Threed, 0xDE4, 0x0); + channel.Write(ClassId.Threed, 0x204, 0x6); + channel.Write(ClassId.Threed, 0x2D0, 0x3FFFFF); + channel.Write(ClassId.Threed, 0x1220, 0x5); + channel.Write(ClassId.Threed, 0xFDC, 0x0); + channel.Write(ClassId.Threed, 0xF98, 0x400008); + channel.Write(ClassId.Threed, 0x1284, 0x8000080); + channel.Write(ClassId.Threed, 0x1450, 0x400008); + channel.Write(ClassId.Threed, 0x1454, 0x8000080); + channel.Write(ClassId.Threed, 0x214, 0x0); + channel.Write(ClassId.Twod, 0x200, 0xCF); + channel.Write(ClassId.Twod, 0x204, 0x1); + channel.Write(ClassId.Twod, 0x208, 0x20); + channel.Write(ClassId.Twod, 0x20C, 0x1); + channel.Write(ClassId.Twod, 0x210, 0x0); + channel.Write(ClassId.Twod, 0x214, 0x80); + channel.Write(ClassId.Twod, 0x218, 0x100); + channel.Write(ClassId.Twod, 0x21C, 0x100); + channel.Write(ClassId.Twod, 0x220, 0x0); + channel.Write(ClassId.Twod, 0x224, 0x0); + channel.Write(ClassId.Twod, 0x230, 0xCF); + channel.Write(ClassId.Twod, 0x234, 0x1); + channel.Write(ClassId.Twod, 0x238, 0x20); + channel.Write(ClassId.Twod, 0x23C, 0x1); + channel.Write(ClassId.Twod, 0x244, 0x80); + channel.Write(ClassId.Twod, 0x248, 0x100); + channel.Write(ClassId.Twod, 0x24C, 0x100); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostChannelDeviceFile.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostChannelDeviceFile.cs new file mode 100644 index 00000000..9f16a280 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostChannelDeviceFile.cs @@ -0,0 +1,574 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Gpu; +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap; +using Ryujinx.HLE.HOS.Services.Nv.Types; +using Ryujinx.Memory; +using System; +using System.Collections.Concurrent; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel +{ + class NvHostChannelDeviceFile : NvDeviceFile + { + private static readonly ConcurrentDictionary<ulong, Host1xContext> _host1xContextRegistry = new(); + + private const uint MaxModuleSyncpoint = 16; + + private uint _timeout; + private uint _submitTimeout; + private uint _timeslice; + + private readonly Switch _device; + + private readonly IVirtualMemoryManager _memory; + private readonly Host1xContext _host1xContext; + private readonly long _contextId; + + public GpuChannel Channel { get; } + + public enum ResourcePolicy + { + Device, + Channel + } + + protected static uint[] DeviceSyncpoints = new uint[MaxModuleSyncpoint]; + + protected uint[] ChannelSyncpoints; + + protected static ResourcePolicy ChannelResourcePolicy = ResourcePolicy.Device; + + private NvFence _channelSyncpoint; + + public NvHostChannelDeviceFile(ServiceCtx context, IVirtualMemoryManager memory, ulong owner) : base(context, owner) + { + _device = context.Device; + _memory = memory; + _timeout = 3000; + _submitTimeout = 0; + _timeslice = 0; + _host1xContext = GetHost1XContext(context.Device.Gpu, owner); + _contextId = _host1xContext.Host1x.CreateContext(); + Channel = _device.Gpu.CreateChannel(); + + ChannelInitialization.InitializeState(Channel); + + ChannelSyncpoints = new uint[MaxModuleSyncpoint]; + + _channelSyncpoint.Id = _device.System.HostSyncpoint.AllocateSyncpoint(false); + _channelSyncpoint.UpdateValue(_device.System.HostSyncpoint); + } + + public override NvInternalResult Ioctl(NvIoctl command, Span<byte> arguments) + { + NvInternalResult result = NvInternalResult.NotImplemented; + + if (command.Type == NvIoctl.NvHostCustomMagic) + { + switch (command.Number) + { + case 0x01: + result = Submit(arguments); + break; + case 0x02: + result = CallIoctlMethod<GetParameterArguments>(GetSyncpoint, arguments); + break; + case 0x03: + result = CallIoctlMethod<GetParameterArguments>(GetWaitBase, arguments); + break; + case 0x07: + result = CallIoctlMethod<uint>(SetSubmitTimeout, arguments); + break; + case 0x09: + result = MapCommandBuffer(arguments); + break; + case 0x0a: + result = UnmapCommandBuffer(arguments); + break; + } + } + else if (command.Type == NvIoctl.NvHostMagic) + { + switch (command.Number) + { + case 0x01: + result = CallIoctlMethod<int>(SetNvMapFd, arguments); + break; + case 0x03: + result = CallIoctlMethod<uint>(SetTimeout, arguments); + break; + case 0x08: + result = SubmitGpfifo(arguments); + break; + case 0x09: + result = CallIoctlMethod<AllocObjCtxArguments>(AllocObjCtx, arguments); + break; + case 0x0b: + result = CallIoctlMethod<ZcullBindArguments>(ZcullBind, arguments); + break; + case 0x0c: + result = CallIoctlMethod<SetErrorNotifierArguments>(SetErrorNotifier, arguments); + break; + case 0x0d: + result = CallIoctlMethod<NvChannelPriority>(SetPriority, arguments); + break; + case 0x18: + result = CallIoctlMethod<AllocGpfifoExArguments>(AllocGpfifoEx, arguments); + break; + case 0x1a: + result = CallIoctlMethod<AllocGpfifoExArguments>(AllocGpfifoEx2, arguments); + break; + case 0x1d: + result = CallIoctlMethod<uint>(SetTimeslice, arguments); + break; + } + } + else if (command.Type == NvIoctl.NvGpuMagic) + { + switch (command.Number) + { + case 0x14: + result = CallIoctlMethod<ulong>(SetUserData, arguments); + break; + } + } + + return result; + } + + private NvInternalResult Submit(Span<byte> arguments) + { + SubmitArguments submitHeader = GetSpanAndSkip<SubmitArguments>(ref arguments, 1)[0]; + Span<CommandBuffer> commandBuffers = GetSpanAndSkip<CommandBuffer>(ref arguments, submitHeader.CmdBufsCount); + Span<Reloc> relocs = GetSpanAndSkip<Reloc>(ref arguments, submitHeader.RelocsCount); + Span<uint> relocShifts = GetSpanAndSkip<uint>(ref arguments, submitHeader.RelocsCount); + Span<SyncptIncr> syncptIncrs = GetSpanAndSkip<SyncptIncr>(ref arguments, submitHeader.SyncptIncrsCount); + Span<uint> fenceThresholds = GetSpanAndSkip<uint>(ref arguments, submitHeader.FencesCount); + + lock (_device) + { + for (int i = 0; i < syncptIncrs.Length; i++) + { + SyncptIncr syncptIncr = syncptIncrs[i]; + + uint id = syncptIncr.Id; + + fenceThresholds[i] = Context.Device.System.HostSyncpoint.IncrementSyncpointMax(id, syncptIncr.Incrs); + } + + foreach (CommandBuffer commandBuffer in commandBuffers) + { + NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, commandBuffer.Mem); + + var data = _memory.GetSpan(map.Address + commandBuffer.Offset, commandBuffer.WordsCount * 4); + + _host1xContext.Host1x.Submit(MemoryMarshal.Cast<byte, int>(data), _contextId); + } + } + + return NvInternalResult.Success; + } + + private Span<T> GetSpanAndSkip<T>(ref Span<byte> arguments, int count) where T : unmanaged + { + Span<T> output = MemoryMarshal.Cast<byte, T>(arguments).Slice(0, count); + + arguments = arguments.Slice(Unsafe.SizeOf<T>() * count); + + return output; + } + + private NvInternalResult GetSyncpoint(ref GetParameterArguments arguments) + { + if (arguments.Parameter >= MaxModuleSyncpoint) + { + return NvInternalResult.InvalidInput; + } + + if (ChannelResourcePolicy == ResourcePolicy.Device) + { + arguments.Value = GetSyncpointDevice(_device.System.HostSyncpoint, arguments.Parameter, false); + } + else + { + arguments.Value = GetSyncpointChannel(arguments.Parameter, false); + } + + if (arguments.Value == 0) + { + return NvInternalResult.TryAgain; + } + + return NvInternalResult.Success; + } + + private NvInternalResult GetWaitBase(ref GetParameterArguments arguments) + { + arguments.Value = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult SetSubmitTimeout(ref uint submitTimeout) + { + _submitTimeout = submitTimeout; + + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult MapCommandBuffer(Span<byte> arguments) + { + int headerSize = Unsafe.SizeOf<MapCommandBufferArguments>(); + MapCommandBufferArguments commandBufferHeader = MemoryMarshal.Cast<byte, MapCommandBufferArguments>(arguments)[0]; + Span<CommandBufferHandle> commandBufferEntries = MemoryMarshal.Cast<byte, CommandBufferHandle>(arguments.Slice(headerSize)).Slice(0, commandBufferHeader.NumEntries); + + foreach (ref CommandBufferHandle commandBufferEntry in commandBufferEntries) + { + NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, commandBufferEntry.MapHandle); + + if (map == null) + { + Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{commandBufferEntry.MapHandle:x8}!"); + + return NvInternalResult.InvalidInput; + } + + lock (map) + { + if (map.DmaMapAddress == 0) + { + ulong va = _host1xContext.MemoryAllocator.GetFreeAddress((ulong)map.Size, out ulong freeAddressStartPosition, 1, MemoryManager.PageSize); + + if (va != NvMemoryAllocator.PteUnmapped && va <= uint.MaxValue && (va + (uint)map.Size) <= uint.MaxValue) + { + _host1xContext.MemoryAllocator.AllocateRange(va, (uint)map.Size, freeAddressStartPosition); + _host1xContext.Smmu.Map(map.Address, va, (uint)map.Size, PteKind.Pitch); // FIXME: This should not use the GMMU. + map.DmaMapAddress = va; + } + else + { + map.DmaMapAddress = NvMemoryAllocator.PteUnmapped; + } + } + + commandBufferEntry.MapAddress = (int)map.DmaMapAddress; + } + } + + return NvInternalResult.Success; + } + + private NvInternalResult UnmapCommandBuffer(Span<byte> arguments) + { + int headerSize = Unsafe.SizeOf<MapCommandBufferArguments>(); + MapCommandBufferArguments commandBufferHeader = MemoryMarshal.Cast<byte, MapCommandBufferArguments>(arguments)[0]; + Span<CommandBufferHandle> commandBufferEntries = MemoryMarshal.Cast<byte, CommandBufferHandle>(arguments.Slice(headerSize)).Slice(0, commandBufferHeader.NumEntries); + + foreach (ref CommandBufferHandle commandBufferEntry in commandBufferEntries) + { + NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, commandBufferEntry.MapHandle); + + if (map == null) + { + Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{commandBufferEntry.MapHandle:x8}!"); + + return NvInternalResult.InvalidInput; + } + + lock (map) + { + if (map.DmaMapAddress != 0) + { + // FIXME: + // To make unmapping work, we need separate address space per channel. + // Right now NVDEC and VIC share the GPU address space which is not correct at all. + + // _host1xContext.MemoryAllocator.Free((ulong)map.DmaMapAddress, (uint)map.Size); + + // map.DmaMapAddress = 0; + } + } + } + + return NvInternalResult.Success; + } + + private NvInternalResult SetNvMapFd(ref int nvMapFd) + { + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult SetTimeout(ref uint timeout) + { + _timeout = timeout; + + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult SubmitGpfifo(Span<byte> arguments) + { + int headerSize = Unsafe.SizeOf<SubmitGpfifoArguments>(); + SubmitGpfifoArguments gpfifoSubmissionHeader = MemoryMarshal.Cast<byte, SubmitGpfifoArguments>(arguments)[0]; + Span<ulong> gpfifoEntries = MemoryMarshal.Cast<byte, ulong>(arguments.Slice(headerSize)).Slice(0, gpfifoSubmissionHeader.NumEntries); + + return SubmitGpfifo(ref gpfifoSubmissionHeader, gpfifoEntries); + } + + private NvInternalResult AllocObjCtx(ref AllocObjCtxArguments arguments) + { + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult ZcullBind(ref ZcullBindArguments arguments) + { + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult SetErrorNotifier(ref SetErrorNotifierArguments arguments) + { + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult SetPriority(ref NvChannelPriority priority) + { + switch (priority) + { + case NvChannelPriority.Low: + _timeslice = 1300; // Timeslice low priority in micro-seconds + break; + case NvChannelPriority.Medium: + _timeslice = 2600; // Timeslice medium priority in micro-seconds + break; + case NvChannelPriority.High: + _timeslice = 5200; // Timeslice high priority in micro-seconds + break; + default: + return NvInternalResult.InvalidInput; + } + + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + // TODO: disable and preempt channel when GPU scheduler will be implemented. + + return NvInternalResult.Success; + } + + private NvInternalResult AllocGpfifoEx(ref AllocGpfifoExArguments arguments) + { + _channelSyncpoint.UpdateValue(_device.System.HostSyncpoint); + + arguments.Fence = _channelSyncpoint; + + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult AllocGpfifoEx2(ref AllocGpfifoExArguments arguments) + { + _channelSyncpoint.UpdateValue(_device.System.HostSyncpoint); + + arguments.Fence = _channelSyncpoint; + + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult SetTimeslice(ref uint timeslice) + { + if (timeslice < 1000 || timeslice > 50000) + { + return NvInternalResult.InvalidInput; + } + + _timeslice = timeslice; // in micro-seconds + + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + // TODO: disable and preempt channel when GPU scheduler will be implemented. + + return NvInternalResult.Success; + } + + private NvInternalResult SetUserData(ref ulong userData) + { + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + protected NvInternalResult SubmitGpfifo(ref SubmitGpfifoArguments header, Span<ulong> entries) + { + if (header.Flags.HasFlag(SubmitGpfifoFlags.FenceWait) && header.Flags.HasFlag(SubmitGpfifoFlags.IncrementWithValue)) + { + return NvInternalResult.InvalidInput; + } + + if (header.Flags.HasFlag(SubmitGpfifoFlags.FenceWait) && !_device.System.HostSyncpoint.IsSyncpointExpired(header.Fence.Id, header.Fence.Value)) + { + Channel.PushHostCommandBuffer(CreateWaitCommandBuffer(header.Fence)); + } + + header.Fence.Id = _channelSyncpoint.Id; + + if (header.Flags.HasFlag(SubmitGpfifoFlags.FenceIncrement) || header.Flags.HasFlag(SubmitGpfifoFlags.IncrementWithValue)) + { + uint incrementCount = header.Flags.HasFlag(SubmitGpfifoFlags.FenceIncrement) ? 2u : 0u; + + if (header.Flags.HasFlag(SubmitGpfifoFlags.IncrementWithValue)) + { + incrementCount += header.Fence.Value; + } + + header.Fence.Value = _device.System.HostSyncpoint.IncrementSyncpointMaxExt(header.Fence.Id, (int)incrementCount); + } + else + { + header.Fence.Value = _device.System.HostSyncpoint.ReadSyncpointMaxValue(header.Fence.Id); + } + + Channel.PushEntries(entries); + + if (header.Flags.HasFlag(SubmitGpfifoFlags.FenceIncrement)) + { + Channel.PushHostCommandBuffer(CreateIncrementCommandBuffer(ref header.Fence, header.Flags)); + } + + header.Flags = SubmitGpfifoFlags.None; + + _device.Gpu.GPFifo.SignalNewEntries(); + + return NvInternalResult.Success; + } + + public uint GetSyncpointChannel(uint index, bool isClientManaged) + { + if (ChannelSyncpoints[index] != 0) + { + return ChannelSyncpoints[index]; + } + + ChannelSyncpoints[index] = _device.System.HostSyncpoint.AllocateSyncpoint(isClientManaged); + + return ChannelSyncpoints[index]; + } + + public static uint GetSyncpointDevice(NvHostSyncpt syncpointManager, uint index, bool isClientManaged) + { + if (DeviceSyncpoints[index] != 0) + { + return DeviceSyncpoints[index]; + } + + DeviceSyncpoints[index] = syncpointManager.AllocateSyncpoint(isClientManaged); + + return DeviceSyncpoints[index]; + } + + private static int[] CreateWaitCommandBuffer(NvFence fence) + { + int[] commandBuffer = new int[4]; + + // SyncpointValue = fence.Value; + commandBuffer[0] = 0x2001001C; + commandBuffer[1] = (int)fence.Value; + + // SyncpointAction(fence.id, increment: false, switch_en: true); + commandBuffer[2] = 0x2001001D; + commandBuffer[3] = (((int)fence.Id << 8) | (0 << 0) | (1 << 4)); + + return commandBuffer; + } + + private int[] CreateIncrementCommandBuffer(ref NvFence fence, SubmitGpfifoFlags flags) + { + bool hasWfi = !flags.HasFlag(SubmitGpfifoFlags.SuppressWfi); + + int[] commandBuffer; + + int offset = 0; + + if (hasWfi) + { + commandBuffer = new int[8]; + + // WaitForInterrupt(handle) + commandBuffer[offset++] = 0x2001001E; + commandBuffer[offset++] = 0x0; + } + else + { + commandBuffer = new int[6]; + } + + // SyncpointValue = 0x0; + commandBuffer[offset++] = 0x2001001C; + commandBuffer[offset++] = 0x0; + + // Increment the syncpoint 2 times. (mitigate a hardware bug) + + // SyncpointAction(fence.id, increment: true, switch_en: false); + commandBuffer[offset++] = 0x2001001D; + commandBuffer[offset++] = (((int)fence.Id << 8) | (1 << 0) | (0 << 4)); + + // SyncpointAction(fence.id, increment: true, switch_en: false); + commandBuffer[offset++] = 0x2001001D; + commandBuffer[offset++] = (((int)fence.Id << 8) | (1 << 0) | (0 << 4)); + + return commandBuffer; + } + + public override void Close() + { + _host1xContext.Host1x.DestroyContext(_contextId); + Channel.Dispose(); + + for (int i = 0; i < MaxModuleSyncpoint; i++) + { + if (ChannelSyncpoints[i] != 0) + { + _device.System.HostSyncpoint.ReleaseSyncpoint(ChannelSyncpoints[i]); + ChannelSyncpoints[i] = 0; + } + } + + _device.System.HostSyncpoint.ReleaseSyncpoint(_channelSyncpoint.Id); + _channelSyncpoint.Id = 0; + } + + private static Host1xContext GetHost1XContext(GpuContext gpu, ulong pid) + { + return _host1xContextRegistry.GetOrAdd(pid, (ulong key) => new Host1xContext(gpu, key)); + } + + public static void Destroy() + { + foreach (Host1xContext host1xContext in _host1xContextRegistry.Values) + { + host1xContext.Dispose(); + } + + _host1xContextRegistry.Clear(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostGpuDeviceFile.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostGpuDeviceFile.cs new file mode 100644 index 00000000..f33cc460 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostGpuDeviceFile.cs @@ -0,0 +1,105 @@ +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types; +using Ryujinx.Horizon.Common; +using Ryujinx.Memory; +using System; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel +{ + internal class NvHostGpuDeviceFile : NvHostChannelDeviceFile + { + private KEvent _smExceptionBptIntReportEvent; + private KEvent _smExceptionBptPauseReportEvent; + private KEvent _errorNotifierEvent; + + private int _smExceptionBptIntReportEventHandle; + private int _smExceptionBptPauseReportEventHandle; + private int _errorNotifierEventHandle; + + public NvHostGpuDeviceFile(ServiceCtx context, IVirtualMemoryManager memory, ulong owner) : base(context, memory, owner) + { + _smExceptionBptIntReportEvent = CreateEvent(context, out _smExceptionBptIntReportEventHandle); + _smExceptionBptPauseReportEvent = CreateEvent(context, out _smExceptionBptPauseReportEventHandle); + _errorNotifierEvent = CreateEvent(context, out _errorNotifierEventHandle); + } + + private static KEvent CreateEvent(ServiceCtx context, out int handle) + { + KEvent evnt = new KEvent(context.Device.System.KernelContext); + + if (context.Process.HandleTable.GenerateHandle(evnt.ReadableEvent, out handle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + return evnt; + } + + public override NvInternalResult Ioctl2(NvIoctl command, Span<byte> arguments, Span<byte> inlineInBuffer) + { + NvInternalResult result = NvInternalResult.NotImplemented; + + if (command.Type == NvIoctl.NvHostMagic) + { + switch (command.Number) + { + case 0x1b: + result = CallIoctlMethod<SubmitGpfifoArguments, ulong>(SubmitGpfifoEx, arguments, inlineInBuffer); + break; + } + } + + return result; + } + + public override NvInternalResult QueryEvent(out int eventHandle, uint eventId) + { + // TODO: accurately represent and implement those events. + switch (eventId) + { + case 0x1: + eventHandle = _smExceptionBptIntReportEventHandle; + break; + case 0x2: + eventHandle = _smExceptionBptPauseReportEventHandle; + break; + case 0x3: + eventHandle = _errorNotifierEventHandle; + break; + default: + eventHandle = 0; + break; + } + + return eventHandle != 0 ? NvInternalResult.Success : NvInternalResult.InvalidInput; + } + + private NvInternalResult SubmitGpfifoEx(ref SubmitGpfifoArguments arguments, Span<ulong> inlineData) + { + return SubmitGpfifo(ref arguments, inlineData); + } + + public override void Close() + { + if (_smExceptionBptIntReportEventHandle != 0) + { + Context.Process.HandleTable.CloseHandle(_smExceptionBptIntReportEventHandle); + _smExceptionBptIntReportEventHandle = 0; + } + + if (_smExceptionBptPauseReportEventHandle != 0) + { + Context.Process.HandleTable.CloseHandle(_smExceptionBptPauseReportEventHandle); + _smExceptionBptPauseReportEventHandle = 0; + } + + if (_errorNotifierEventHandle != 0) + { + Context.Process.HandleTable.CloseHandle(_errorNotifierEventHandle); + _errorNotifierEventHandle = 0; + } + + base.Close(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/AllocGpfifoExArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/AllocGpfifoExArguments.cs new file mode 100644 index 00000000..8e5a1523 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/AllocGpfifoExArguments.cs @@ -0,0 +1,17 @@ +using Ryujinx.HLE.HOS.Services.Nv.Types; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct AllocGpfifoExArguments + { + public uint NumEntries; + public uint NumJobs; + public uint Flags; + public NvFence Fence; + public uint Reserved1; + public uint Reserved2; + public uint Reserved3; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/AllocObjCtxArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/AllocObjCtxArguments.cs new file mode 100644 index 00000000..fae91622 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/AllocObjCtxArguments.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct AllocObjCtxArguments + { + public uint ClassNumber; + public uint Flags; + public ulong ObjectId; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/GetParameterArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/GetParameterArguments.cs new file mode 100644 index 00000000..425e665f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/GetParameterArguments.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct GetParameterArguments + { + public uint Parameter; + public uint Value; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/MapCommandBufferArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/MapCommandBufferArguments.cs new file mode 100644 index 00000000..6a7e3da8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/MapCommandBufferArguments.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct CommandBufferHandle + { + public int MapHandle; + public int MapAddress; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct MapCommandBufferArguments + { + public int NumEntries; + public int DataAddress; // Ignored by the driver. + public bool AttachHostChDas; + public byte Padding1; + public short Padding2; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvChannel.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvChannel.cs new file mode 100644 index 00000000..8e2c6ca3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvChannel.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel +{ + class NvChannel + { +#pragma warning disable CS0649 + public int Timeout; + public int SubmitTimeout; + public int Timeslice; +#pragma warning restore CS0649 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvChannelPriority.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvChannelPriority.cs new file mode 100644 index 00000000..4112a9fc --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvChannelPriority.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel +{ + enum NvChannelPriority : uint + { + Low = 50, + Medium = 100, + High = 150 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SetErrorNotifierArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SetErrorNotifierArguments.cs new file mode 100644 index 00000000..1aba53ca --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SetErrorNotifierArguments.cs @@ -0,0 +1,13 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct SetErrorNotifierArguments + { + public ulong Offset; + public ulong Size; + public uint Mem; + public uint Reserved; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitArguments.cs new file mode 100644 index 00000000..05c4280c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitArguments.cs @@ -0,0 +1,40 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct CommandBuffer + { + public int Mem; + public uint Offset; + public int WordsCount; + } + + [StructLayout(LayoutKind.Sequential)] + struct Reloc + { + public int CmdbufMem; + public int CmdbufOffset; + public int Target; + public int TargetOffset; + } + + [StructLayout(LayoutKind.Sequential)] + struct SyncptIncr + { + public uint Id; + public uint Incrs; + public uint Reserved1; + public uint Reserved2; + public uint Reserved3; + } + + [StructLayout(LayoutKind.Sequential)] + struct SubmitArguments + { + public int CmdBufsCount; + public int RelocsCount; + public int SyncptIncrsCount; + public int FencesCount; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitGpfifoArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitGpfifoArguments.cs new file mode 100644 index 00000000..a10abd4b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitGpfifoArguments.cs @@ -0,0 +1,14 @@ +using Ryujinx.HLE.HOS.Services.Nv.Types; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct SubmitGpfifoArguments + { + public long Address; + public int NumEntries; + public SubmitGpfifoFlags Flags; + public NvFence Fence; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitGpfifoFlags.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitGpfifoFlags.cs new file mode 100644 index 00000000..d81fd386 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitGpfifoFlags.cs @@ -0,0 +1,15 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types +{ + [Flags] + enum SubmitGpfifoFlags : uint + { + None, + FenceWait = 1 << 0, + FenceIncrement = 1 << 1, + HwFormat = 1 << 2, + SuppressWfi = 1 << 4, + IncrementWithValue = 1 << 8, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/ZcullBindArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/ZcullBindArguments.cs new file mode 100644 index 00000000..19a997f4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/ZcullBindArguments.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct ZcullBindArguments + { + public ulong GpuVirtualAddress; + public uint Mode; + public uint Reserved; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/NvHostCtrlDeviceFile.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/NvHostCtrlDeviceFile.cs new file mode 100644 index 00000000..f130c455 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/NvHostCtrlDeviceFile.cs @@ -0,0 +1,540 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Gpu.Synchronization; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl.Types; +using Ryujinx.HLE.HOS.Services.Nv.Types; +using Ryujinx.HLE.HOS.Services.Settings; +using Ryujinx.Memory; +using System; +using System.Text; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl +{ + internal class NvHostCtrlDeviceFile : NvDeviceFile + { + public const int EventsCount = 64; + + private bool _isProductionMode; + private Switch _device; + private NvHostEvent[] _events; + + public NvHostCtrlDeviceFile(ServiceCtx context, IVirtualMemoryManager memory, ulong owner) : base(context, owner) + { + if (NxSettings.Settings.TryGetValue("nv!rmos_set_production_mode", out object productionModeSetting)) + { + _isProductionMode = ((string)productionModeSetting) != "0"; // Default value is "" + } + else + { + _isProductionMode = true; + } + + _device = context.Device; + + _events = new NvHostEvent[EventsCount]; + } + + public override NvInternalResult Ioctl(NvIoctl command, Span<byte> arguments) + { + NvInternalResult result = NvInternalResult.NotImplemented; + + if (command.Type == NvIoctl.NvHostCustomMagic) + { + switch (command.Number) + { + case 0x14: + result = CallIoctlMethod<NvFence>(SyncptRead, arguments); + break; + case 0x15: + result = CallIoctlMethod<uint>(SyncptIncr, arguments); + break; + case 0x16: + result = CallIoctlMethod<SyncptWaitArguments>(SyncptWait, arguments); + break; + case 0x19: + result = CallIoctlMethod<SyncptWaitExArguments>(SyncptWaitEx, arguments); + break; + case 0x1a: + result = CallIoctlMethod<NvFence>(SyncptReadMax, arguments); + break; + case 0x1b: + // As Marshal cannot handle unaligned arrays, we do everything by hand here. + GetConfigurationArguments configArgument = GetConfigurationArguments.FromSpan(arguments); + result = GetConfig(configArgument); + + if (result == NvInternalResult.Success) + { + configArgument.CopyTo(arguments); + } + break; + case 0x1c: + result = CallIoctlMethod<uint>(EventSignal, arguments); + break; + case 0x1d: + result = CallIoctlMethod<EventWaitArguments>(EventWait, arguments); + break; + case 0x1e: + result = CallIoctlMethod<EventWaitArguments>(EventWaitAsync, arguments); + break; + case 0x1f: + result = CallIoctlMethod<uint>(EventRegister, arguments); + break; + case 0x20: + result = CallIoctlMethod<uint>(EventUnregister, arguments); + break; + case 0x21: + result = CallIoctlMethod<ulong>(EventKill, arguments); + break; + } + } + + return result; + } + + private int QueryEvent(uint eventId) + { + lock (_events) + { + uint eventSlot; + uint syncpointId; + + if ((eventId >> 28) == 1) + { + eventSlot = eventId & 0xFFFF; + syncpointId = (eventId >> 16) & 0xFFF; + } + else + { + eventSlot = eventId & 0xFF; + syncpointId = eventId >> 4; + } + + if (eventSlot >= EventsCount || _events[eventSlot] == null || _events[eventSlot].Fence.Id != syncpointId) + { + return 0; + } + + return _events[eventSlot].EventHandle; + } + } + + public override NvInternalResult QueryEvent(out int eventHandle, uint eventId) + { + eventHandle = QueryEvent(eventId); + + return eventHandle != 0 ? NvInternalResult.Success : NvInternalResult.InvalidInput; + } + + private NvInternalResult SyncptRead(ref NvFence arguments) + { + return SyncptReadMinOrMax(ref arguments, max: false); + } + + private NvInternalResult SyncptIncr(ref uint id) + { + if (id >= SynchronizationManager.MaxHardwareSyncpoints) + { + return NvInternalResult.InvalidInput; + } + + _device.System.HostSyncpoint.Increment(id); + + return NvInternalResult.Success; + } + + private NvInternalResult SyncptWait(ref SyncptWaitArguments arguments) + { + uint dummyValue = 0; + + return EventWait(ref arguments.Fence, ref dummyValue, arguments.Timeout, isWaitEventAsyncCmd: false, isWaitEventCmd: false); + } + + private NvInternalResult SyncptWaitEx(ref SyncptWaitExArguments arguments) + { + return EventWait(ref arguments.Input.Fence, ref arguments.Value, arguments.Input.Timeout, isWaitEventAsyncCmd: false, isWaitEventCmd: false); + } + + private NvInternalResult SyncptReadMax(ref NvFence arguments) + { + return SyncptReadMinOrMax(ref arguments, max: true); + } + + private NvInternalResult GetConfig(GetConfigurationArguments arguments) + { + if (!_isProductionMode && NxSettings.Settings.TryGetValue($"{arguments.Domain}!{arguments.Parameter}".ToLower(), out object nvSetting)) + { + byte[] settingBuffer = new byte[0x101]; + + if (nvSetting is string stringValue) + { + if (stringValue.Length > 0x100) + { + Logger.Error?.Print(LogClass.ServiceNv, $"{arguments.Domain}!{arguments.Parameter} String value size is too big!"); + } + else + { + settingBuffer = Encoding.ASCII.GetBytes(stringValue + "\0"); + } + } + else if (nvSetting is int intValue) + { + settingBuffer = BitConverter.GetBytes(intValue); + } + else if (nvSetting is bool boolValue) + { + settingBuffer[0] = boolValue ? (byte)1 : (byte)0; + } + else + { + throw new NotImplementedException(nvSetting.GetType().Name); + } + + Logger.Debug?.Print(LogClass.ServiceNv, $"Got setting {arguments.Domain}!{arguments.Parameter}"); + + arguments.Configuration = settingBuffer; + + return NvInternalResult.Success; + } + + // NOTE: This actually return NotAvailableInProduction but this is directly translated as a InvalidInput before returning the ioctl. + //return NvInternalResult.NotAvailableInProduction; + return NvInternalResult.InvalidInput; + } + + private NvInternalResult EventWait(ref EventWaitArguments arguments) + { + return EventWait(ref arguments.Fence, ref arguments.Value, arguments.Timeout, isWaitEventAsyncCmd: false, isWaitEventCmd: true); + } + + private NvInternalResult EventWaitAsync(ref EventWaitArguments arguments) + { + return EventWait(ref arguments.Fence, ref arguments.Value, arguments.Timeout, isWaitEventAsyncCmd: true, isWaitEventCmd: false); + } + + private NvInternalResult EventRegister(ref uint userEventId) + { + lock (_events) + { + NvInternalResult result = EventUnregister(ref userEventId); + + if (result == NvInternalResult.Success) + { + _events[userEventId] = new NvHostEvent(_device.System.HostSyncpoint, userEventId, _device.System); + } + + return result; + } + + } + + private NvInternalResult EventUnregister(ref uint userEventId) + { + lock (_events) + { + if (userEventId >= EventsCount) + { + return NvInternalResult.InvalidInput; + } + + NvHostEvent hostEvent = _events[userEventId]; + + if (hostEvent == null) + { + return NvInternalResult.Success; + } + + if (hostEvent.State == NvHostEventState.Available || + hostEvent.State == NvHostEventState.Cancelled || + hostEvent.State == NvHostEventState.Signaled) + { + _events[userEventId].CloseEvent(Context); + _events[userEventId] = null; + + return NvInternalResult.Success; + } + + return NvInternalResult.Busy; + } + } + + private NvInternalResult EventKill(ref ulong eventMask) + { + lock (_events) + { + NvInternalResult result = NvInternalResult.Success; + + for (uint eventId = 0; eventId < EventsCount; eventId++) + { + if ((eventMask & (1UL << (int)eventId)) != 0) + { + NvInternalResult tmp = EventUnregister(ref eventId); + + if (tmp != NvInternalResult.Success) + { + result = tmp; + } + } + } + + return result; + } + } + + private NvInternalResult EventSignal(ref uint userEventId) + { + uint eventId = userEventId & ushort.MaxValue; + + if (eventId >= EventsCount) + { + return NvInternalResult.InvalidInput; + } + + lock (_events) + { + NvHostEvent hostEvent = _events[eventId]; + + if (hostEvent == null) + { + return NvInternalResult.InvalidInput; + } + + hostEvent.Cancel(_device.Gpu); + + _device.System.HostSyncpoint.UpdateMin(hostEvent.Fence.Id); + + return NvInternalResult.Success; + } + } + + private NvInternalResult SyncptReadMinOrMax(ref NvFence arguments, bool max) + { + if (arguments.Id >= SynchronizationManager.MaxHardwareSyncpoints) + { + return NvInternalResult.InvalidInput; + } + + if (max) + { + arguments.Value = _device.System.HostSyncpoint.ReadSyncpointMaxValue(arguments.Id); + } + else + { + arguments.Value = _device.System.HostSyncpoint.ReadSyncpointValue(arguments.Id); + } + + return NvInternalResult.Success; + } + + private NvInternalResult EventWait(ref NvFence fence, ref uint value, int timeout, bool isWaitEventAsyncCmd, bool isWaitEventCmd) + { + if (fence.Id >= SynchronizationManager.MaxHardwareSyncpoints) + { + return NvInternalResult.InvalidInput; + } + + // First try to check if the syncpoint is already expired on the CPU side + if (_device.System.HostSyncpoint.IsSyncpointExpired(fence.Id, fence.Value)) + { + value = _device.System.HostSyncpoint.ReadSyncpointMinValue(fence.Id); + + return NvInternalResult.Success; + } + + // Try to invalidate the CPU cache and check for expiration again. + uint newCachedSyncpointValue = _device.System.HostSyncpoint.UpdateMin(fence.Id); + + // Has the fence already expired? + if (_device.System.HostSyncpoint.IsSyncpointExpired(fence.Id, fence.Value)) + { + value = newCachedSyncpointValue; + + return NvInternalResult.Success; + } + + // If the timeout is 0, directly return. + if (timeout == 0) + { + return NvInternalResult.TryAgain; + } + + // The syncpoint value isn't at the fence yet, we need to wait. + + if (!isWaitEventAsyncCmd) + { + value = 0; + } + + NvHostEvent hostEvent; + + NvInternalResult result; + + uint eventIndex; + + lock (_events) + { + if (isWaitEventAsyncCmd) + { + eventIndex = value; + + if (eventIndex >= EventsCount) + { + return NvInternalResult.InvalidInput; + } + + hostEvent = _events[eventIndex]; + } + else + { + hostEvent = GetFreeEventLocked(fence.Id, out eventIndex); + } + + if (hostEvent != null) + { + lock (hostEvent.Lock) + { + if (hostEvent.State == NvHostEventState.Available || + hostEvent.State == NvHostEventState.Signaled || + hostEvent.State == NvHostEventState.Cancelled) + { + bool timedOut = hostEvent.Wait(_device.Gpu, fence); + + if (timedOut) + { + if (isWaitEventCmd) + { + value = ((fence.Id & 0xfff) << 16) | 0x10000000; + } + else + { + value = fence.Id << 4; + } + + value |= eventIndex; + + result = NvInternalResult.TryAgain; + } + else + { + value = fence.Value; + + return NvInternalResult.Success; + } + } + else + { + Logger.Error?.Print(LogClass.ServiceNv, $"Invalid Event at index {eventIndex} (isWaitEventAsyncCmd: {isWaitEventAsyncCmd}, isWaitEventCmd: {isWaitEventCmd})"); + + if (hostEvent != null) + { + Logger.Error?.Print(LogClass.ServiceNv, hostEvent.DumpState(_device.Gpu)); + } + + result = NvInternalResult.InvalidInput; + } + } + } + else + { + Logger.Error?.Print(LogClass.ServiceNv, $"Invalid Event at index {eventIndex} (isWaitEventAsyncCmd: {isWaitEventAsyncCmd}, isWaitEventCmd: {isWaitEventCmd})"); + + result = NvInternalResult.InvalidInput; + } + } + + return result; + } + + private NvHostEvent GetFreeEventLocked(uint id, out uint eventIndex) + { + eventIndex = EventsCount; + + uint nullIndex = EventsCount; + + for (uint index = 0; index < EventsCount; index++) + { + NvHostEvent Event = _events[index]; + + if (Event != null) + { + if (Event.State == NvHostEventState.Available || + Event.State == NvHostEventState.Signaled || + Event.State == NvHostEventState.Cancelled) + { + eventIndex = index; + + if (Event.Fence.Id == id) + { + return Event; + } + } + } + else if (nullIndex == EventsCount) + { + nullIndex = index; + } + } + + if (nullIndex < EventsCount) + { + eventIndex = nullIndex; + + EventRegister(ref eventIndex); + + return _events[nullIndex]; + } + + if (eventIndex < EventsCount) + { + return _events[eventIndex]; + } + + return null; + } + + public override void Close() + { + Logger.Warning?.Print(LogClass.ServiceNv, "Closing channel"); + + lock (_events) + { + // If the device file need to be closed, cancel all user events and dispose events. + for (int i = 0; i < _events.Length; i++) + { + NvHostEvent evnt = _events[i]; + + if (evnt != null) + { + lock (evnt.Lock) + { + if (evnt.State == NvHostEventState.Waiting) + { + evnt.State = NvHostEventState.Cancelling; + + evnt.Cancel(_device.Gpu); + } + else if (evnt.State == NvHostEventState.Signaling) + { + // Wait at max 9ms if the guest app is trying to signal the event while closing it.. + int retryCount = 0; + do + { + if (retryCount++ > 9) + { + break; + } + + // TODO: This should be handled by the kernel (reschedule the current thread ect), waiting for Kernel decoupling work. + Thread.Sleep(1); + } while (evnt.State != NvHostEventState.Signaled); + } + + evnt.CloseEvent(Context); + + _events[i] = null; + } + } + } + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/EventWaitArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/EventWaitArguments.cs new file mode 100644 index 00000000..16f970e8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/EventWaitArguments.cs @@ -0,0 +1,13 @@ +using Ryujinx.HLE.HOS.Services.Nv.Types; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct EventWaitArguments + { + public NvFence Fence; + public int Timeout; + public uint Value; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/GetConfigurationArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/GetConfigurationArguments.cs new file mode 100644 index 00000000..3ee318a3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/GetConfigurationArguments.cs @@ -0,0 +1,34 @@ +using System; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl.Types +{ + class GetConfigurationArguments + { + public string Domain; + public string Parameter; + public byte[] Configuration; + + public static GetConfigurationArguments FromSpan(Span<byte> span) + { + string domain = Encoding.ASCII.GetString(span.Slice(0, 0x41)); + string parameter = Encoding.ASCII.GetString(span.Slice(0x41, 0x41)); + + GetConfigurationArguments result = new GetConfigurationArguments + { + Domain = domain.Substring(0, domain.IndexOf('\0')), + Parameter = parameter.Substring(0, parameter.IndexOf('\0')), + Configuration = span.Slice(0x82, 0x101).ToArray() + }; + + return result; + } + + public void CopyTo(Span<byte> span) + { + Encoding.ASCII.GetBytes(Domain + '\0').CopyTo(span.Slice(0, 0x41)); + Encoding.ASCII.GetBytes(Parameter + '\0').CopyTo(span.Slice(0x41, 0x41)); + Configuration.CopyTo(span.Slice(0x82, 0x101)); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostEvent.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostEvent.cs new file mode 100644 index 00000000..ac5512ed --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostEvent.cs @@ -0,0 +1,185 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Gpu; +using Ryujinx.Graphics.Gpu.Synchronization; +using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Nv.Types; +using Ryujinx.Horizon.Common; +using System; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl +{ + class NvHostEvent + { + public NvFence Fence; + public NvHostEventState State; + public KEvent Event; + public int EventHandle; + + private uint _eventId; + private NvHostSyncpt _syncpointManager; + private SyncpointWaiterHandle _waiterInformation; + + private NvFence _previousFailingFence; + private uint _failingCount; + + public readonly object Lock = new object(); + + /// <summary> + /// Max failing count until waiting on CPU. + /// FIXME: This seems enough for most of the cases, reduce if needed. + /// </summary> + private const uint FailingCountMax = 2; + + public NvHostEvent(NvHostSyncpt syncpointManager, uint eventId, Horizon system) + { + Fence.Id = 0; + + State = NvHostEventState.Available; + + Event = new KEvent(system.KernelContext); + + if (KernelStatic.GetCurrentProcess().HandleTable.GenerateHandle(Event.ReadableEvent, out EventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + _eventId = eventId; + + _syncpointManager = syncpointManager; + + ResetFailingState(); + } + + private void ResetFailingState() + { + _previousFailingFence.Id = NvFence.InvalidSyncPointId; + _previousFailingFence.Value = 0; + _failingCount = 0; + } + + private void Signal() + { + lock (Lock) + { + NvHostEventState oldState = State; + + State = NvHostEventState.Signaling; + + if (oldState == NvHostEventState.Waiting) + { + Event.WritableEvent.Signal(); + } + + State = NvHostEventState.Signaled; + } + } + + private void GpuSignaled(SyncpointWaiterHandle waiterInformation) + { + lock (Lock) + { + // If the signal does not match our current waiter, + // then it is from a past fence and we should just ignore it. + if (waiterInformation != null && waiterInformation != _waiterInformation) + { + return; + } + + ResetFailingState(); + + Signal(); + } + } + + public void Cancel(GpuContext gpuContext) + { + lock (Lock) + { + NvHostEventState oldState = State; + + State = NvHostEventState.Cancelling; + + if (oldState == NvHostEventState.Waiting && _waiterInformation != null) + { + gpuContext.Synchronization.UnregisterCallback(Fence.Id, _waiterInformation); + _waiterInformation = null; + + if (_previousFailingFence.Id == Fence.Id && _previousFailingFence.Value == Fence.Value) + { + _failingCount++; + } + else + { + _failingCount = 1; + + _previousFailingFence = Fence; + } + } + + State = NvHostEventState.Cancelled; + + Event.WritableEvent.Clear(); + } + } + + public bool Wait(GpuContext gpuContext, NvFence fence) + { + lock (Lock) + { + // NOTE: nvservices code should always wait on the GPU side. + // If we do this, we may get an abort or undefined behaviour when the GPU processing thread is blocked for a long period (for example, during shader compilation). + // The reason for this is that the NVN code will try to wait until giving up. + // This is done by trying to wait and signal multiple times until aborting after you are past the timeout. + // As such, if it fails too many time, we enforce a wait on the CPU side indefinitely. + // This allows to keep GPU and CPU in sync when we are slow. + if (_failingCount == FailingCountMax) + { + Logger.Warning?.Print(LogClass.ServiceNv, "GPU processing thread is too slow, waiting on CPU..."); + + Fence.Wait(gpuContext, Timeout.InfiniteTimeSpan); + + ResetFailingState(); + + return false; + } + else + { + Fence = fence; + State = NvHostEventState.Waiting; + + _waiterInformation = gpuContext.Synchronization.RegisterCallbackOnSyncpoint(Fence.Id, Fence.Value, GpuSignaled); + + return true; + } + } + } + + public string DumpState(GpuContext gpuContext) + { + string res = $"\nNvHostEvent {_eventId}:\n"; + res += $"\tState: {State}\n"; + + if (State == NvHostEventState.Waiting) + { + res += "\tFence:\n"; + res += $"\t\tId : {Fence.Id}\n"; + res += $"\t\tThreshold : {Fence.Value}\n"; + res += $"\t\tCurrent Value : {gpuContext.Synchronization.GetSyncpointValue(Fence.Id)}\n"; + res += $"\t\tWaiter Valid : {_waiterInformation != null}\n"; + } + + return res; + } + + public void CloseEvent(ServiceCtx context) + { + if (EventHandle != 0) + { + context.Process.HandleTable.CloseHandle(EventHandle); + EventHandle = 0; + } + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostEventState.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostEventState.cs new file mode 100644 index 00000000..c7b4bc9f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostEventState.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl +{ + enum NvHostEventState + { + Available = 0, + Waiting = 1, + Cancelling = 2, + Signaling = 3, + Signaled = 4, + Cancelled = 5 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostSyncPt.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostSyncPt.cs new file mode 100644 index 00000000..27dd1bd1 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostSyncPt.cs @@ -0,0 +1,199 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Gpu.Synchronization; +using System; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl +{ + class NvHostSyncpt + { + public const int VBlank0SyncpointId = 26; + public const int VBlank1SyncpointId = 27; + + private int[] _counterMin; + private int[] _counterMax; + private bool[] _clientManaged; + private bool[] _assigned; + + private Switch _device; + + private object _syncpointAllocatorLock = new object(); + + public NvHostSyncpt(Switch device) + { + _device = device; + _counterMin = new int[SynchronizationManager.MaxHardwareSyncpoints]; + _counterMax = new int[SynchronizationManager.MaxHardwareSyncpoints]; + _clientManaged = new bool[SynchronizationManager.MaxHardwareSyncpoints]; + _assigned = new bool[SynchronizationManager.MaxHardwareSyncpoints]; + + // Reserve VBLANK syncpoints + ReserveSyncpointLocked(VBlank0SyncpointId, true); + ReserveSyncpointLocked(VBlank1SyncpointId, true); + } + + private void ReserveSyncpointLocked(uint id, bool isClientManaged) + { + if (id >= SynchronizationManager.MaxHardwareSyncpoints || _assigned[id]) + { + throw new ArgumentOutOfRangeException(nameof(id)); + } + + _assigned[id] = true; + _clientManaged[id] = isClientManaged; + } + + public uint AllocateSyncpoint(bool isClientManaged) + { + lock (_syncpointAllocatorLock) + { + for (uint i = 1; i < SynchronizationManager.MaxHardwareSyncpoints; i++) + { + if (!_assigned[i]) + { + ReserveSyncpointLocked(i, isClientManaged); + return i; + } + } + } + + Logger.Error?.Print(LogClass.ServiceNv, "Cannot allocate a new syncpoint!"); + + return 0; + } + + public void ReleaseSyncpoint(uint id) + { + if (id == 0) + { + return; + } + + lock (_syncpointAllocatorLock) + { + if (id >= SynchronizationManager.MaxHardwareSyncpoints || !_assigned[id]) + { + throw new ArgumentOutOfRangeException(nameof(id)); + } + + _assigned[id] = false; + _clientManaged[id] = false; + + SetSyncpointMinEqualSyncpointMax(id); + } + } + + public void SetSyncpointMinEqualSyncpointMax(uint id) + { + if (id >= SynchronizationManager.MaxHardwareSyncpoints) + { + throw new ArgumentOutOfRangeException(nameof(id)); + } + + int value = (int)ReadSyncpointValue(id); + + Interlocked.Exchange(ref _counterMax[id], value); + } + + public uint ReadSyncpointValue(uint id) + { + return UpdateMin(id); + } + + public uint ReadSyncpointMinValue(uint id) + { + return (uint)_counterMin[id]; + } + + public uint ReadSyncpointMaxValue(uint id) + { + return (uint)_counterMax[id]; + } + + private bool IsClientManaged(uint id) + { + if (id >= SynchronizationManager.MaxHardwareSyncpoints) + { + return false; + } + + return _clientManaged[id]; + } + + public void Increment(uint id) + { + if (IsClientManaged(id)) + { + IncrementSyncpointMax(id); + } + + IncrementSyncpointGPU(id); + } + + public uint UpdateMin(uint id) + { + uint newValue = _device.Gpu.Synchronization.GetSyncpointValue(id); + + Interlocked.Exchange(ref _counterMin[id], (int)newValue); + + return newValue; + } + + private void IncrementSyncpointGPU(uint id) + { + _device.Gpu.Synchronization.IncrementSyncpoint(id); + } + + public void IncrementSyncpointMin(uint id) + { + Interlocked.Increment(ref _counterMin[id]); + } + + public uint IncrementSyncpointMaxExt(uint id, int count) + { + if (count == 0) + { + return ReadSyncpointMaxValue(id); + } + + uint result = 0; + + for (int i = 0; i < count; i++) + { + result = IncrementSyncpointMax(id); + } + + return result; + } + + private uint IncrementSyncpointMax(uint id) + { + return (uint)Interlocked.Increment(ref _counterMax[id]); + } + + public uint IncrementSyncpointMax(uint id, uint incrs) + { + return (uint)Interlocked.Add(ref _counterMax[id], (int)incrs); + } + + public bool IsSyncpointExpired(uint id, uint threshold) + { + return MinCompare(id, _counterMin[id], _counterMax[id], (int)threshold); + } + + private bool MinCompare(uint id, int min, int max, int threshold) + { + int minDiff = min - threshold; + int maxDiff = max - threshold; + + if (IsClientManaged(id)) + { + return minDiff >= 0; + } + else + { + return (uint)maxDiff >= (uint)minDiff; + } + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/SyncptWaitArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/SyncptWaitArguments.cs new file mode 100644 index 00000000..cda97f18 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/SyncptWaitArguments.cs @@ -0,0 +1,12 @@ +using Ryujinx.HLE.HOS.Services.Nv.Types; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct SyncptWaitArguments + { + public NvFence Fence; + public int Timeout; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/SyncptWaitExArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/SyncptWaitExArguments.cs new file mode 100644 index 00000000..f2279c3d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/SyncptWaitExArguments.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct SyncptWaitExArguments + { + public SyncptWaitArguments Input; + public uint Value; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/NvHostCtrlGpuDeviceFile.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/NvHostCtrlGpuDeviceFile.cs new file mode 100644 index 00000000..d6a8e29f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/NvHostCtrlGpuDeviceFile.cs @@ -0,0 +1,239 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types; +using Ryujinx.Horizon.Common; +using Ryujinx.Memory; +using System; +using System.Diagnostics; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu +{ + class NvHostCtrlGpuDeviceFile : NvDeviceFile + { + private static Stopwatch _pTimer = new Stopwatch(); + private static double _ticksToNs = (1.0 / Stopwatch.Frequency) * 1_000_000_000; + + private KEvent _errorEvent; + private KEvent _unknownEvent; + + public NvHostCtrlGpuDeviceFile(ServiceCtx context, IVirtualMemoryManager memory, ulong owner) : base(context, owner) + { + _errorEvent = new KEvent(context.Device.System.KernelContext); + _unknownEvent = new KEvent(context.Device.System.KernelContext); + } + + static NvHostCtrlGpuDeviceFile() + { + _pTimer.Start(); + } + + public override NvInternalResult Ioctl(NvIoctl command, Span<byte> arguments) + { + NvInternalResult result = NvInternalResult.NotImplemented; + + if (command.Type == NvIoctl.NvGpuMagic) + { + switch (command.Number) + { + case 0x01: + result = CallIoctlMethod<ZcullGetCtxSizeArguments>(ZcullGetCtxSize, arguments); + break; + case 0x02: + result = CallIoctlMethod<ZcullGetInfoArguments>(ZcullGetInfo, arguments); + break; + case 0x03: + result = CallIoctlMethod<ZbcSetTableArguments>(ZbcSetTable, arguments); + break; + case 0x05: + result = CallIoctlMethod<GetCharacteristicsArguments>(GetCharacteristics, arguments); + break; + case 0x06: + result = CallIoctlMethod<GetTpcMasksArguments>(GetTpcMasks, arguments); + break; + case 0x14: + result = CallIoctlMethod<GetActiveSlotMaskArguments>(GetActiveSlotMask, arguments); + break; + case 0x1c: + result = CallIoctlMethod<GetGpuTimeArguments>(GetGpuTime, arguments); + break; + } + } + + return result; + } + + public override NvInternalResult Ioctl3(NvIoctl command, Span<byte> arguments, Span<byte> inlineOutBuffer) + { + NvInternalResult result = NvInternalResult.NotImplemented; + + if (command.Type == NvIoctl.NvGpuMagic) + { + switch (command.Number) + { + case 0x05: + result = CallIoctlMethod<GetCharacteristicsArguments, GpuCharacteristics>(GetCharacteristics, arguments, inlineOutBuffer); + break; + case 0x06: + result = CallIoctlMethod<GetTpcMasksArguments, int>(GetTpcMasks, arguments, inlineOutBuffer); + break; + } + } + + return result; + } + + public override NvInternalResult QueryEvent(out int eventHandle, uint eventId) + { + // TODO: accurately represent and implement those events. + KEvent targetEvent = null; + + switch (eventId) + { + case 0x1: + targetEvent = _errorEvent; + break; + case 0x2: + targetEvent = _unknownEvent; + break; + } + + if (targetEvent != null) + { + if (Context.Process.HandleTable.GenerateHandle(targetEvent.ReadableEvent, out eventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + else + { + eventHandle = 0; + + return NvInternalResult.InvalidInput; + } + + return NvInternalResult.Success; + } + + public override void Close() { } + + private NvInternalResult ZcullGetCtxSize(ref ZcullGetCtxSizeArguments arguments) + { + arguments.Size = 1; + + return NvInternalResult.Success; + } + + private NvInternalResult ZcullGetInfo(ref ZcullGetInfoArguments arguments) + { + arguments.WidthAlignPixels = 0x20; + arguments.HeightAlignPixels = 0x20; + arguments.PixelSquaresByAliquots = 0x400; + arguments.AliquotTotal = 0x800; + arguments.RegionByteMultiplier = 0x20; + arguments.RegionHeaderSize = 0x20; + arguments.SubregionHeaderSize = 0xc0; + arguments.SubregionWidthAlignPixels = 0x20; + arguments.SubregionHeightAlignPixels = 0x40; + arguments.SubregionCount = 0x10; + + return NvInternalResult.Success; + } + + private NvInternalResult ZbcSetTable(ref ZbcSetTableArguments arguments) + { + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult GetCharacteristics(ref GetCharacteristicsArguments arguments) + { + return GetCharacteristics(ref arguments, ref arguments.Characteristics); + } + + private NvInternalResult GetCharacteristics(ref GetCharacteristicsArguments arguments, ref GpuCharacteristics characteristics) + { + arguments.Header.BufferSize = 0xa0; + + characteristics.Arch = 0x120; + characteristics.Impl = 0xb; + characteristics.Rev = 0xa1; + characteristics.NumGpc = 0x1; + characteristics.L2CacheSize = 0x40000; + characteristics.OnBoardVideoMemorySize = 0x0; + characteristics.NumTpcPerGpc = 0x2; + characteristics.BusType = 0x20; + characteristics.BigPageSize = 0x20000; + characteristics.CompressionPageSize = 0x20000; + characteristics.PdeCoverageBitCount = 0x1b; + characteristics.AvailableBigPageSizes = 0x30000; + characteristics.GpcMask = 0x1; + characteristics.SmArchSmVersion = 0x503; + characteristics.SmArchSpaVersion = 0x503; + characteristics.SmArchWarpCount = 0x80; + characteristics.GpuVaBitCount = 0x28; + characteristics.Reserved = 0x0; + characteristics.Flags = 0x55; + characteristics.TwodClass = 0x902d; + characteristics.ThreedClass = 0xb197; + characteristics.ComputeClass = 0xb1c0; + characteristics.GpfifoClass = 0xb06f; + characteristics.InlineToMemoryClass = 0xa140; + characteristics.DmaCopyClass = 0xb0b5; + characteristics.MaxFbpsCount = 0x1; + characteristics.FbpEnMask = 0x0; + characteristics.MaxLtcPerFbp = 0x2; + characteristics.MaxLtsPerLtc = 0x1; + characteristics.MaxTexPerTpc = 0x0; + characteristics.MaxGpcCount = 0x1; + characteristics.RopL2EnMask0 = 0x21d70; + characteristics.RopL2EnMask1 = 0x0; + characteristics.ChipName = 0x6230326d67; + characteristics.GrCompbitStoreBaseHw = 0x0; + + arguments.Characteristics = characteristics; + + return NvInternalResult.Success; + } + + private NvInternalResult GetTpcMasks(ref GetTpcMasksArguments arguments) + { + return GetTpcMasks(ref arguments, ref arguments.TpcMask); + } + + private NvInternalResult GetTpcMasks(ref GetTpcMasksArguments arguments, ref int tpcMask) + { + if (arguments.MaskBufferSize != 0) + { + tpcMask = 3; + arguments.TpcMask = tpcMask; + } + + return NvInternalResult.Success; + } + + private NvInternalResult GetActiveSlotMask(ref GetActiveSlotMaskArguments arguments) + { + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + arguments.Slot = 0x07; + arguments.Mask = 0x01; + + return NvInternalResult.Success; + } + + private NvInternalResult GetGpuTime(ref GetGpuTimeArguments arguments) + { + arguments.Timestamp = GetPTimerNanoSeconds(); + + return NvInternalResult.Success; + } + + private static ulong GetPTimerNanoSeconds() + { + double ticks = _pTimer.ElapsedTicks; + + return (ulong)(ticks * _ticksToNs) & 0xff_ffff_ffff_ffff; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetActiveSlotMaskArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetActiveSlotMaskArguments.cs new file mode 100644 index 00000000..fd73be9e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetActiveSlotMaskArguments.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct GetActiveSlotMaskArguments + { + public int Slot; + public int Mask; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetCharacteristicsArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetCharacteristicsArguments.cs new file mode 100644 index 00000000..d6648178 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetCharacteristicsArguments.cs @@ -0,0 +1,59 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct GpuCharacteristics + { + public int Arch; + public int Impl; + public int Rev; + public int NumGpc; + public long L2CacheSize; + public long OnBoardVideoMemorySize; + public int NumTpcPerGpc; + public int BusType; + public int BigPageSize; + public int CompressionPageSize; + public int PdeCoverageBitCount; + public int AvailableBigPageSizes; + public int GpcMask; + public int SmArchSmVersion; + public int SmArchSpaVersion; + public int SmArchWarpCount; + public int GpuVaBitCount; + public int Reserved; + public long Flags; + public int TwodClass; + public int ThreedClass; + public int ComputeClass; + public int GpfifoClass; + public int InlineToMemoryClass; + public int DmaCopyClass; + public int MaxFbpsCount; + public int FbpEnMask; + public int MaxLtcPerFbp; + public int MaxLtsPerLtc; + public int MaxTexPerTpc; + public int MaxGpcCount; + public int RopL2EnMask0; + public int RopL2EnMask1; + public long ChipName; + public long GrCompbitStoreBaseHw; + } + + struct CharacteristicsHeader + { +#pragma warning disable CS0649 + public long BufferSize; + public long BufferAddress; +#pragma warning restore CS0649 + } + + [StructLayout(LayoutKind.Sequential)] + struct GetCharacteristicsArguments + { + public CharacteristicsHeader Header; + public GpuCharacteristics Characteristics; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetGpuTimeArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetGpuTimeArguments.cs new file mode 100644 index 00000000..084ef71f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetGpuTimeArguments.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct GetGpuTimeArguments + { + public ulong Timestamp; + public ulong Reserved; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetTpcMasksArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetTpcMasksArguments.cs new file mode 100644 index 00000000..16ef2d6e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetTpcMasksArguments.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types +{ + + [StructLayout(LayoutKind.Sequential)] + struct GetTpcMasksArguments + { + public int MaskBufferSize; + public int Reserved; + public long MaskBufferAddress; + public int TpcMask; + public int Padding; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZbcSetTableArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZbcSetTableArguments.cs new file mode 100644 index 00000000..ed74cc26 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZbcSetTableArguments.cs @@ -0,0 +1,49 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct ZbcColorArray + { + private uint element0; + private uint element1; + private uint element2; + private uint element3; + + public uint this[int index] + { + get + { + if (index == 0) + { + return element0; + } + else if (index == 1) + { + return element1; + } + else if (index == 2) + { + return element2; + } + else if (index == 2) + { + return element3; + } + + throw new IndexOutOfRangeException(); + } + } + } + + [StructLayout(LayoutKind.Sequential)] + struct ZbcSetTableArguments + { + public ZbcColorArray ColorDs; + public ZbcColorArray ColorL2; + public uint Depth; + public uint Format; + public uint Type; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZcullGetCtxSizeArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZcullGetCtxSizeArguments.cs new file mode 100644 index 00000000..1e668f86 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZcullGetCtxSizeArguments.cs @@ -0,0 +1,10 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct ZcullGetCtxSizeArguments + { + public int Size; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZcullGetInfoArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZcullGetInfoArguments.cs new file mode 100644 index 00000000..d0d152a3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZcullGetInfoArguments.cs @@ -0,0 +1,19 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct ZcullGetInfoArguments + { + public int WidthAlignPixels; + public int HeightAlignPixels; + public int PixelSquaresByAliquots; + public int AliquotTotal; + public int RegionByteMultiplier; + public int RegionHeaderSize; + public int SubregionHeaderSize; + public int SubregionWidthAlignPixels; + public int SubregionHeightAlignPixels; + public int SubregionCount; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostDbgGpu/NvHostDbgGpuDeviceFile.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostDbgGpu/NvHostDbgGpuDeviceFile.cs new file mode 100644 index 00000000..fe302b98 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostDbgGpu/NvHostDbgGpuDeviceFile.cs @@ -0,0 +1,11 @@ +using Ryujinx.Memory; +using System; +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostDbgGpu +{ + class NvHostDbgGpuDeviceFile : NvDeviceFile + { + public NvHostDbgGpuDeviceFile(ServiceCtx context, IVirtualMemoryManager memory, ulong owner) : base(context, owner) { } + + public override void Close() { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostProfGpu/NvHostProfGpuDeviceFile.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostProfGpu/NvHostProfGpuDeviceFile.cs new file mode 100644 index 00000000..0a2087ed --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostProfGpu/NvHostProfGpuDeviceFile.cs @@ -0,0 +1,11 @@ +using Ryujinx.Memory; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostProfGpu +{ + class NvHostProfGpuDeviceFile : NvDeviceFile + { + public NvHostProfGpuDeviceFile(ServiceCtx context, IVirtualMemoryManager memory, ulong owner) : base(context, owner) { } + + public override void Close() { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvInternalResult.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvInternalResult.cs new file mode 100644 index 00000000..9345baeb --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvInternalResult.cs @@ -0,0 +1,32 @@ +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices +{ + enum NvInternalResult : int + { + Success = 0, + OperationNotPermitted = -1, + NoEntry = -2, + Interrupted = -4, + IoError = -5, + DeviceNotFound = -6, + BadFileNumber = -9, + TryAgain = -11, + OutOfMemory = -12, + AccessDenied = -13, + BadAddress = -14, + Busy = -16, + NotADirectory = -20, + InvalidInput = -22, + FileTableOverflow = -23, + Unknown0x18 = -24, + NotSupported = -25, + FileTooBig = -27, + NoSpaceLeft = -28, + ReadOnlyAttribute = -30, + NotImplemented = -38, + InvalidState = -40, + Restart = -85, + InvalidAddress = -99, + TimedOut = -110, + Unknown0x72 = -114, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/NvMapDeviceFile.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/NvMapDeviceFile.cs new file mode 100644 index 00000000..a52b36a2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/NvMapDeviceFile.cs @@ -0,0 +1,272 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.Memory; +using System; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap +{ + internal class NvMapDeviceFile : NvDeviceFile + { + private const int FlagNotFreedYet = 1; + + private static NvMapIdDictionary _maps = new NvMapIdDictionary(); + + public NvMapDeviceFile(ServiceCtx context, IVirtualMemoryManager memory, ulong owner) : base(context, owner) + { + } + + public override NvInternalResult Ioctl(NvIoctl command, Span<byte> arguments) + { + NvInternalResult result = NvInternalResult.NotImplemented; + + if (command.Type == NvIoctl.NvMapCustomMagic) + { + switch (command.Number) + { + case 0x01: + result = CallIoctlMethod<NvMapCreate>(Create, arguments); + break; + case 0x03: + result = CallIoctlMethod<NvMapFromId>(FromId, arguments); + break; + case 0x04: + result = CallIoctlMethod<NvMapAlloc>(Alloc, arguments); + break; + case 0x05: + result = CallIoctlMethod<NvMapFree>(Free, arguments); + break; + case 0x09: + result = CallIoctlMethod<NvMapParam>(Param, arguments); + break; + case 0x0e: + result = CallIoctlMethod<NvMapGetId>(GetId, arguments); + break; + case 0x02: + case 0x06: + case 0x07: + case 0x08: + case 0x0a: + case 0x0c: + case 0x0d: + case 0x0f: + case 0x10: + case 0x11: + result = NvInternalResult.NotSupported; + break; + } + } + + return result; + } + + private NvInternalResult Create(ref NvMapCreate arguments) + { + if (arguments.Size == 0) + { + Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid size 0x{arguments.Size:x8}!"); + + return NvInternalResult.InvalidInput; + } + + int size = BitUtils.AlignUp(arguments.Size, (int)MemoryManager.PageSize); + + arguments.Handle = CreateHandleFromMap(new NvMapHandle(size)); + + Logger.Debug?.Print(LogClass.ServiceNv, $"Created map {arguments.Handle} with size 0x{size:x8}!"); + + return NvInternalResult.Success; + } + + private NvInternalResult FromId(ref NvMapFromId arguments) + { + NvMapHandle map = GetMapFromHandle(Owner, arguments.Id); + + if (map == null) + { + Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{arguments.Handle:x8}!"); + + return NvInternalResult.InvalidInput; + } + + map.IncrementRefCount(); + + arguments.Handle = arguments.Id; + + return NvInternalResult.Success; + } + + private NvInternalResult Alloc(ref NvMapAlloc arguments) + { + NvMapHandle map = GetMapFromHandle(Owner, arguments.Handle); + + if (map == null) + { + Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{arguments.Handle:x8}!"); + + return NvInternalResult.InvalidInput; + } + + if ((arguments.Align & (arguments.Align - 1)) != 0) + { + Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid alignment 0x{arguments.Align:x8}!"); + + return NvInternalResult.InvalidInput; + } + + if ((uint)arguments.Align < MemoryManager.PageSize) + { + arguments.Align = (int)MemoryManager.PageSize; + } + + NvInternalResult result = NvInternalResult.Success; + + if (!map.Allocated) + { + map.Allocated = true; + + map.Align = arguments.Align; + map.Kind = (byte)arguments.Kind; + + int size = BitUtils.AlignUp(map.Size, (int)MemoryManager.PageSize); + + ulong address = arguments.Address; + + if (address == 0) + { + // When the address is zero, we need to allocate + // our own backing memory for the NvMap. + // TODO: Is this allocation inside the transfer memory? + result = NvInternalResult.OutOfMemory; + } + + if (result == NvInternalResult.Success) + { + map.Size = size; + map.Address = address; + } + } + + return result; + } + + private NvInternalResult Free(ref NvMapFree arguments) + { + NvMapHandle map = GetMapFromHandle(Owner, arguments.Handle); + + if (map == null) + { + Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{arguments.Handle:x8}!"); + + return NvInternalResult.InvalidInput; + } + + if (DecrementMapRefCount(Owner, arguments.Handle)) + { + arguments.Address = map.Address; + arguments.Flags = 0; + } + else + { + arguments.Address = 0; + arguments.Flags = FlagNotFreedYet; + } + + arguments.Size = map.Size; + + return NvInternalResult.Success; + } + + private NvInternalResult Param(ref NvMapParam arguments) + { + NvMapHandle map = GetMapFromHandle(Owner, arguments.Handle); + + if (map == null) + { + Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{arguments.Handle:x8}!"); + + return NvInternalResult.InvalidInput; + } + + switch (arguments.Param) + { + case NvMapHandleParam.Size: arguments.Result = map.Size; break; + case NvMapHandleParam.Align: arguments.Result = map.Align; break; + case NvMapHandleParam.Heap: arguments.Result = 0x40000000; break; + case NvMapHandleParam.Kind: arguments.Result = map.Kind; break; + case NvMapHandleParam.Compr: arguments.Result = 0; break; + + // Note: Base is not supported and returns an error. + // Any other value also returns an error. + default: return NvInternalResult.InvalidInput; + } + + return NvInternalResult.Success; + } + + private NvInternalResult GetId(ref NvMapGetId arguments) + { + NvMapHandle map = GetMapFromHandle(Owner, arguments.Handle); + + if (map == null) + { + Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{arguments.Handle:x8}!"); + + return NvInternalResult.InvalidInput; + } + + arguments.Id = arguments.Handle; + + return NvInternalResult.Success; + } + + public override void Close() + { + // TODO: refcount NvMapDeviceFile instances and remove when closing + // _maps.TryRemove(GetOwner(), out _); + } + + private int CreateHandleFromMap(NvMapHandle map) + { + return _maps.Add(map); + } + + private static bool DeleteMapWithHandle(ulong pid, int handle) + { + return _maps.Delete(handle) != null; + } + + public static void IncrementMapRefCount(ulong pid, int handle) + { + GetMapFromHandle(pid, handle)?.IncrementRefCount(); + } + + public static bool DecrementMapRefCount(ulong pid, int handle) + { + NvMapHandle map = GetMapFromHandle(pid, handle); + + if (map == null) + { + return false; + } + + if (map.DecrementRefCount() <= 0) + { + DeleteMapWithHandle(pid, handle); + + Logger.Debug?.Print(LogClass.ServiceNv, $"Deleted map {handle}!"); + + return true; + } + else + { + return false; + } + } + + public static NvMapHandle GetMapFromHandle(ulong pid, int handle) + { + return _maps.Get(handle); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapAlloc.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapAlloc.cs new file mode 100644 index 00000000..2ec75fc9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapAlloc.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap +{ + [StructLayout(LayoutKind.Sequential)] + struct NvMapAlloc + { + public int Handle; + public int HeapMask; + public int Flags; + public int Align; + public long Kind; + public ulong Address; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapCreate.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapCreate.cs new file mode 100644 index 00000000..b47e4629 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapCreate.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap +{ + [StructLayout(LayoutKind.Sequential)] + struct NvMapCreate + { + public int Size; + public int Handle; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapFree.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapFree.cs new file mode 100644 index 00000000..34bcbc64 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapFree.cs @@ -0,0 +1,14 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap +{ + [StructLayout(LayoutKind.Sequential)] + struct NvMapFree + { + public int Handle; + public int Padding; + public ulong Address; + public int Size; + public int Flags; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapFromId.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapFromId.cs new file mode 100644 index 00000000..2e559534 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapFromId.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap +{ + [StructLayout(LayoutKind.Sequential)] + struct NvMapFromId + { + public int Id; + public int Handle; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapGetId.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapGetId.cs new file mode 100644 index 00000000..fe574eea --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapGetId.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap +{ + [StructLayout(LayoutKind.Sequential)] + struct NvMapGetId + { + public int Id; + public int Handle; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapHandle.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapHandle.cs new file mode 100644 index 00000000..c97cee49 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapHandle.cs @@ -0,0 +1,40 @@ +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap +{ + class NvMapHandle + { +#pragma warning disable CS0649 + public int Handle; + public int Id; +#pragma warning restore CS0649 + public int Size; + public int Align; + public int Kind; + public ulong Address; + public bool Allocated; + public ulong DmaMapAddress; + + private long _dupes; + + public NvMapHandle() + { + _dupes = 1; + } + + public NvMapHandle(int size) : this() + { + Size = size; + } + + public void IncrementRefCount() + { + Interlocked.Increment(ref _dupes); + } + + public long DecrementRefCount() + { + return Interlocked.Decrement(ref _dupes); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapHandleParam.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapHandleParam.cs new file mode 100644 index 00000000..61b73cba --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapHandleParam.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap +{ + enum NvMapHandleParam : int + { + Size = 1, + Align = 2, + Base = 3, + Heap = 4, + Kind = 5, + Compr = 6 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapIdDictionary.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapIdDictionary.cs new file mode 100644 index 00000000..c4733e94 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapIdDictionary.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap +{ + class NvMapIdDictionary + { + private readonly ConcurrentDictionary<int, NvMapHandle> _nvmapHandles; + private int _id; + + public ICollection<NvMapHandle> Values => _nvmapHandles.Values; + + public NvMapIdDictionary() + { + _nvmapHandles = new ConcurrentDictionary<int, NvMapHandle>(); + } + + public int Add(NvMapHandle handle) + { + int id = Interlocked.Add(ref _id, 4); + + if (id != 0 && _nvmapHandles.TryAdd(id, handle)) + { + return id; + } + + throw new InvalidOperationException("NvMap ID overflow."); + } + + public NvMapHandle Get(int id) + { + if (_nvmapHandles.TryGetValue(id, out NvMapHandle handle)) + { + return handle; + } + + return null; + } + + public NvMapHandle Delete(int id) + { + if (_nvmapHandles.TryRemove(id, out NvMapHandle handle)) + { + return handle; + } + + return null; + } + + public ICollection<NvMapHandle> Clear() + { + ICollection<NvMapHandle> values = _nvmapHandles.Values; + + _nvmapHandles.Clear(); + + return values; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapParam.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapParam.cs new file mode 100644 index 00000000..de5bab77 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapParam.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap +{ + [StructLayout(LayoutKind.Sequential)] + struct NvMapParam + { + public int Handle; + public NvMapHandleParam Param; + public int Result; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvIoctl.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvIoctl.cs new file mode 100644 index 00000000..05858694 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvIoctl.cs @@ -0,0 +1,45 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv +{ + [StructLayout(LayoutKind.Sequential)] + struct NvIoctl + { + public const int NvHostCustomMagic = 0x00; + public const int NvMapCustomMagic = 0x01; + public const int NvGpuAsMagic = 0x41; + public const int NvGpuMagic = 0x47; + public const int NvHostMagic = 0x48; + + private const int NumberBits = 8; + private const int TypeBits = 8; + private const int SizeBits = 14; + private const int DirectionBits = 2; + + private const int NumberShift = 0; + private const int TypeShift = NumberShift + NumberBits; + private const int SizeShift = TypeShift + TypeBits; + private const int DirectionShift = SizeShift + SizeBits; + + private const int NumberMask = (1 << NumberBits) - 1; + private const int TypeMask = (1 << TypeBits) - 1; + private const int SizeMask = (1 << SizeBits) - 1; + private const int DirectionMask = (1 << DirectionBits) - 1; + + [Flags] + public enum Direction : uint + { + None = 0, + Read = 1, + Write = 2, + } + + public uint RawValue; + + public uint Number => (RawValue >> NumberShift) & NumberMask; + public uint Type => (RawValue >> TypeShift) & TypeMask; + public uint Size => (RawValue >> SizeShift) & SizeMask; + public Direction DirectionValue => (Direction)((RawValue >> DirectionShift) & DirectionMask); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvMemoryAllocator.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvMemoryAllocator.cs new file mode 100644 index 00000000..341b5e57 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvMemoryAllocator.cs @@ -0,0 +1,310 @@ +using Ryujinx.Common.Collections; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Gpu.Memory; +using System; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Services.Nv +{ + class NvMemoryAllocator + { + private const ulong AddressSpaceSize = 1UL << 40; + + private const ulong DefaultStart = 1UL << 32; + private const ulong InvalidAddress = 0; + + private const ulong PageSize = MemoryManager.PageSize; + private const ulong PageMask = MemoryManager.PageMask; + + public const ulong PteUnmapped = MemoryManager.PteUnmapped; + + // Key --> Start Address of Region + // Value --> End Address of Region + private readonly TreeDictionary<ulong, ulong> _tree = new TreeDictionary<ulong, ulong>(); + + private readonly Dictionary<ulong, LinkedListNode<ulong>> _dictionary = new Dictionary<ulong, LinkedListNode<ulong>>(); + private readonly LinkedList<ulong> _list = new LinkedList<ulong>(); + + public NvMemoryAllocator() + { + _tree.Add(PageSize, AddressSpaceSize); + LinkedListNode<ulong> node = _list.AddFirst(PageSize); + _dictionary[PageSize] = node; + } + + /// <summary> + /// Marks a range of memory as consumed by removing it from the tree. + /// This function will split memory regions if there is available space. + /// </summary> + /// <param name="va">Virtual address at which to allocate</param> + /// <param name="size">Size of the allocation in bytes</param> + /// <param name="referenceAddress">Reference to the address of memory where the allocation can take place</param> + #region Memory Allocation + public void AllocateRange(ulong va, ulong size, ulong referenceAddress = InvalidAddress) + { + lock (_tree) + { + Logger.Debug?.Print(LogClass.ServiceNv, $"Allocating range from 0x{va:X} to 0x{(va + size):X}."); + if (referenceAddress != InvalidAddress) + { + ulong endAddress = va + size; + ulong referenceEndAddress = _tree.Get(referenceAddress); + if (va >= referenceAddress) + { + // Need Left Node + if (va > referenceAddress) + { + ulong leftEndAddress = va; + + // Overwrite existing block with its new smaller range. + _tree.Add(referenceAddress, leftEndAddress); + Logger.Debug?.Print(LogClass.ServiceNv, $"Created smaller address range from 0x{referenceAddress:X} to 0x{leftEndAddress:X}."); + } + else + { + // We need to get rid of the large chunk. + _tree.Remove(referenceAddress); + } + + ulong rightSize = referenceEndAddress - endAddress; + // If leftover space, create a right node. + if (rightSize > 0) + { + Logger.Debug?.Print(LogClass.ServiceNv, $"Created smaller address range from 0x{endAddress:X} to 0x{referenceEndAddress:X}."); + _tree.Add(endAddress, referenceEndAddress); + + LinkedListNode<ulong> node = _list.AddAfter(_dictionary[referenceAddress], endAddress); + _dictionary[endAddress] = node; + } + + if (va == referenceAddress) + { + _list.Remove(_dictionary[referenceAddress]); + _dictionary.Remove(referenceAddress); + } + } + } + } + } + + /// <summary> + /// Marks a range of memory as free by adding it to the tree. + /// This function will automatically compact the tree when it determines there are multiple ranges of free memory adjacent to each other. + /// </summary> + /// <param name="va">Virtual address at which to deallocate</param> + /// <param name="size">Size of the allocation in bytes</param> + public void DeallocateRange(ulong va, ulong size) + { + lock (_tree) + { + Logger.Debug?.Print(LogClass.ServiceNv, $"Deallocating address range from 0x{va:X} to 0x{(va + size):X}."); + + ulong freeAddressStartPosition = _tree.Floor(va); + if (freeAddressStartPosition != InvalidAddress) + { + LinkedListNode<ulong> node = _dictionary[freeAddressStartPosition]; + ulong targetPrevAddress = _dictionary[freeAddressStartPosition].Previous != null ? _dictionary[_dictionary[freeAddressStartPosition].Previous.Value].Value : InvalidAddress; + ulong targetNextAddress = _dictionary[freeAddressStartPosition].Next != null ? _dictionary[_dictionary[freeAddressStartPosition].Next.Value].Value : InvalidAddress; + ulong expandedStart = va; + ulong expandedEnd = va + size; + + while (targetPrevAddress != InvalidAddress) + { + ulong prevAddress = targetPrevAddress; + ulong prevEndAddress = _tree.Get(targetPrevAddress); + if (prevEndAddress >= expandedStart) + { + expandedStart = targetPrevAddress; + LinkedListNode<ulong> prevPtr = _dictionary[prevAddress]; + if (prevPtr.Previous != null) + { + targetPrevAddress = prevPtr.Previous.Value; + } + else + { + targetPrevAddress = InvalidAddress; + } + node = node.Previous; + _tree.Remove(prevAddress); + _list.Remove(_dictionary[prevAddress]); + _dictionary.Remove(prevAddress); + } + else + { + break; + } + } + + while (targetNextAddress != InvalidAddress) + { + ulong nextAddress = targetNextAddress; + ulong nextEndAddress = _tree.Get(targetNextAddress); + if (nextAddress <= expandedEnd) + { + expandedEnd = Math.Max(expandedEnd, nextEndAddress); + LinkedListNode<ulong> nextPtr = _dictionary[nextAddress]; + if (nextPtr.Next != null) + { + targetNextAddress = nextPtr.Next.Value; + } + else + { + targetNextAddress = InvalidAddress; + } + _tree.Remove(nextAddress); + _list.Remove(_dictionary[nextAddress]); + _dictionary.Remove(nextAddress); + } + else + { + break; + } + } + + Logger.Debug?.Print(LogClass.ServiceNv, $"Deallocation resulted in new free range from 0x{expandedStart:X} to 0x{expandedEnd:X}."); + + _tree.Add(expandedStart, expandedEnd); + LinkedListNode<ulong> nodePtr = _list.AddAfter(node, expandedStart); + _dictionary[expandedStart] = nodePtr; + } + } + } + + /// <summary> + /// Gets the address of an unused (free) region of the specified size. + /// </summary> + /// <param name="size">Size of the region in bytes</param> + /// <param name="freeAddressStartPosition">Position at which memory can be allocated</param> + /// <param name="alignment">Required alignment of the region address in bytes</param> + /// <param name="start">Start address of the search on the address space</param> + /// <returns>GPU virtual address of the allocation, or an all ones mask in case of failure</returns> + public ulong GetFreeAddress(ulong size, out ulong freeAddressStartPosition, ulong alignment = 1, ulong start = DefaultStart) + { + // Note: Address 0 is not considered valid by the driver, + // when 0 is returned it's considered a mapping error. + lock (_tree) + { + Logger.Debug?.Print(LogClass.ServiceNv, $"Searching for a free address @ 0x{start:X} of size 0x{size:X}."); + ulong address = start; + + if (alignment == 0) + { + alignment = 1; + } + + alignment = (alignment + PageMask) & ~PageMask; + if (address < AddressSpaceSize) + { + bool reachedEndOfAddresses = false; + ulong targetAddress; + if (start == DefaultStart) + { + Logger.Debug?.Print(LogClass.ServiceNv, $"Target address set to start of the last available range: 0x{_list.Last.Value:X}."); + targetAddress = _list.Last.Value; + } + else + { + targetAddress = _tree.Floor(address); + Logger.Debug?.Print(LogClass.ServiceNv, $"Target address set to floor of 0x{address:X}; resulted in 0x{targetAddress:X}."); + if (targetAddress == InvalidAddress) + { + targetAddress = _tree.Ceiling(address); + Logger.Debug?.Print(LogClass.ServiceNv, $"Target address was invalid, set to ceiling of 0x{address:X}; resulted in 0x{targetAddress:X}"); + } + } + while (address < AddressSpaceSize) + { + if (targetAddress != InvalidAddress) + { + if (address >= targetAddress) + { + if (address + size <= _tree.Get(targetAddress)) + { + Logger.Debug?.Print(LogClass.ServiceNv, $"Found a suitable free address range from 0x{targetAddress:X} to 0x{_tree.Get(targetAddress):X} for 0x{address:X}."); + freeAddressStartPosition = targetAddress; + return address; + } + else + { + Logger.Debug?.Print(LogClass.ServiceNv, "Address requirements exceeded the available space in the target range."); + LinkedListNode<ulong> nextPtr = _dictionary[targetAddress]; + if (nextPtr.Next != null) + { + targetAddress = nextPtr.Next.Value; + Logger.Debug?.Print(LogClass.ServiceNv, $"Moved search to successor range starting at 0x{targetAddress:X}."); + } + else + { + if (reachedEndOfAddresses) + { + Logger.Debug?.Print(LogClass.ServiceNv, "Exiting loop, a full pass has already been completed w/ no suitable free address range."); + break; + } + else + { + reachedEndOfAddresses = true; + address = start; + targetAddress = _tree.Floor(address); + Logger.Debug?.Print(LogClass.ServiceNv, $"Reached the end of the available free ranges, restarting loop @ 0x{targetAddress:X} for 0x{address:X}."); + } + } + } + } + else + { + address += PageSize * (targetAddress / PageSize - (address / PageSize)); + + ulong remainder = address % alignment; + + if (remainder != 0) + { + address = (address - remainder) + alignment; + } + + Logger.Debug?.Print(LogClass.ServiceNv, $"Reset and aligned address to {address:X}."); + + if (address + size > AddressSpaceSize && !reachedEndOfAddresses) + { + reachedEndOfAddresses = true; + address = start; + targetAddress = _tree.Floor(address); + Logger.Debug?.Print(LogClass.ServiceNv, $"Address requirements exceeded the capacity of available address space, restarting loop @ 0x{targetAddress:X} for 0x{address:X}."); + } + } + } + else + { + break; + } + } + } + Logger.Debug?.Print(LogClass.ServiceNv, $"No suitable address range found; returning: 0x{InvalidAddress:X}."); + freeAddressStartPosition = InvalidAddress; + } + + return PteUnmapped; + } + + /// <summary> + /// Checks if a given memory region is mapped or reserved. + /// </summary> + /// <param name="gpuVa">GPU virtual address of the page</param> + /// <param name="size">Size of the allocation in bytes</param> + /// <param name="freeAddressStartPosition">Nearest lower address that memory can be allocated</param> + /// <returns>True if the page is mapped or reserved, false otherwise</returns> + public bool IsRegionInUse(ulong gpuVa, ulong size, out ulong freeAddressStartPosition) + { + lock (_tree) + { + ulong floorAddress = _tree.Floor(gpuVa); + freeAddressStartPosition = floorAddress; + if (floorAddress != InvalidAddress) + { + return !(gpuVa >= floorAddress && ((gpuVa + size) <= _tree.Get(floorAddress))); + } + } + return true; + } + #endregion + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/Types/NvFence.cs b/src/Ryujinx.HLE/HOS/Services/Nv/Types/NvFence.cs new file mode 100644 index 00000000..664610a4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/Types/NvFence.cs @@ -0,0 +1,41 @@ +using Ryujinx.Graphics.Gpu; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x8)] + internal struct NvFence + { + public const uint InvalidSyncPointId = uint.MaxValue; + + public uint Id; + public uint Value; + + public bool IsValid() + { + return Id != InvalidSyncPointId; + } + + public void UpdateValue(NvHostSyncpt hostSyncpt) + { + Value = hostSyncpt.ReadSyncpointValue(Id); + } + + public void Increment(GpuContext gpuContext) + { + Value = gpuContext.Synchronization.IncrementSyncpoint(Id); + } + + public bool Wait(GpuContext gpuContext, TimeSpan timeout) + { + if (IsValid()) + { + return gpuContext.Synchronization.WaitOnSyncpoint(Id, Value, timeout); + } + + return false; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/Types/NvIoctlNotImplementedException.cs b/src/Ryujinx.HLE/HOS/Services/Nv/Types/NvIoctlNotImplementedException.cs new file mode 100644 index 00000000..9404c18c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/Types/NvIoctlNotImplementedException.cs @@ -0,0 +1,55 @@ +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices; +using System; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Nv.Types +{ + class NvIoctlNotImplementedException : Exception + { + public ServiceCtx Context { get; } + public NvDeviceFile DeviceFile { get; } + public NvIoctl Command { get; } + + public NvIoctlNotImplementedException(ServiceCtx context, NvDeviceFile deviceFile, NvIoctl command) + : this(context, deviceFile, command, "The ioctl is not implemented.") + { } + + public NvIoctlNotImplementedException(ServiceCtx context, NvDeviceFile deviceFile, NvIoctl command, string message) + : base(message) + { + Context = context; + DeviceFile = deviceFile; + Command = command; + } + + public override string Message + { + get + { + return base.Message + + Environment.NewLine + + Environment.NewLine + + BuildMessage(); + } + } + + private string BuildMessage() + { + StringBuilder sb = new StringBuilder(); + + sb.AppendLine($"Device File: {DeviceFile.GetType().Name}"); + sb.AppendLine(); + + sb.AppendLine($"Ioctl (0x{Command.RawValue:x8})"); + sb.AppendLine($"\tNumber: 0x{Command.Number:x8}"); + sb.AppendLine($"\tType: 0x{Command.Type:x8}"); + sb.AppendLine($"\tSize: 0x{Command.Size:x8}"); + sb.AppendLine($"\tDirection: {Command.DirectionValue}"); + + sb.AppendLine("Guest Stack Trace:"); + sb.AppendLine(Context.Thread.GetGuestStackTrace()); + + return sb.ToString(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/Types/NvQueryEventNotImplementedException.cs b/src/Ryujinx.HLE/HOS/Services/Nv/Types/NvQueryEventNotImplementedException.cs new file mode 100644 index 00000000..b7a72eba --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/Types/NvQueryEventNotImplementedException.cs @@ -0,0 +1,51 @@ +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices; +using System; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Nv.Types +{ + class NvQueryEventNotImplementedException : Exception + { + public ServiceCtx Context { get; } + public NvDeviceFile DeviceFile { get; } + public uint EventId { get; } + + public NvQueryEventNotImplementedException(ServiceCtx context, NvDeviceFile deviceFile, uint eventId) + : this(context, deviceFile, eventId, "This query event is not implemented.") + { } + + public NvQueryEventNotImplementedException(ServiceCtx context, NvDeviceFile deviceFile, uint eventId, string message) + : base(message) + { + Context = context; + DeviceFile = deviceFile; + EventId = eventId; + } + + public override string Message + { + get + { + return base.Message + + Environment.NewLine + + Environment.NewLine + + BuildMessage(); + } + } + + private string BuildMessage() + { + StringBuilder sb = new StringBuilder(); + + sb.AppendLine($"Device File: {DeviceFile.GetType().Name}"); + sb.AppendLine(); + + sb.AppendLine($"Event ID: (0x{EventId:x8})"); + + sb.AppendLine("Guest Stack Trace:"); + sb.AppendLine(Context.Thread.GetGuestStackTrace()); + + return sb.ToString(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/Types/NvResult.cs b/src/Ryujinx.HLE/HOS/Services/Nv/Types/NvResult.cs new file mode 100644 index 00000000..1c9cae8c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/Types/NvResult.cs @@ -0,0 +1,30 @@ +namespace Ryujinx.HLE.HOS.Services.Nv +{ + enum NvResult : uint + { + Success = 0, + NotImplemented = 1, + NotSupported = 2, + NotInitialized = 3, + InvalidParameter = 4, + Timeout = 5, + InsufficientMemory = 6, + ReadOnlyAttribute = 7, + InvalidState = 8, + InvalidAddress = 9, + InvalidSize = 10, + InvalidValue = 11, + AlreadyAllocated = 13, + Busy = 14, + ResourceError = 15, + CountMismatch = 16, + SharedMemoryTooSmall = 0x1000, + FileOperationFailed = 0x30003, + DirectoryOperationFailed = 0x30004, + NotAvailableInProduction = 0x30006, + IoctlFailed = 0x3000F, + AccessDenied = 0x30010, + FileNotFound = 0x30013, + ModuleNotPresent = 0xA000E, + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/Types/NvStatus.cs b/src/Ryujinx.HLE/HOS/Services/Nv/Types/NvStatus.cs new file mode 100644 index 00000000..d5c35265 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/Types/NvStatus.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x20)] + struct NvStatus + { + public uint MemoryValue1; + public uint MemoryValue2; + public uint MemoryValue3; + public uint MemoryValue4; + public long Padding1; + public long Padding2; + } +}
\ No newline at end of file |
