aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.HLE/HOS/Services/Nv
diff options
context:
space:
mode:
authorTSR Berry <20988865+TSRBerry@users.noreply.github.com>2023-04-08 01:22:00 +0200
committerMary <thog@protonmail.com>2023-04-27 23:51:14 +0200
commitcee712105850ac3385cd0091a923438167433f9f (patch)
tree4a5274b21d8b7f938c0d0ce18736d3f2993b11b1 /src/Ryujinx.HLE/HOS/Services/Nv
parentcd124bda587ef09668a971fa1cac1c3f0cfc9f21 (diff)
Move solution and projects to src
Diffstat (limited to 'src/Ryujinx.HLE/HOS/Services/Nv')
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/Host1xContext.cs32
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/INvDrvDebugFSServices.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs598
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/INvGemControl.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/INvGemCoreDump.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvDeviceFile.cs94
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/NvHostAsGpuDeviceFile.cs401
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceContext.cs190
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceFlags.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AllocSpaceArguments.cs14
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/BindChannelArguments.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/FreeSpaceArguments.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/GetVaRegionsArguments.cs23
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/InitializeExArguments.cs16
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/MapBufferExArguments.cs16
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/RemapArguments.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/UnmapBufferArguments.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/ChannelInitialization.cs1361
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostChannelDeviceFile.cs574
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostGpuDeviceFile.cs105
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/AllocGpfifoExArguments.cs17
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/AllocObjCtxArguments.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/GetParameterArguments.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/MapCommandBufferArguments.cs21
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvChannel.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvChannelPriority.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SetErrorNotifierArguments.cs13
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitArguments.cs40
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitGpfifoArguments.cs14
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitGpfifoFlags.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/ZcullBindArguments.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/NvHostCtrlDeviceFile.cs540
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/EventWaitArguments.cs13
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/GetConfigurationArguments.cs34
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostEvent.cs185
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostEventState.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostSyncPt.cs199
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/SyncptWaitArguments.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/SyncptWaitExArguments.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/NvHostCtrlGpuDeviceFile.cs239
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetActiveSlotMaskArguments.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetCharacteristicsArguments.cs59
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetGpuTimeArguments.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetTpcMasksArguments.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZbcSetTableArguments.cs49
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZcullGetCtxSizeArguments.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZcullGetInfoArguments.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostDbgGpu/NvHostDbgGpuDeviceFile.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostProfGpu/NvHostProfGpuDeviceFile.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvInternalResult.cs32
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/NvMapDeviceFile.cs272
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapAlloc.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapCreate.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapFree.cs14
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapFromId.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapGetId.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapHandle.cs40
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapHandleParam.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapIdDictionary.cs61
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapParam.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvIoctl.cs45
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvMemoryAllocator.cs310
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/Types/NvFence.cs41
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/Types/NvIoctlNotImplementedException.cs55
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/Types/NvQueryEventNotImplementedException.cs51
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/Types/NvResult.cs30
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/Types/NvStatus.cs15
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