diff --git a/Ryujinx.HLE/HOS/GlobalStateTable.cs b/Ryujinx.HLE/HOS/GlobalStateTable.cs
deleted file mode 100644
index 5e5e5ecfa..000000000
--- a/Ryujinx.HLE/HOS/GlobalStateTable.cs
+++ /dev/null
@@ -1,70 +0,0 @@
-using Ryujinx.HLE.HOS.Kernel.Process;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-
-namespace Ryujinx.HLE.HOS
-{
-    class GlobalStateTable
-    {
-        private ConcurrentDictionary<KProcess, IdDictionary> _dictByProcess;
-
-        public GlobalStateTable()
-        {
-            _dictByProcess = new ConcurrentDictionary<KProcess, IdDictionary>();
-        }
-
-        public bool Add(KProcess process, int id, object data)
-        {
-            IdDictionary dict = _dictByProcess.GetOrAdd(process, (key) => new IdDictionary());
-
-            return dict.Add(id, data);
-        }
-
-        public int Add(KProcess process, object data)
-        {
-            IdDictionary dict = _dictByProcess.GetOrAdd(process, (key) => new IdDictionary());
-
-            return dict.Add(data);
-        }
-
-        public object GetData(KProcess process, int id)
-        {
-            if (_dictByProcess.TryGetValue(process, out IdDictionary dict))
-            {
-                return dict.GetData(id);
-            }
-
-            return null;
-        }
-
-        public T GetData<T>(KProcess process, int id)
-        {
-            if (_dictByProcess.TryGetValue(process, out IdDictionary dict))
-            {
-                return dict.GetData<T>(id);
-            }
-
-            return default(T);
-        }
-
-        public object Delete(KProcess process, int id)
-        {
-            if (_dictByProcess.TryGetValue(process, out IdDictionary dict))
-            {
-                return dict.Delete(id);
-            }
-
-            return null;
-        }
-
-        public ICollection<object> DeleteProcess(KProcess process)
-        {
-            if (_dictByProcess.TryRemove(process, out IdDictionary dict))
-            {
-                return dict.Clear();
-            }
-
-            return null;
-        }
-    }
-}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Android/NvFlinger.cs b/Ryujinx.HLE/HOS/Services/Android/NvFlinger.cs
index 5580c3be4..63df78e5e 100644
--- a/Ryujinx.HLE/HOS/Services/Android/NvFlinger.cs
+++ b/Ryujinx.HLE/HOS/Services/Android/NvFlinger.cs
@@ -2,7 +2,7 @@ using Ryujinx.Common.Logging;
 using Ryujinx.Graphics.Gal;
 using Ryujinx.Graphics.Memory;
 using Ryujinx.HLE.HOS.Kernel.Threading;
-using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvGpuAS;
+using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu;
 using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap;
 using System;
 using System.Collections.Generic;
@@ -290,7 +290,7 @@ namespace Ryujinx.HLE.HOS.Services.Android
 
             int bufferOffset = _bufferQueue[slot].Data.Buffer.Surfaces[0].Offset;
 
-            NvMapHandle map = NvMapIoctl.GetNvMap(context, nvMapHandle);
+            NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(context.Process, nvMapHandle);
 
             long fbAddr = map.Address + bufferOffset;
 
@@ -312,7 +312,7 @@ namespace Ryujinx.HLE.HOS.Services.Android
             int right  = crop.Right;
             int bottom = crop.Bottom;
 
-            NvGpuVmm vmm = NvGpuASIoctl.GetASCtx(context).Vmm;
+            NvGpuVmm vmm = NvHostAsGpuDeviceFile.GetAddressSpaceContext(context.Process).Vmm;
 
             _renderer.QueueAction(() =>
             {
diff --git a/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs b/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs
index da34421b7..9e22d17e7 100644
--- a/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs
+++ b/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs
@@ -1,104 +1,301 @@
 using ARMeilleure.Memory;
+using Ryujinx.Common;
 using Ryujinx.Common.Logging;
+using Ryujinx.HLE.Exceptions;
 using Ryujinx.HLE.HOS.Ipc;
-using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.HLE.HOS.Kernel.Memory;
 using Ryujinx.HLE.HOS.Kernel.Process;
-using Ryujinx.HLE.HOS.Kernel.Threading;
-using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvGpuAS;
-using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvGpuGpu;
+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.NvMap;
+using Ryujinx.HLE.HOS.Services.Nv.Types;
 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 delegate int IoctlProcessor(ServiceCtx context, int cmd);
-
-        private static Dictionary<string, IoctlProcessor> _ioctlProcessors =
-                   new Dictionary<string, IoctlProcessor>()
+        private static Dictionary<string, Type> _deviceFileRegistry =
+                   new Dictionary<string, Type>()
         {
-            { "/dev/nvhost-as-gpu",   ProcessIoctlNvGpuAS       },
-            { "/dev/nvhost-ctrl",     ProcessIoctlNvHostCtrl    },
-            { "/dev/nvhost-ctrl-gpu", ProcessIoctlNvGpuGpu      },
-            { "/dev/nvhost-gpu",      ProcessIoctlNvHostChannel },
-            { "/dev/nvhost-nvdec",    ProcessIoctlNvHostChannel },
-            { "/dev/nvhost-vic",      ProcessIoctlNvHostChannel },
-            { "/dev/nvmap",           ProcessIoctlNvMap         }
+                       { "/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) },
         };
 
-        public static GlobalStateTable Fds { get; private set; }
+        private static IdDictionary _deviceFileIdRegistry = new IdDictionary();
 
-        private KEvent _event;
+        private KProcess _owner;
 
         public INvDrvServices(ServiceCtx context)
         {
-            _event = new KEvent(context.Device.System);
+            _owner = null;
         }
 
-        static INvDrvServices()
+        private int Open(ServiceCtx context, string path)
         {
-            Fds = new GlobalStateTable();
+            if (context.Process == _owner)
+            {
+                if (_deviceFileRegistry.TryGetValue(path, out Type deviceFileClass))
+                {
+                    ConstructorInfo constructor = deviceFileClass.GetConstructor(new Type[] { typeof(ServiceCtx) });
+
+                    NvDeviceFile deviceFile = (NvDeviceFile)constructor.Invoke(new object[] { context });
+
+                    return _deviceFileIdRegistry.Add(deviceFile);
+                }
+                else
+                {
+                    Logger.PrintWarning(LogClass.ServiceNv, $"Cannot find file device \"{path}\"!");
+                }
+            }
+
+            return -1;
+        }
+
+        private NvResult GetIoctlArgument(ServiceCtx context, NvIoctl ioctlCommand, out Span<byte> arguments)
+        {
+            (long inputDataPosition,  long inputDataSize)  = context.Request.GetBufferType0x21(0);
+            (long outputDataPosition, long 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.PrintWarning(LogClass.ServiceNv, "Ioctl size inconsistency found!");
+
+                return NvResult.InvalidSize;
+            }
+
+            if (isRead && isWrite)
+            {
+                if (outputDataPosition < inputDataSize)
+                {
+                    arguments = null;
+
+                    Logger.PrintWarning(LogClass.ServiceNv, "Ioctl size inconsistency found!");
+
+                    return NvResult.InvalidSize;
+                }
+
+                byte[] outputData = new byte[outputDataSize];
+
+                context.Memory.ReadBytes(inputDataPosition, outputData, 0, (int)inputDataSize);
+
+                arguments = new Span<byte>(outputData);
+            }
+            else if (isWrite)
+            {
+                byte[] outputData = new byte[outputDataSize];
+
+                arguments = new Span<byte>(outputData);
+            }
+            else
+            {
+                arguments = new Span<byte>(context.Memory.ReadBytes(inputDataPosition, inputDataSize));
+            }
+
+            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.PrintWarning(LogClass.ServiceNv, $"Invalid file descriptor {fd}");
+
+                return NvResult.NotImplemented;
+            }
+
+            if (deviceFile.Owner.Pid != _owner.Pid)
+            {
+                return NvResult.AccessDenied;
+            }
+
+            return NvResult.Success;
+        }
+
+        private NvResult EnsureInitialized()
+        {
+            if (_owner == null)
+            {
+                Logger.PrintWarning(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;
+            }
         }
 
         [Command(0)]
-        // Open(buffer<bytes, 5> path) -> (u32 fd, u32 error_code)
+        // Open(buffer<bytes, 5> path) -> (s32 fd, u32 error_code)
         public ResultCode Open(ServiceCtx context)
         {
-            long namePtr = context.Request.SendBuff[0].Position;
+            NvResult errorCode = EnsureInitialized();
+            int      fd        = -1;
 
-            string name = MemoryHelper.ReadAsciiString(context.Memory, namePtr);
+            if (errorCode == NvResult.Success)
+            {
+                long pathPtr = context.Request.SendBuff[0].Position;
 
-            int fd = Fds.Add(context.Process, new NvFd(name));
+                string path = MemoryHelper.ReadAsciiString(context.Memory, pathPtr);
+
+                fd = Open(context, path);
+
+                if (fd == -1)
+                {
+                    errorCode = NvResult.FileOperationFailed;
+                }
+            }
 
             context.ResponseData.Write(fd);
-            context.ResponseData.Write(0);
+            context.ResponseData.Write((uint)errorCode);
 
             return ResultCode.Success;
         }
 
         [Command(1)]
-        // Ioctl(u32 fd, u32 rq_id, buffer<bytes, 0x21>) -> (u32 error_code, buffer<bytes, 0x22>)
-        [Command(11)] // 3.0.0+
-        // Ioctl2(u32, u32, buffer<bytes, 0x21>, buffer<bytes, 0x21>) -> (u32, buffer<bytes, 0x22>)
+        // Ioctl(s32 fd, u32 ioctl_cmd, buffer<bytes, 0x21> in_args) -> (u32 error_code, buffer<bytes, 0x22> out_args)
         public ResultCode Ioctl(ServiceCtx context)
         {
-            int fd  = context.RequestData.ReadInt32();
-            int cmd = context.RequestData.ReadInt32();
+            NvResult errorCode = EnsureInitialized();
 
-            NvFd fdData = Fds.GetData<NvFd>(context.Process, fd);
-
-            int result = 0;
-
-            if (_ioctlProcessors.TryGetValue(fdData.Name, out IoctlProcessor process))
+            if (errorCode == NvResult.Success)
             {
-                result = process(context, cmd);
-            }
-            else if (!ServiceConfiguration.IgnoreMissingServices)
-            {
-                throw new NotImplementedException($"{fdData.Name} {cmd:x4}");
+                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 (errorCode == NvResult.Success && (ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0)
+                        {
+                            context.Memory.WriteBytes(context.Request.GetBufferType0x22(0).Position, arguments.ToArray());
+                        }
+                    }
+                }
             }
 
-            // TODO: Verify if the error codes needs to be translated.
-            context.ResponseData.Write(result);
+            context.ResponseData.Write((uint)errorCode);
 
             return ResultCode.Success;
         }
 
         [Command(2)]
-        // Close(u32 fd) -> u32 error_code
+        // Close(s32 fd) -> u32 error_code
         public ResultCode Close(ServiceCtx context)
         {
-            int fd = context.RequestData.ReadInt32();
+            NvResult errorCode = EnsureInitialized();
 
-            Fds.Delete(context.Process, fd);
+            if (errorCode == NvResult.Success)
+            {
+                int fd = context.RequestData.ReadInt32();
 
-            context.ResponseData.Write(0);
+                errorCode = GetDeviceFileFromFd(fd, out NvDeviceFile deviceFile);
+
+                if (errorCode == NvResult.Success)
+                {
+                    deviceFile.Close();
+
+                    _deviceFileIdRegistry.Delete(fd);
+                }
+            }
+
+            context.ResponseData.Write((uint)errorCode);
 
             return ResultCode.Success;
         }
@@ -110,33 +307,90 @@ namespace Ryujinx.HLE.HOS.Services.Nv
             long transferMemSize   = context.RequestData.ReadInt64();
             int  transferMemHandle = context.Request.HandleDesc.ToCopy[0];
 
-            NvMapIoctl.InitializeNvMap(context);
+            _owner = context.Process;
 
-            context.ResponseData.Write(0);
+            context.ResponseData.Write((uint)NvResult.Success);
 
             return ResultCode.Success;
         }
 
         [Command(4)]
-        // QueryEvent(u32 fd, u32 event_id) -> (u32, handle<copy, event>)
+        // QueryEvent(s32 fd, u32 event_id) -> (u32, handle<copy, event>)
         public ResultCode QueryEvent(ServiceCtx context)
         {
-            int fd      = context.RequestData.ReadInt32();
-            int eventId = context.RequestData.ReadInt32();
+            NvResult errorCode = EnsureInitialized();
 
-            // TODO: Use Fd/EventId, different channels have different events.
-            if (context.Process.HandleTable.GenerateHandle(_event.ReadableEvent, out int handle) != KernelResult.Success)
+            if (errorCode == NvResult.Success)
             {
-                throw new InvalidOperationException("Out of handles!");
+                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.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
-
-            context.ResponseData.Write(0);
+            context.ResponseData.Write((uint)errorCode);
 
             return ResultCode.Success;
         }
 
+        [Command(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)
+                {
+                    KSharedMemory sharedMemory = context.Process.HandleTable.GetObject<KSharedMemory>(sharedMemoryHandle);
+
+                    errorCode = ConvertInternalErrorCode(deviceFile.MapSharedMemory(sharedMemory, argument));
+                }
+            }
+
+            context.ResponseData.Write((uint)errorCode);
+
+            return ResultCode.Success;
+        }
+
+        [Command(6)]
+        // GetStatus() -> (unknown<0x20>, u32 error_code)
+        public ResultCode GetStatus(ServiceCtx context)
+        {
+            throw new ServiceNotImplementedException(context);
+        }
+
+        [Command(7)]
+        // ForceSetClientPid(u64) -> u32 error_code
+        public ResultCode ForceSetClientPid(ServiceCtx context)
+        {
+            throw new ServiceNotImplementedException(context);
+        }
+
         [Command(8)]
         // SetClientPID(u64, pid) -> u32 error_code
         public ResultCode SetClientPid(ServiceCtx context)
@@ -157,7 +411,105 @@ namespace Ryujinx.HLE.HOS.Services.Nv
             return ResultCode.Success;
         }
 
-        [Command(13)]
+        [Command(10)] // 3.0.0+
+        // InitializeDevtools(u32, handle<copy>) -> u32 error_code;
+        public ResultCode InitializeDevtools(ServiceCtx context)
+        {
+            throw new ServiceNotImplementedException(context);
+        }
+
+        [Command(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>();
+
+                (long inlineInBufferPosition, long inlineInBufferSize) = context.Request.GetBufferType0x21(1);
+
+                errorCode = GetIoctlArgument(context, ioctlCommand, out Span<byte> arguments);
+
+                Span<byte> inlineInBuffer = new Span<byte>(context.Memory.ReadBytes(inlineInBufferPosition, inlineInBufferSize));
+
+                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 (errorCode == NvResult.Success && (ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0)
+                        {
+                            context.Memory.WriteBytes(context.Request.GetBufferType0x22(0).Position, arguments.ToArray());
+                        }
+                    }
+                }
+            }
+
+            context.ResponseData.Write((uint)errorCode);
+
+            return ResultCode.Success;
+        }
+
+        [Command(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>();
+
+                (long inlineOutBufferPosition, long inlineOutBufferSize) = context.Request.GetBufferType0x22(1);
+
+                errorCode = GetIoctlArgument(context, ioctlCommand, out Span<byte> arguments);
+
+                Span<byte> inlineOutBuffer = new Span<byte>(context.Memory.ReadBytes(inlineOutBufferPosition, inlineOutBufferSize));
+
+                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 (errorCode == NvResult.Success && (ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0)
+                        {
+                            context.Memory.WriteBytes(context.Request.GetBufferType0x22(0).Position, arguments.ToArray());
+                            context.Memory.WriteBytes(inlineOutBufferPosition, inlineOutBuffer.ToArray());
+                        }
+                    }
+                }
+            }
+
+            context.ResponseData.Write((uint)errorCode);
+
+            return ResultCode.Success;
+        }
+
+        [Command(13)] // 3.0.0+
         // FinishInitialize(unknown<8>)
         public ResultCode FinishInitialize(ServiceCtx context)
         {
@@ -165,72 +517,5 @@ namespace Ryujinx.HLE.HOS.Services.Nv
 
             return ResultCode.Success;
         }
-
-        private static int ProcessIoctlNvGpuAS(ServiceCtx context, int cmd)
-        {
-            return ProcessIoctl(context, cmd, NvGpuASIoctl.ProcessIoctl);
-        }
-
-        private static int ProcessIoctlNvHostCtrl(ServiceCtx context, int cmd)
-        {
-            return ProcessIoctl(context, cmd, NvHostCtrlIoctl.ProcessIoctl);
-        }
-
-        private static int ProcessIoctlNvGpuGpu(ServiceCtx context, int cmd)
-        {
-            return ProcessIoctl(context, cmd, NvGpuGpuIoctl.ProcessIoctl);
-        }
-
-        private static int ProcessIoctlNvHostChannel(ServiceCtx context, int cmd)
-        {
-            return ProcessIoctl(context, cmd, NvHostChannelIoctl.ProcessIoctl);
-        }
-
-        private static int ProcessIoctlNvMap(ServiceCtx context, int cmd)
-        {
-            return ProcessIoctl(context, cmd, NvMapIoctl.ProcessIoctl);
-        }
-
-        private static int ProcessIoctl(ServiceCtx context, int cmd, IoctlProcessor processor)
-        {
-            if (CmdIn(cmd) && context.Request.GetBufferType0x21().Position == 0)
-            {
-                Logger.PrintError(LogClass.ServiceNv, "Input buffer is null!");
-
-                return NvResult.InvalidInput;
-            }
-
-            if (CmdOut(cmd) && context.Request.GetBufferType0x22().Position == 0)
-            {
-                Logger.PrintError(LogClass.ServiceNv, "Output buffer is null!");
-
-                return NvResult.InvalidInput;
-            }
-
-            return processor(context, cmd);
-        }
-
-        private static bool CmdIn(int cmd)
-        {
-            return ((cmd >> 30) & 1) != 0;
-        }
-
-        private static bool CmdOut(int cmd)
-        {
-            return ((cmd >> 31) & 1) != 0;
-        }
-
-        public static void UnloadProcess(KProcess process)
-        {
-            Fds.DeleteProcess(process);
-
-            NvGpuASIoctl.UnloadProcess(process);
-
-            NvHostChannelIoctl.UnloadProcess(process);
-
-            NvHostCtrlIoctl.UnloadProcess(process);
-
-            NvMapIoctl.UnloadProcess(process);
-        }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvDeviceFile.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvDeviceFile.cs
new file mode 100644
index 000000000..73007c911
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvDeviceFile.cs
@@ -0,0 +1,80 @@
+using Ryujinx.HLE.HOS.Kernel.Memory;
+using Ryujinx.HLE.HOS.Kernel.Process;
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices
+{
+    abstract class NvDeviceFile
+    {
+        public readonly KProcess Owner;
+
+        public NvDeviceFile(ServiceCtx context)
+        {
+            Owner = context.Process;
+        }
+
+        public virtual NvInternalResult QueryEvent(out int eventHandle, uint eventId)
+        {
+            eventHandle = 0;
+
+            return NvInternalResult.NotImplemented;
+        }
+
+        public virtual NvInternalResult MapSharedMemory(KSharedMemory sharedMemory, uint argument)
+        {
+            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);
+
+        protected static NvInternalResult CallIoctlMethod<T>(IoctlProcessor<T> callback, Span<byte> arguments) where T : struct
+        {
+            Debug.Assert(arguments.Length == Unsafe.SizeOf<T>());
+
+            return 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 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 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 callback(ref MemoryMarshal.Cast<byte, T>(arguments)[0], MemoryMarshal.Cast<byte, T1>(inlineBuffer));
+        }
+
+        public abstract void Close();
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvGpuAS/NvGpuASIoctl.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvGpuAS/NvGpuASIoctl.cs
deleted file mode 100644
index 5c8d1fe0d..000000000
--- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvGpuAS/NvGpuASIoctl.cs
+++ /dev/null
@@ -1,330 +0,0 @@
-using ARMeilleure.Memory;
-using Ryujinx.Common.Logging;
-using Ryujinx.Graphics.Memory;
-using Ryujinx.HLE.HOS.Kernel.Process;
-using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap;
-using System;
-using System.Collections.Concurrent;
-
-namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvGpuAS
-{
-    class NvGpuASIoctl
-    {
-        private const int FlagFixedOffset = 1;
-
-        private const int FlagRemapSubRange = 0x100;
-
-        private static ConcurrentDictionary<KProcess, NvGpuASCtx> _asCtxs;
-
-        static NvGpuASIoctl()
-        {
-            _asCtxs = new ConcurrentDictionary<KProcess, NvGpuASCtx>();
-        }
-
-        public static int ProcessIoctl(ServiceCtx context, int cmd)
-        {
-            switch (cmd & 0xffff)
-            {
-                case 0x4101: return BindChannel (context);
-                case 0x4102: return AllocSpace  (context);
-                case 0x4103: return FreeSpace   (context);
-                case 0x4105: return UnmapBuffer (context);
-                case 0x4106: return MapBufferEx (context);
-                case 0x4108: return GetVaRegions(context);
-                case 0x4109: return InitializeEx(context);
-                case 0x4114: return Remap       (context, cmd);
-            }
-
-            throw new NotImplementedException(cmd.ToString("x8"));
-        }
-
-        private static int BindChannel(ServiceCtx context)
-        {
-            long inputPosition  = context.Request.GetBufferType0x21().Position;
-            long outputPosition = context.Request.GetBufferType0x22().Position;
-
-            Logger.PrintStub(LogClass.ServiceNv);
-
-            return NvResult.Success;
-        }
-
-        private static int AllocSpace(ServiceCtx context)
-        {
-            long inputPosition  = context.Request.GetBufferType0x21().Position;
-            long outputPosition = context.Request.GetBufferType0x22().Position;
-
-            NvGpuASAllocSpace args = MemoryHelper.Read<NvGpuASAllocSpace>(context.Memory, inputPosition);
-
-            NvGpuASCtx asCtx = GetASCtx(context);
-
-            ulong size = (ulong)args.Pages *
-                         (ulong)args.PageSize;
-
-            int result = NvResult.Success;
-
-            lock (asCtx)
-            {
-                // Note: When the fixed offset flag is not set,
-                // the Offset field holds the alignment size instead.
-                if ((args.Flags & FlagFixedOffset) != 0)
-                {
-                    args.Offset = asCtx.Vmm.ReserveFixed(args.Offset, (long)size);
-                }
-                else
-                {
-                    args.Offset = asCtx.Vmm.Reserve((long)size, args.Offset);
-                }
-
-                if (args.Offset < 0)
-                {
-                    args.Offset = 0;
-
-                    Logger.PrintWarning(LogClass.ServiceNv, $"Failed to allocate size {size:x16}!");
-
-                    result = NvResult.OutOfMemory;
-                }
-                else
-                {
-                    asCtx.AddReservation(args.Offset, (long)size);
-                }
-            }
-
-            MemoryHelper.Write(context.Memory, outputPosition, args);
-
-            return result;
-        }
-
-        private static int FreeSpace(ServiceCtx context)
-        {
-            long inputPosition  = context.Request.GetBufferType0x21().Position;
-            long outputPosition = context.Request.GetBufferType0x22().Position;
-
-            NvGpuASAllocSpace args = MemoryHelper.Read<NvGpuASAllocSpace>(context.Memory, inputPosition);
-
-            NvGpuASCtx asCtx = GetASCtx(context);
-
-            int result = NvResult.Success;
-
-            lock (asCtx)
-            {
-                ulong size = (ulong)args.Pages *
-                             (ulong)args.PageSize;
-
-                if (asCtx.RemoveReservation(args.Offset))
-                {
-                    asCtx.Vmm.Free(args.Offset, (long)size);
-                }
-                else
-                {
-                    Logger.PrintWarning(LogClass.ServiceNv,
-                        $"Failed to free offset 0x{args.Offset:x16} size 0x{size:x16}!");
-
-                    result = NvResult.InvalidInput;
-                }
-            }
-
-            return result;
-        }
-
-        private static int UnmapBuffer(ServiceCtx context)
-        {
-            long inputPosition  = context.Request.GetBufferType0x21().Position;
-            long outputPosition = context.Request.GetBufferType0x22().Position;
-
-            NvGpuASUnmapBuffer args = MemoryHelper.Read<NvGpuASUnmapBuffer>(context.Memory, inputPosition);
-
-            NvGpuASCtx asCtx = GetASCtx(context);
-
-            lock (asCtx)
-            {
-                if (asCtx.RemoveMap(args.Offset, out long size))
-                {
-                    if (size != 0)
-                    {
-                        asCtx.Vmm.Free(args.Offset, size);
-                    }
-                }
-                else
-                {
-                    Logger.PrintWarning(LogClass.ServiceNv, $"Invalid buffer offset {args.Offset:x16}!");
-                }
-            }
-
-            return NvResult.Success;
-        }
-
-        private static int MapBufferEx(ServiceCtx context)
-        {
-            const string mapErrorMsg = "Failed to map fixed buffer with offset 0x{0:x16} and size 0x{1:x16}!";
-
-            long inputPosition  = context.Request.GetBufferType0x21().Position;
-            long outputPosition = context.Request.GetBufferType0x22().Position;
-
-            NvGpuASMapBufferEx args = MemoryHelper.Read<NvGpuASMapBufferEx>(context.Memory, inputPosition);
-
-            NvGpuASCtx asCtx = GetASCtx(context);
-
-            NvMapHandle map = NvMapIoctl.GetNvMapWithFb(context, args.NvMapHandle);
-
-            if (map == null)
-            {
-                Logger.PrintWarning(LogClass.ServiceNv, $"Invalid NvMap handle 0x{args.NvMapHandle:x8}!");
-
-                return NvResult.InvalidInput;
-            }
-
-            long pa;
-
-            if ((args.Flags & FlagRemapSubRange) != 0)
-            {
-                lock (asCtx)
-                {
-                    if (asCtx.TryGetMapPhysicalAddress(args.Offset, out pa))
-                    {
-                        long va = args.Offset + args.BufferOffset;
-
-                        pa += args.BufferOffset;
-
-                        if (asCtx.Vmm.Map(pa, va, args.MappingSize) < 0)
-                        {
-                            string msg = string.Format(mapErrorMsg, va, args.MappingSize);
-
-                            Logger.PrintWarning(LogClass.ServiceNv, msg);
-
-                            return NvResult.InvalidInput;
-                        }
-
-                        return NvResult.Success;
-                    }
-                    else
-                    {
-                        Logger.PrintWarning(LogClass.ServiceNv, $"Address 0x{args.Offset:x16} not mapped!");
-
-                        return NvResult.InvalidInput;
-                    }
-                }
-            }
-
-            pa = map.Address + args.BufferOffset;
-
-            long size = args.MappingSize;
-
-            if (size == 0)
-            {
-                size = (uint)map.Size;
-            }
-
-            int result = NvResult.Success;
-
-            lock (asCtx)
-            {
-                // Note: When the fixed offset flag is not set,
-                // the Offset field holds the alignment size instead.
-                bool vaAllocated = (args.Flags & FlagFixedOffset) == 0;
-
-                if (!vaAllocated)
-                {
-                    if (asCtx.ValidateFixedBuffer(args.Offset, size))
-                    {
-                        args.Offset = asCtx.Vmm.Map(pa, args.Offset, size);
-                    }
-                    else
-                    {
-                        string msg = string.Format(mapErrorMsg, args.Offset, size);
-
-                        Logger.PrintWarning(LogClass.ServiceNv, msg);
-
-                        result = NvResult.InvalidInput;
-                    }
-                }
-                else
-                {
-                    args.Offset = asCtx.Vmm.Map(pa, size);
-                }
-
-                if (args.Offset < 0)
-                {
-                    args.Offset = 0;
-
-                    Logger.PrintWarning(LogClass.ServiceNv, $"Failed to map size 0x{size:x16}!");
-
-                    result = NvResult.InvalidInput;
-                }
-                else
-                {
-                    asCtx.AddMap(args.Offset, size, pa, vaAllocated);
-                }
-            }
-
-            MemoryHelper.Write(context.Memory, outputPosition, args);
-
-            return result;
-        }
-
-        private static int GetVaRegions(ServiceCtx context)
-        {
-            long inputPosition  = context.Request.GetBufferType0x21().Position;
-            long outputPosition = context.Request.GetBufferType0x22().Position;
-
-            Logger.PrintStub(LogClass.ServiceNv);
-
-            return NvResult.Success;
-        }
-
-        private static int InitializeEx(ServiceCtx context)
-        {
-            long inputPosition  = context.Request.GetBufferType0x21().Position;
-            long outputPosition = context.Request.GetBufferType0x22().Position;
-
-            Logger.PrintStub(LogClass.ServiceNv);
-
-            return NvResult.Success;
-        }
-
-        private static int Remap(ServiceCtx context, int cmd)
-        {
-            int count = ((cmd >> 16) & 0xff) / 0x14;
-
-            long inputPosition  = context.Request.GetBufferType0x21().Position;
-
-            for (int index = 0; index < count; index++, inputPosition += 0x14)
-            {
-                NvGpuASRemap args = MemoryHelper.Read<NvGpuASRemap>(context.Memory, inputPosition);
-
-                NvGpuVmm vmm = GetASCtx(context).Vmm;
-
-                NvMapHandle map = NvMapIoctl.GetNvMapWithFb(context, args.NvMapHandle);
-
-                if (map == null)
-                {
-                    Logger.PrintWarning(LogClass.ServiceNv, $"Invalid NvMap handle 0x{args.NvMapHandle:x8}!");
-
-                    return NvResult.InvalidInput;
-                }
-
-                long result = vmm.Map(map.Address, (long)(uint)args.Offset << 16,
-                                                   (long)(uint)args.Pages  << 16);
-
-                if (result < 0)
-                {
-                    Logger.PrintWarning(LogClass.ServiceNv,
-                        $"Page 0x{args.Offset:x16} size 0x{args.Pages:x16} not allocated!");
-
-                    return NvResult.InvalidInput;
-                }
-            }
-
-            return NvResult.Success;
-        }
-
-        public static NvGpuASCtx GetASCtx(ServiceCtx context)
-        {
-            return _asCtxs.GetOrAdd(context.Process, (key) => new NvGpuASCtx(context));
-        }
-
-        public static void UnloadProcess(KProcess process)
-        {
-            _asCtxs.TryRemove(process, out _);
-        }
-    }
-}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvGpuAS/Types/NvGpuASAllocSpace.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvGpuAS/Types/NvGpuASAllocSpace.cs
deleted file mode 100644
index f0a0db35e..000000000
--- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvGpuAS/Types/NvGpuASAllocSpace.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvGpuAS
-{
-    struct NvGpuASAllocSpace
-    {
-        public int  Pages;
-        public int  PageSize;
-        public int  Flags;
-        public int  Padding;
-        public long Offset;
-    }
-}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvGpuAS/Types/NvGpuASMapBufferEx.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvGpuAS/Types/NvGpuASMapBufferEx.cs
deleted file mode 100644
index 6ef803774..000000000
--- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvGpuAS/Types/NvGpuASMapBufferEx.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvGpuAS
-{
-    struct NvGpuASMapBufferEx
-    {
-        public int  Flags;
-        public int  Kind;
-        public int  NvMapHandle;
-        public int  PageSize;
-        public long BufferOffset;
-        public long MappingSize;
-        public long Offset;
-    }
-}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvGpuAS/Types/NvGpuASRemap.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvGpuAS/Types/NvGpuASRemap.cs
deleted file mode 100644
index 0a6f8003e..000000000
--- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvGpuAS/Types/NvGpuASRemap.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvGpuAS
-{
-    struct NvGpuASRemap
-    {
-        public short Flags;
-        public short Kind;
-        public int   NvMapHandle;
-        public int   Padding;
-        public int   Offset;
-        public int   Pages;
-    }
-}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvGpuAS/Types/NvGpuASUnmapBuffer.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvGpuAS/Types/NvGpuASUnmapBuffer.cs
deleted file mode 100644
index 63476b2fe..000000000
--- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvGpuAS/Types/NvGpuASUnmapBuffer.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvGpuAS
-{
-    struct NvGpuASUnmapBuffer
-    {
-        public long Offset;
-    }
-}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvGpuGpu/NvGpuGpuIoctl.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvGpuGpu/NvGpuGpuIoctl.cs
deleted file mode 100644
index 12f131539..000000000
--- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvGpuGpu/NvGpuGpuIoctl.cs
+++ /dev/null
@@ -1,190 +0,0 @@
-using ARMeilleure.Memory;
-using Ryujinx.Common.Logging;
-using System;
-using System.Diagnostics;
-
-namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvGpuGpu
-{
-    class NvGpuGpuIoctl
-    {
-        private static Stopwatch _pTimer;
-
-        private static double _ticksToNs;
-
-        static NvGpuGpuIoctl()
-        {
-            _pTimer = new Stopwatch();
-
-            _pTimer.Start();
-
-            _ticksToNs = (1.0 / Stopwatch.Frequency) * 1_000_000_000;
-        }
-
-        public static int ProcessIoctl(ServiceCtx context, int cmd)
-        {
-            switch (cmd & 0xffff)
-            {
-                case 0x4701: return ZcullGetCtxSize   (context);
-                case 0x4702: return ZcullGetInfo      (context);
-                case 0x4703: return ZbcSetTable       (context);
-                case 0x4705: return GetCharacteristics(context);
-                case 0x4706: return GetTpcMasks       (context);
-                case 0x4714: return GetActiveSlotMask (context);
-                case 0x471c: return GetGpuTime        (context);
-            }
-
-            throw new NotImplementedException(cmd.ToString("x8"));
-        }
-
-        private static int ZcullGetCtxSize(ServiceCtx context)
-        {
-            long outputPosition = context.Request.GetBufferType0x22().Position;
-
-            NvGpuGpuZcullGetCtxSize args = new NvGpuGpuZcullGetCtxSize
-            {
-                Size = 1
-            };
-
-            MemoryHelper.Write(context.Memory, outputPosition, args);
-
-            Logger.PrintStub(LogClass.ServiceNv);
-
-            return NvResult.Success;
-        }
-
-        private static int ZcullGetInfo(ServiceCtx context)
-        {
-            long outputPosition = context.Request.GetBufferType0x22().Position;
-
-            NvGpuGpuZcullGetInfo args = new NvGpuGpuZcullGetInfo
-            {
-                WidthAlignPixels           = 0x20,
-                HeightAlignPixels          = 0x20,
-                PixelSquaresByAliquots     = 0x400,
-                AliquotTotal               = 0x800,
-                RegionByteMultiplier       = 0x20,
-                RegionHeaderSize           = 0x20,
-                SubregionHeaderSize        = 0xc0,
-                SubregionWidthAlignPixels  = 0x20,
-                SubregionHeightAlignPixels = 0x40,
-                SubregionCount             = 0x10
-            };
-
-            MemoryHelper.Write(context.Memory, outputPosition, args);
-
-            Logger.PrintStub(LogClass.ServiceNv);
-
-            return NvResult.Success;
-        }
-
-        private static int ZbcSetTable(ServiceCtx context)
-        {
-            long inputPosition  = context.Request.GetBufferType0x21().Position;
-            long outputPosition = context.Request.GetBufferType0x22().Position;
-
-            Logger.PrintStub(LogClass.ServiceNv);
-
-            return NvResult.Success;
-        }
-
-        private static int GetCharacteristics(ServiceCtx context)
-        {
-            long inputPosition  = context.Request.GetBufferType0x21().Position;
-            long outputPosition = context.Request.GetBufferType0x22().Position;
-
-            NvGpuGpuGetCharacteristics args = MemoryHelper.Read<NvGpuGpuGetCharacteristics>(context.Memory, inputPosition);
-
-            args.BufferSize = 0xa0;
-
-            args.Arch                   = 0x120;
-            args.Impl                   = 0xb;
-            args.Rev                    = 0xa1;
-            args.NumGpc                 = 0x1;
-            args.L2CacheSize            = 0x40000;
-            args.OnBoardVideoMemorySize = 0x0;
-            args.NumTpcPerGpc           = 0x2;
-            args.BusType                = 0x20;
-            args.BigPageSize            = 0x20000;
-            args.CompressionPageSize    = 0x20000;
-            args.PdeCoverageBitCount    = 0x1b;
-            args.AvailableBigPageSizes  = 0x30000;
-            args.GpcMask                = 0x1;
-            args.SmArchSmVersion        = 0x503;
-            args.SmArchSpaVersion       = 0x503;
-            args.SmArchWarpCount        = 0x80;
-            args.GpuVaBitCount          = 0x28;
-            args.Reserved               = 0x0;
-            args.Flags                  = 0x55;
-            args.TwodClass              = 0x902d;
-            args.ThreedClass            = 0xb197;
-            args.ComputeClass           = 0xb1c0;
-            args.GpfifoClass            = 0xb06f;
-            args.InlineToMemoryClass    = 0xa140;
-            args.DmaCopyClass           = 0xb0b5;
-            args.MaxFbpsCount           = 0x1;
-            args.FbpEnMask              = 0x0;
-            args.MaxLtcPerFbp           = 0x2;
-            args.MaxLtsPerLtc           = 0x1;
-            args.MaxTexPerTpc           = 0x0;
-            args.MaxGpcCount            = 0x1;
-            args.RopL2EnMask0           = 0x21d70;
-            args.RopL2EnMask1           = 0x0;
-            args.ChipName               = 0x6230326d67;
-            args.GrCompbitStoreBaseHw   = 0x0;
-
-            MemoryHelper.Write(context.Memory, outputPosition, args);
-
-            return NvResult.Success;
-        }
-
-        private static int GetTpcMasks(ServiceCtx context)
-        {
-            long inputPosition  = context.Request.GetBufferType0x21().Position;
-            long outputPosition = context.Request.GetBufferType0x22().Position;
-
-            NvGpuGpuGetTpcMasks args = MemoryHelper.Read<NvGpuGpuGetTpcMasks>(context.Memory, inputPosition);
-
-            if (args.MaskBufferSize != 0)
-            {
-                args.TpcMask = 3;
-            }
-
-            MemoryHelper.Write(context.Memory, outputPosition, args);
-
-            return NvResult.Success;
-        }
-
-        private static int GetActiveSlotMask(ServiceCtx context)
-        {
-            long outputPosition = context.Request.GetBufferType0x22().Position;
-
-            NvGpuGpuGetActiveSlotMask args = new NvGpuGpuGetActiveSlotMask
-            {
-                Slot = 0x07,
-                Mask = 0x01
-            };
-
-            MemoryHelper.Write(context.Memory, outputPosition, args);
-
-            Logger.PrintStub(LogClass.ServiceNv);
-
-            return NvResult.Success;
-        }
-
-        private static int GetGpuTime(ServiceCtx context)
-        {
-            long outputPosition = context.Request.GetBufferType0x22().Position;
-
-            context.Memory.WriteInt64(outputPosition, GetPTimerNanoSeconds());
-
-            return NvResult.Success;
-        }
-
-        private static long GetPTimerNanoSeconds()
-        {
-            double ticks = _pTimer.ElapsedTicks;
-
-            return (long)(ticks * _ticksToNs) & 0xff_ffff_ffff_ffff;
-        }
-    }
-}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvGpuGpu/Types/NvGpuGpuGetActiveSlotMask.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvGpuGpu/Types/NvGpuGpuGetActiveSlotMask.cs
deleted file mode 100644
index 1b4c53451..000000000
--- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvGpuGpu/Types/NvGpuGpuGetActiveSlotMask.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvGpuGpu
-{
-    struct NvGpuGpuGetActiveSlotMask
-    {
-        public int Slot;
-        public int Mask;
-    }
-}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvGpuGpu/Types/NvGpuGpuGetTpcMasks.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvGpuGpu/Types/NvGpuGpuGetTpcMasks.cs
deleted file mode 100644
index bc0966da1..000000000
--- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvGpuGpu/Types/NvGpuGpuGetTpcMasks.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvGpuGpu
-{
-    struct NvGpuGpuGetTpcMasks
-    {
-        public int  MaskBufferSize;
-        public int  Reserved;
-        public long MaskBufferAddress;
-        public int  TpcMask;
-        public int  Padding;
-    }
-}
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvGpuGpu/Types/NvGpuGpuZcullGetCtxSize.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvGpuGpu/Types/NvGpuGpuZcullGetCtxSize.cs
deleted file mode 100644
index 8706d51da..000000000
--- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvGpuGpu/Types/NvGpuGpuZcullGetCtxSize.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvGpuGpu
-{
-    struct NvGpuGpuZcullGetCtxSize
-    {
-        public int Size;
-    }
-}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/NvHostAsGpuDeviceFile.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/NvHostAsGpuDeviceFile.cs
new file mode 100644
index 000000000..70783b438
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/NvHostAsGpuDeviceFile.cs
@@ -0,0 +1,318 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.Graphics.Memory;
+using Ryujinx.HLE.HOS.Kernel.Process;
+using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types;
+using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap;
+using System;
+using System.Collections.Concurrent;
+
+namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu
+{
+    class NvHostAsGpuDeviceFile : NvDeviceFile
+    {
+        private static ConcurrentDictionary<KProcess, AddressSpaceContext> _addressSpaceContextRegistry = new ConcurrentDictionary<KProcess, AddressSpaceContext>();
+
+        public NvHostAsGpuDeviceFile(ServiceCtx context) : base(context) { }
+
+        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)
+        {
+            Logger.PrintStub(LogClass.ServiceNv);
+
+            return NvInternalResult.Success;
+        }
+
+        private NvInternalResult AllocSpace(ref AllocSpaceArguments arguments)
+        {
+            AddressSpaceContext addressSpaceContext = GetAddressSpaceContext(Owner);
+
+            ulong size = (ulong)arguments.Pages * (ulong)arguments.PageSize;
+
+            NvInternalResult result = NvInternalResult.Success;
+
+            lock (addressSpaceContext)
+            {
+                // Note: When the fixed offset flag is not set,
+                // the Offset field holds the alignment size instead.
+                if ((arguments.Flags & AddressSpaceFlags.FixedOffset) != 0)
+                {
+                    arguments.Offset = addressSpaceContext.Vmm.ReserveFixed(arguments.Offset, (long)size);
+                }
+                else
+                {
+                    arguments.Offset = addressSpaceContext.Vmm.Reserve((long)size, arguments.Offset);
+                }
+
+                if (arguments.Offset < 0)
+                {
+                    arguments.Offset = 0;
+
+                    Logger.PrintWarning(LogClass.ServiceNv, $"Failed to allocate size {size:x16}!");
+
+                    result = NvInternalResult.OutOfMemory;
+                }
+                else
+                {
+                    addressSpaceContext.AddReservation(arguments.Offset, (long)size);
+                }
+            }
+
+            return result;
+        }
+
+        private NvInternalResult FreeSpace(ref FreeSpaceArguments arguments)
+        {
+            AddressSpaceContext addressSpaceContext = GetAddressSpaceContext(Owner);
+
+            NvInternalResult result = NvInternalResult.Success;
+
+            lock (addressSpaceContext)
+            {
+                ulong size = (ulong)arguments.Pages * (ulong)arguments.PageSize;
+
+                if (addressSpaceContext.RemoveReservation(arguments.Offset))
+                {
+                    addressSpaceContext.Vmm.Free(arguments.Offset, (long)size);
+                }
+                else
+                {
+                    Logger.PrintWarning(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)
+        {
+            AddressSpaceContext addressSpaceContext = GetAddressSpaceContext(Owner);
+
+            lock (addressSpaceContext)
+            {
+                if (addressSpaceContext.RemoveMap(arguments.Offset, out long size))
+                {
+                    if (size != 0)
+                    {
+                        addressSpaceContext.Vmm.Free(arguments.Offset, size);
+                    }
+                }
+                else
+                {
+                    Logger.PrintWarning(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} and size 0x{1:x16}!";
+
+            AddressSpaceContext addressSpaceContext = GetAddressSpaceContext(Owner);
+
+            NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, arguments.NvMapHandle, true);
+
+            if (map == null)
+            {
+                Logger.PrintWarning(LogClass.ServiceNv, $"Invalid NvMap handle 0x{arguments.NvMapHandle:x8}!");
+
+                return NvInternalResult.InvalidInput;
+            }
+
+            long physicalAddress;
+
+            if ((arguments.Flags & AddressSpaceFlags.RemapSubRange) != 0)
+            {
+                lock (addressSpaceContext)
+                {
+                    if (addressSpaceContext.TryGetMapPhysicalAddress(arguments.Offset, out physicalAddress))
+                    {
+                        long virtualAddress = arguments.Offset + arguments.BufferOffset;
+
+                        physicalAddress += arguments.BufferOffset;
+
+                        if (addressSpaceContext.Vmm.Map(physicalAddress, virtualAddress, arguments.MappingSize) < 0)
+                        {
+                            string message = string.Format(mapErrorMsg, virtualAddress, arguments.MappingSize);
+
+                            Logger.PrintWarning(LogClass.ServiceNv, message);
+
+                            return NvInternalResult.InvalidInput;
+                        }
+
+                        return NvInternalResult.Success;
+                    }
+                    else
+                    {
+                        Logger.PrintWarning(LogClass.ServiceNv, $"Address 0x{arguments.Offset:x16} not mapped!");
+
+                        return NvInternalResult.InvalidInput;
+                    }
+                }
+            }
+
+            physicalAddress = map.Address + arguments.BufferOffset;
+
+            long size = arguments.MappingSize;
+
+            if (size == 0)
+            {
+                size = (uint)map.Size;
+            }
+
+            NvInternalResult result = NvInternalResult.Success;
+
+            lock (addressSpaceContext)
+            {
+                // 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 (addressSpaceContext.ValidateFixedBuffer(arguments.Offset, size))
+                    {
+                        arguments.Offset = addressSpaceContext.Vmm.Map(physicalAddress, arguments.Offset, size);
+                    }
+                    else
+                    {
+                        string message = string.Format(mapErrorMsg, arguments.Offset, size);
+
+                        Logger.PrintWarning(LogClass.ServiceNv, message);
+
+                        result = NvInternalResult.InvalidInput;
+                    }
+                }
+                else
+                {
+                    arguments.Offset = addressSpaceContext.Vmm.Map(physicalAddress, size);
+                }
+
+                if (arguments.Offset < 0)
+                {
+                    arguments.Offset = 0;
+
+                    Logger.PrintWarning(LogClass.ServiceNv, $"Failed to map size 0x{size:x16}!");
+
+                    result = NvInternalResult.InvalidInput;
+                }
+                else
+                {
+                    addressSpaceContext.AddMap(arguments.Offset, size, physicalAddress, virtualAddressAllocated);
+                }
+            }
+
+            return result;
+        }
+
+        private NvInternalResult GetVaRegions(ref GetVaRegionsArguments arguments)
+        {
+            Logger.PrintStub(LogClass.ServiceNv);
+
+            return NvInternalResult.Success;
+        }
+
+        private NvInternalResult InitializeEx(ref InitializeExArguments arguments)
+        {
+            Logger.PrintStub(LogClass.ServiceNv);
+
+            return NvInternalResult.Success;
+        }
+
+        private NvInternalResult Remap(Span<RemapArguments> arguments)
+        {
+            for (int index = 0; index < arguments.Length; index++)
+            {
+                NvGpuVmm vmm = GetAddressSpaceContext(Owner).Vmm;
+
+                NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, arguments[index].NvMapHandle, true);
+
+                if (map == null)
+                {
+                    Logger.PrintWarning(LogClass.ServiceNv, $"Invalid NvMap handle 0x{arguments[index].NvMapHandle:x8}!");
+
+                    return NvInternalResult.InvalidInput;
+                }
+
+                long result = vmm.Map(map.Address, (long)arguments[index].Offset << 16,
+                                                   (long)arguments[index].Pages << 16);
+
+                if (result < 0)
+                {
+                    Logger.PrintWarning(LogClass.ServiceNv,
+                        $"Page 0x{arguments[index].Offset:x16} size 0x{arguments[index].Pages:x16} not allocated!");
+
+                    return NvInternalResult.InvalidInput;
+                }
+            }
+
+            return NvInternalResult.Success;
+        }
+
+        public override void Close() { }
+
+        public static AddressSpaceContext GetAddressSpaceContext(KProcess process)
+        {
+            return _addressSpaceContextRegistry.GetOrAdd(process, (key) => new AddressSpaceContext(process));
+        }
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvGpuAS/Types/NvGpuASCtx.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceContext.cs
similarity index 91%
rename from Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvGpuAS/Types/NvGpuASCtx.cs
rename to Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceContext.cs
index 315fe353e..5c2830000 100644
--- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvGpuAS/Types/NvGpuASCtx.cs
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceContext.cs
@@ -1,16 +1,20 @@
+using ARMeilleure.Memory;
 using Ryujinx.Graphics.Memory;
+using Ryujinx.HLE.HOS.Kernel.Process;
+using System;
 using System.Collections.Generic;
+using System.Text;
 
-namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvGpuAS
+namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types
 {
-    class NvGpuASCtx
+    class AddressSpaceContext
     {
         public NvGpuVmm Vmm { get; private set; }
 
         private class Range
         {
-            public ulong Start  { get; private set; }
-            public ulong End    { get; private set; }
+            public ulong Start { get; private set; }
+            public ulong End   { get; private set; }
 
             public Range(long position, long size)
             {
@@ -22,7 +26,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvGpuAS
         private class MappedMemory : Range
         {
             public long PhysicalAddress { get; private set; }
-            public bool VaAllocated  { get; private set; }
+            public bool VaAllocated     { get; private set; }
 
             public MappedMemory(
                 long position,
@@ -38,9 +42,9 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvGpuAS
         private SortedList<long, Range> _maps;
         private SortedList<long, Range> _reservations;
 
-        public NvGpuASCtx(ServiceCtx context)
+        public AddressSpaceContext(KProcess process)
         {
-            Vmm = new NvGpuVmm(context.Memory);
+            Vmm = new NvGpuVmm(process.CpuMemory);
 
             _maps         = new SortedList<long, Range>();
             _reservations = new SortedList<long, Range>();
@@ -197,4 +201,4 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvGpuAS
             return ltRg;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceFlags.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceFlags.cs
new file mode 100644
index 000000000..611cf78bf
--- /dev/null
+++ b/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/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AllocSpaceArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AllocSpaceArguments.cs
new file mode 100644
index 000000000..73f746e2d
--- /dev/null
+++ b/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 long              Offset;
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/BindChannelArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/BindChannelArguments.cs
new file mode 100644
index 000000000..9c6568a3f
--- /dev/null
+++ b/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/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/FreeSpaceArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/FreeSpaceArguments.cs
new file mode 100644
index 000000000..a853974b4
--- /dev/null
+++ b/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 long Offset;
+        public uint Pages;
+        public uint PageSize;
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/GetVaRegionsArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/GetVaRegionsArguments.cs
new file mode 100644
index 000000000..b3a9cf26c
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/GetVaRegionsArguments.cs
@@ -0,0 +1,23 @@
+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 VaRegion Region0;
+        public VaRegion Region1;
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/InitializeExArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/InitializeExArguments.cs
new file mode 100644
index 000000000..882bda591
--- /dev/null
+++ b/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/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/MapBufferExArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/MapBufferExArguments.cs
new file mode 100644
index 000000000..02e058df6
--- /dev/null
+++ b/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 long              BufferOffset;
+        public long              MappingSize;
+        public long              Offset;
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/RemapArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/RemapArguments.cs
new file mode 100644
index 000000000..0cf324b45
--- /dev/null
+++ b/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 int    Padding;
+        public uint   Offset;
+        public uint   Pages;
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/UnmapBufferArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/UnmapBufferArguments.cs
new file mode 100644
index 000000000..1ef880afe
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/UnmapBufferArguments.cs
@@ -0,0 +1,7 @@
+namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types
+{
+    struct UnmapBufferArguments
+    {
+        public long Offset;
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostChannelDeviceFile.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostChannelDeviceFile.cs
new file mode 100644
index 000000000..80a7ce800
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostChannelDeviceFile.cs
@@ -0,0 +1,347 @@
+using ARMeilleure.Memory;
+using Ryujinx.Common.Logging;
+using Ryujinx.Graphics;
+using Ryujinx.Graphics.Memory;
+using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu;
+using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types;
+using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap;
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel
+{
+    class NvHostChannelDeviceFile : NvDeviceFile
+    {
+        private uint           _timeout;
+        private uint           _submitTimeout;
+        private uint           _timeslice;
+        private NvGpu          _gpu;
+        private MemoryManager  _memory;
+
+        public NvHostChannelDeviceFile(ServiceCtx context) : base(context)
+        {
+            _gpu           = context.Device.Gpu;
+            _memory        = context.Memory;
+            _timeout       = 3000;
+            _submitTimeout = 0;
+            _timeslice     = 0;
+        }
+
+        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)
+        {
+            int                 headerSize           = Unsafe.SizeOf<SubmitArguments>();
+            SubmitArguments     submitHeader         = MemoryMarshal.Cast<byte, SubmitArguments>(arguments)[0];
+            Span<CommandBuffer> commandBufferEntries = MemoryMarshal.Cast<byte, CommandBuffer>(arguments.Slice(headerSize)).Slice(0, submitHeader.CmdBufsCount);
+            NvGpuVmm            vmm                  = NvHostAsGpuDeviceFile.GetAddressSpaceContext(Owner).Vmm;
+
+            foreach (CommandBuffer commandBufferEntry in commandBufferEntries)
+            {
+                NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, commandBufferEntry.MemoryId);
+
+                int[] commandBufferData = new int[commandBufferEntry.WordsCount];
+
+                for (int offset = 0; offset < commandBufferData.Length; offset++)
+                {
+                    commandBufferData[offset] = _memory.ReadInt32(map.Address + commandBufferEntry.Offset + offset * 4);
+                }
+
+                _gpu.PushCommandBuffer(vmm, commandBufferData);
+            }
+
+            return NvInternalResult.Success;
+        }
+
+        private NvInternalResult GetSyncpoint(ref GetParameterArguments arguments)
+        {
+            arguments.Value = 0;
+
+            Logger.PrintStub(LogClass.ServiceNv);
+
+            return NvInternalResult.Success;
+        }
+
+        private NvInternalResult GetWaitBase(ref GetParameterArguments arguments)
+        {
+            arguments.Value = 0;
+
+            Logger.PrintStub(LogClass.ServiceNv);
+
+            return NvInternalResult.Success;
+        }
+
+        private NvInternalResult SetSubmitTimeout(ref uint submitTimeout)
+        {
+            _submitTimeout = submitTimeout;
+
+            Logger.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);
+            NvGpuVmm                  vmm                  = NvHostAsGpuDeviceFile.GetAddressSpaceContext(Owner).Vmm;
+
+            foreach (ref CommandBufferHandle commandBufferEntry in commandBufferEntries)
+            {
+                NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, commandBufferEntry.MapHandle);
+
+                if (map == null)
+                {
+                    Logger.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{commandBufferEntry.MapHandle:x8}!");
+
+                    return NvInternalResult.InvalidInput;
+                }
+
+                lock (map)
+                {
+                    if (map.DmaMapAddress == 0)
+                    {
+                        map.DmaMapAddress = vmm.MapLow(map.Address, map.Size);
+                    }
+
+                    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);
+            NvGpuVmm                  vmm                  = NvHostAsGpuDeviceFile.GetAddressSpaceContext(Owner).Vmm;
+
+            foreach (ref CommandBufferHandle commandBufferEntry in commandBufferEntries)
+            {
+                NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, commandBufferEntry.MapHandle);
+
+                if (map == null)
+                {
+                    Logger.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{commandBufferEntry.MapHandle:x8}!");
+
+                    return NvInternalResult.InvalidInput;
+                }
+
+                lock (map)
+                {
+                    if (map.DmaMapAddress != 0)
+                    {
+                        vmm.Free(map.DmaMapAddress, map.Size);
+
+                        map.DmaMapAddress = 0;
+                    }
+                }
+            }
+
+            return NvInternalResult.Success;
+        }
+
+        private NvInternalResult SetNvMapFd(ref int nvMapFd)
+        {
+            Logger.PrintStub(LogClass.ServiceNv);
+
+            return NvInternalResult.Success;
+        }
+
+        private NvInternalResult SetTimeout(ref uint timeout)
+        {
+            _timeout = timeout;
+
+            Logger.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<long>            gpfifoEntries          = MemoryMarshal.Cast<byte, long>(arguments.Slice(headerSize)).Slice(0, gpfifoSubmissionHeader.NumEntries);
+
+            return SubmitGpfifo(ref gpfifoSubmissionHeader, gpfifoEntries);
+        }
+
+        private NvInternalResult AllocObjCtx(ref AllocObjCtxArguments arguments)
+        {
+            Logger.PrintStub(LogClass.ServiceNv);
+
+            return NvInternalResult.Success;
+        }
+
+        private NvInternalResult ZcullBind(ref ZcullBindArguments arguments)
+        {
+            Logger.PrintStub(LogClass.ServiceNv);
+
+            return NvInternalResult.Success;
+        }
+
+        private NvInternalResult SetErrorNotifier(ref SetErrorNotifierArguments arguments)
+        {
+            Logger.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.PrintStub(LogClass.ServiceNv);
+
+            // TODO: disable and preempt channel when GPU scheduler will be implemented.
+
+            return NvInternalResult.Success;
+        }
+
+        private NvInternalResult AllocGpfifoEx(ref AllocGpfifoExArguments arguments)
+        {
+            Logger.PrintStub(LogClass.ServiceNv);
+
+            return NvInternalResult.Success;
+        }
+
+        private NvInternalResult AllocGpfifoEx2(ref AllocGpfifoExArguments arguments)
+        {
+            Logger.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.PrintStub(LogClass.ServiceNv);
+
+            // TODO: disable and preempt channel when GPU scheduler will be implemented.
+
+            return NvInternalResult.Success;
+        }
+
+        private NvInternalResult SetUserData(ref ulong userData)
+        {
+            Logger.PrintStub(LogClass.ServiceNv);
+
+            return NvInternalResult.Success;
+        }
+
+        protected NvInternalResult SubmitGpfifo(ref SubmitGpfifoArguments header, Span<long> entries)
+        {
+            NvGpuVmm vmm = NvHostAsGpuDeviceFile.GetAddressSpaceContext(Owner).Vmm;
+
+            foreach (long entry in entries)
+            {
+                _gpu.Pusher.Push(vmm, entry);
+            }
+
+            header.Fence.Id    = 0;
+            header.Fence.Value = 0;
+
+            return NvInternalResult.Success;
+        }
+
+        public override void Close() { }
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostChannelIoctl.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostChannelIoctl.cs
deleted file mode 100644
index 0d06e7e41..000000000
--- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostChannelIoctl.cs
+++ /dev/null
@@ -1,371 +0,0 @@
-using ARMeilleure.Memory;
-using Ryujinx.Common.Logging;
-using Ryujinx.Graphics.Memory;
-using Ryujinx.HLE.HOS.Kernel.Process;
-using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvGpuAS;
-using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap;
-using System;
-using System.Collections.Concurrent;
-
-namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel
-{
-    class NvHostChannelIoctl
-    {
-        private static ConcurrentDictionary<KProcess, NvChannel> _channels;
-
-        static NvHostChannelIoctl()
-        {
-            _channels = new ConcurrentDictionary<KProcess, NvChannel>();
-        }
-
-        public static int ProcessIoctl(ServiceCtx context, int cmd)
-        {
-            switch (cmd & 0xffff)
-            {
-                case 0x0001: return Submit           (context);
-                case 0x0002: return GetSyncpoint     (context);
-                case 0x0003: return GetWaitBase      (context);
-                case 0x0007: return SetSubmitTimeout (context);
-                case 0x0009: return MapBuffer        (context);
-                case 0x000a: return UnmapBuffer      (context);
-                case 0x4714: return SetUserData      (context);
-                case 0x4801: return SetNvMap         (context);
-                case 0x4803: return SetTimeout       (context);
-                case 0x4808: return SubmitGpfifo     (context);
-                case 0x4809: return AllocObjCtx      (context);
-                case 0x480b: return ZcullBind        (context);
-                case 0x480c: return SetErrorNotifier (context);
-                case 0x480d: return SetPriority      (context);
-                case 0x481a: return AllocGpfifoEx2   (context);
-                case 0x481b: return KickoffPbWithAttr(context);
-                case 0x481d: return SetTimeslice     (context);
-            }
-
-            throw new NotImplementedException(cmd.ToString("x8"));
-        }
-
-        private static int Submit(ServiceCtx context)
-        {
-            long inputPosition  = context.Request.GetBufferType0x21().Position;
-            long outputPosition = context.Request.GetBufferType0x22().Position;
-
-            NvHostChannelSubmit args = MemoryHelper.Read<NvHostChannelSubmit>(context.Memory, inputPosition);
-
-            NvGpuVmm vmm = NvGpuASIoctl.GetASCtx(context).Vmm;
-
-            for (int index = 0; index < args.CmdBufsCount; index++)
-            {
-                long cmdBufOffset = inputPosition + 0x10 + index * 0xc;
-
-                NvHostChannelCmdBuf cmdBuf = MemoryHelper.Read<NvHostChannelCmdBuf>(context.Memory, cmdBufOffset);
-
-                NvMapHandle map = NvMapIoctl.GetNvMap(context, cmdBuf.MemoryId);
-
-                int[] cmdBufData = new int[cmdBuf.WordsCount];
-
-                for (int offset = 0; offset < cmdBufData.Length; offset++)
-                {
-                    cmdBufData[offset] = context.Memory.ReadInt32(map.Address + cmdBuf.Offset + offset * 4);
-                }
-
-                context.Device.Gpu.PushCommandBuffer(vmm, cmdBufData);
-            }
-
-            // TODO: Relocation, waitchecks, etc.
-
-            return NvResult.Success;
-        }
-
-        private static int GetSyncpoint(ServiceCtx context)
-        {
-            // TODO
-            long inputPosition  = context.Request.GetBufferType0x21().Position;
-            long outputPosition = context.Request.GetBufferType0x22().Position;
-
-            NvHostChannelGetParamArg args = MemoryHelper.Read<NvHostChannelGetParamArg>(context.Memory, inputPosition);
-
-            args.Value = 0;
-
-            MemoryHelper.Write(context.Memory, outputPosition, args);
-
-            return NvResult.Success;
-        }
-
-        private static int GetWaitBase(ServiceCtx context)
-        {
-            long inputPosition  = context.Request.GetBufferType0x21().Position;
-            long outputPosition = context.Request.GetBufferType0x22().Position;
-
-            NvHostChannelGetParamArg args = MemoryHelper.Read<NvHostChannelGetParamArg>(context.Memory, inputPosition);
-
-            args.Value = 0;
-
-            MemoryHelper.Write(context.Memory, outputPosition, args);
-
-            return NvResult.Success;
-        }
-
-        private static int SetSubmitTimeout(ServiceCtx context)
-        {
-            long inputPosition = context.Request.GetBufferType0x21().Position;
-
-            GetChannel(context).SubmitTimeout = context.Memory.ReadInt32(inputPosition);
-
-            // TODO: Handle the timeout in the submit method.
-
-            Logger.PrintStub(LogClass.ServiceNv);
-
-            return NvResult.Success;
-        }
-
-        private static int MapBuffer(ServiceCtx context)
-        {
-            long inputPosition  = context.Request.GetBufferType0x21().Position;
-            long outputPosition = context.Request.GetBufferType0x22().Position;
-
-            NvHostChannelMapBuffer args = MemoryHelper.Read<NvHostChannelMapBuffer>(context.Memory, inputPosition);
-
-            NvGpuVmm vmm = NvGpuASIoctl.GetASCtx(context).Vmm;
-
-            for (int index = 0; index < args.NumEntries; index++)
-            {
-                int handle = context.Memory.ReadInt32(inputPosition + 0xc + index * 8);
-
-                NvMapHandle map = NvMapIoctl.GetNvMap(context, handle);
-
-                if (map == null)
-                {
-                    Logger.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{handle:x8}!");
-
-                    return NvResult.InvalidInput;
-                }
-
-                lock (map)
-                {
-                    if (map.DmaMapAddress == 0)
-                    {
-                        map.DmaMapAddress = vmm.MapLow(map.Address, map.Size);
-                    }
-
-                    context.Memory.WriteInt32(outputPosition + 0xc + 4 + index * 8, (int)map.DmaMapAddress);
-                }
-            }
-
-            return NvResult.Success;
-        }
-
-        private static int UnmapBuffer(ServiceCtx context)
-        {
-            long inputPosition  = context.Request.GetBufferType0x21().Position;
-
-            NvHostChannelMapBuffer args = MemoryHelper.Read<NvHostChannelMapBuffer>(context.Memory, inputPosition);
-
-            NvGpuVmm vmm = NvGpuASIoctl.GetASCtx(context).Vmm;
-
-            for (int index = 0; index < args.NumEntries; index++)
-            {
-                int handle = context.Memory.ReadInt32(inputPosition + 0xc + index * 8);
-
-                NvMapHandle map = NvMapIoctl.GetNvMap(context, handle);
-
-                if (map == null)
-                {
-                    Logger.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{handle:x8}!");
-
-                    return NvResult.InvalidInput;
-                }
-
-                lock (map)
-                {
-                    if (map.DmaMapAddress != 0)
-                    {
-                        vmm.Free(map.DmaMapAddress, map.Size);
-
-                        map.DmaMapAddress = 0;
-                    }
-                }
-            }
-
-            return NvResult.Success;
-        }
-
-        private static int SetUserData(ServiceCtx context)
-        {
-            long inputPosition  = context.Request.GetBufferType0x21().Position;
-            long outputPosition = context.Request.GetBufferType0x22().Position;
-
-            Logger.PrintStub(LogClass.ServiceNv);
-
-            return NvResult.Success;
-        }
-
-        private static int SetNvMap(ServiceCtx context)
-        {
-            long inputPosition  = context.Request.GetBufferType0x21().Position;
-            long outputPosition = context.Request.GetBufferType0x22().Position;
-
-            Logger.PrintStub(LogClass.ServiceNv);
-
-            return NvResult.Success;
-        }
-
-        private static int SetTimeout(ServiceCtx context)
-        {
-            long inputPosition = context.Request.GetBufferType0x21().Position;
-
-            GetChannel(context).Timeout = context.Memory.ReadInt32(inputPosition);
-
-            Logger.PrintStub(LogClass.ServiceNv);
-
-            return NvResult.Success;
-        }
-
-        private static int SubmitGpfifo(ServiceCtx context)
-        {
-            long inputPosition  = context.Request.GetBufferType0x21().Position;
-            long outputPosition = context.Request.GetBufferType0x22().Position;
-
-            NvHostChannelSubmitGpfifo args = MemoryHelper.Read<NvHostChannelSubmitGpfifo>(context.Memory, inputPosition);
-
-            NvGpuVmm vmm = NvGpuASIoctl.GetASCtx(context).Vmm;
-
-            for (int index = 0; index < args.NumEntries; index++)
-            {
-                long gpfifo = context.Memory.ReadInt64(inputPosition + 0x18 + index * 8);
-
-                PushGpfifo(context, vmm, gpfifo);
-            }
-
-            args.SyncptId    = 0;
-            args.SyncptValue = 0;
-
-            MemoryHelper.Write(context.Memory, outputPosition, args);
-
-            return NvResult.Success;
-        }
-
-        private static int AllocObjCtx(ServiceCtx context)
-        {
-            long inputPosition  = context.Request.GetBufferType0x21().Position;
-            long outputPosition = context.Request.GetBufferType0x22().Position;
-
-            Logger.PrintStub(LogClass.ServiceNv);
-
-            return NvResult.Success;
-        }
-
-        private static int ZcullBind(ServiceCtx context)
-        {
-            long inputPosition  = context.Request.GetBufferType0x21().Position;
-            long outputPosition = context.Request.GetBufferType0x22().Position;
-
-            Logger.PrintStub(LogClass.ServiceNv);
-
-            return NvResult.Success;
-        }
-
-        private static int SetErrorNotifier(ServiceCtx context)
-        {
-            long inputPosition  = context.Request.GetBufferType0x21().Position;
-            long outputPosition = context.Request.GetBufferType0x22().Position;
-
-            Logger.PrintStub(LogClass.ServiceNv);
-
-            return NvResult.Success;
-        }
-
-        private static int SetPriority(ServiceCtx context)
-        {
-            long inputPosition = context.Request.GetBufferType0x21().Position;
-
-            switch ((NvChannelPriority)context.Memory.ReadInt32(inputPosition))
-            {
-                case NvChannelPriority.Low:
-                    GetChannel(context).Timeslice = 1300; // Timeslice low priority in micro-seconds
-                    break;
-                case NvChannelPriority.Medium:
-                    GetChannel(context).Timeslice = 2600; // Timeslice medium priority in micro-seconds
-                    break;
-                case NvChannelPriority.High:
-                    GetChannel(context).Timeslice = 5200; // Timeslice high priority in micro-seconds
-                    break;
-                default:
-                    return NvResult.InvalidInput;
-            }
-
-            Logger.PrintStub(LogClass.ServiceNv);
-
-            // TODO: disable and preempt channel when GPU scheduler will be implemented.
-
-            return NvResult.Success;
-        }
-
-        private static int AllocGpfifoEx2(ServiceCtx context)
-        {
-            long inputPosition  = context.Request.GetBufferType0x21().Position;
-            long outputPosition = context.Request.GetBufferType0x22().Position;
-
-            Logger.PrintStub(LogClass.ServiceNv);
-
-            return NvResult.Success;
-        }
-
-        private static int KickoffPbWithAttr(ServiceCtx context)
-        {
-            long inputPosition  = context.Request.GetBufferType0x21().Position;
-            long outputPosition = context.Request.GetBufferType0x22().Position;
-
-            NvHostChannelSubmitGpfifo args = MemoryHelper.Read<NvHostChannelSubmitGpfifo>(context.Memory, inputPosition);
-
-            NvGpuVmm vmm = NvGpuASIoctl.GetASCtx(context).Vmm;
-
-            for (int index = 0; index < args.NumEntries; index++)
-            {
-                long gpfifo = context.Memory.ReadInt64(args.Address + index * 8);
-
-                PushGpfifo(context, vmm, gpfifo);
-            }
-
-            args.SyncptId    = 0;
-            args.SyncptValue = 0;
-
-            MemoryHelper.Write(context.Memory, outputPosition, args);
-
-            return NvResult.Success;
-        }
-
-        private static int SetTimeslice(ServiceCtx context)
-        {
-            long inputPosition = context.Request.GetBufferType0x21().Position;
-            int  timeslice     = context.Memory.ReadInt32(inputPosition);
-
-            if (timeslice < 1000 || timeslice > 50000)
-            {
-                return NvResult.InvalidInput;
-            }
-
-            GetChannel(context).Timeslice = timeslice; // in micro-seconds
-
-            Logger.PrintStub(LogClass.ServiceNv);
-
-            // TODO: disable and preempt channel when GPU scheduler will be implemented.
-
-            return NvResult.Success;
-        }
-
-        private static void PushGpfifo(ServiceCtx context, NvGpuVmm vmm, long gpfifo)
-        {
-            context.Device.Gpu.Pusher.Push(vmm, gpfifo);
-        }
-
-        public static NvChannel GetChannel(ServiceCtx context)
-        {
-            return _channels.GetOrAdd(context.Process, (key) => new NvChannel());
-        }
-
-        public static void UnloadProcess(KProcess process)
-        {
-            _channels.TryRemove(process, out _);
-        }
-    }
-}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostGpuDeviceFile.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostGpuDeviceFile.cs
new file mode 100644
index 000000000..582ba50e9
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostGpuDeviceFile.cs
@@ -0,0 +1,78 @@
+using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types;
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel
+{
+    internal class NvHostGpuDeviceFile : NvHostChannelDeviceFile
+    {
+        private KEvent _smExceptionBptIntReportEvent;
+        private KEvent _smExceptionBptPauseReportEvent;
+        private KEvent _errorNotifierEvent;
+
+        public NvHostGpuDeviceFile(ServiceCtx context) : base(context)
+        {
+            _smExceptionBptIntReportEvent   = new KEvent(context.Device.System);
+            _smExceptionBptPauseReportEvent = new KEvent(context.Device.System);
+            _errorNotifierEvent             = new KEvent(context.Device.System);
+        }
+
+        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, long>(SubmitGpfifoEx, arguments, inlineInBuffer);
+                        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 = _smExceptionBptIntReportEvent;
+                    break;
+                case 0x2:
+                    targetEvent = _smExceptionBptPauseReportEvent;
+                    break;
+                case 0x3:
+                    targetEvent = _errorNotifierEvent;
+                    break;
+            }
+
+            if (targetEvent != null)
+            {
+                if (Owner.HandleTable.GenerateHandle(targetEvent.ReadableEvent, out eventHandle) != KernelResult.Success)
+                {
+                    throw new InvalidOperationException("Out of handles!");
+                }
+            }
+            else
+            {
+                eventHandle = 0;
+
+                return NvInternalResult.InvalidInput;
+            }
+
+            return NvInternalResult.Success;
+        }
+
+        private NvInternalResult SubmitGpfifoEx(ref SubmitGpfifoArguments arguments, Span<long> inlineData)
+        {
+            return SubmitGpfifo(ref arguments, inlineData);
+        }
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/AllocGpfifoExArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/AllocGpfifoExArguments.cs
new file mode 100644
index 000000000..8e5a15235
--- /dev/null
+++ b/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/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/AllocObjCtxArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/AllocObjCtxArguments.cs
new file mode 100644
index 000000000..fae91622c
--- /dev/null
+++ b/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/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/GetParameterArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/GetParameterArguments.cs
new file mode 100644
index 000000000..425e665f4
--- /dev/null
+++ b/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/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/MapCommandBufferArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/MapCommandBufferArguments.cs
new file mode 100644
index 000000000..6a7e3da80
--- /dev/null
+++ b/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/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvChannelPriority.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvChannelPriority.cs
index 148a640b5..4112a9fcc 100644
--- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvChannelPriority.cs
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvChannelPriority.cs
@@ -1,6 +1,6 @@
 namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel
 {
-    enum NvChannelPriority
+    enum NvChannelPriority : uint
     {
         Low    = 50,
         Medium = 100,
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvHostChannelCmdBuf.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvHostChannelCmdBuf.cs
deleted file mode 100644
index 0308912b8..000000000
--- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvHostChannelCmdBuf.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using System.Runtime.InteropServices;
-
-namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel
-{
-    [StructLayout(LayoutKind.Sequential, Size = 8, Pack = 4)]
-    struct NvHostChannelCmdBuf
-    {
-        public int MemoryId;
-        public int Offset;
-        public int WordsCount;
-    }
-}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvHostChannelGetParamArg.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvHostChannelGetParamArg.cs
deleted file mode 100644
index 729464846..000000000
--- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvHostChannelGetParamArg.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using System.Runtime.InteropServices;
-
-namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel
-{
-    [StructLayout(LayoutKind.Sequential, Size = 8, Pack = 4)]
-    struct NvHostChannelGetParamArg
-    {
-        public int Param;
-        public int Value;
-    }
-}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvHostChannelMapBuffer.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvHostChannelMapBuffer.cs
deleted file mode 100644
index f516588e8..000000000
--- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvHostChannelMapBuffer.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using System.Runtime.InteropServices;
-
-namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel
-{
-    [StructLayout(LayoutKind.Sequential, Size = 0xc, Pack = 4)]
-    struct NvHostChannelMapBuffer
-    {
-        public int  NumEntries;
-        public int  DataAddress; // Ignored by the driver.
-        public bool AttachHostChDas;
-    }
-}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvHostChannelSubmit.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvHostChannelSubmit.cs
deleted file mode 100644
index ef2f24e78..000000000
--- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvHostChannelSubmit.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using System.Runtime.InteropServices;
-
-namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel
-{
-    [StructLayout(LayoutKind.Sequential, Size = 8, Pack = 4)]
-    struct NvHostChannelSubmit
-    {
-        public int CmdBufsCount;
-        public int RelocsCount;
-        public int SyncptIncrsCount;
-        public int WaitchecksCount;
-    }
-}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvHostChannelSubmitGpfifo.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvHostChannelSubmitGpfifo.cs
deleted file mode 100644
index e8cb5f0f5..000000000
--- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvHostChannelSubmitGpfifo.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel
-{
-    struct NvHostChannelSubmitGpfifo
-    {
-        public long Address;
-        public int  NumEntries;
-        public int  Flags;
-        public int  SyncptId;
-        public int  SyncptValue;
-    }
-}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SetErrorNotifierArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SetErrorNotifierArguments.cs
new file mode 100644
index 000000000..1aba53ca6
--- /dev/null
+++ b/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/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitArguments.cs
new file mode 100644
index 000000000..bb2fd1cc0
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitArguments.cs
@@ -0,0 +1,21 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types
+{
+    [StructLayout(LayoutKind.Sequential)]
+    struct CommandBuffer
+    {
+        public int MemoryId;
+        public int Offset;
+        public int WordsCount;
+    }
+
+    [StructLayout(LayoutKind.Sequential)]
+    struct SubmitArguments
+    {
+        public int CmdBufsCount;
+        public int RelocsCount;
+        public int SyncptIncrsCount;
+        public int WaitchecksCount;
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitGpfifoArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitGpfifoArguments.cs
new file mode 100644
index 000000000..18cdde06b
--- /dev/null
+++ b/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 int     Flags;
+        public NvFence Fence;
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/ZcullBindArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/ZcullBindArguments.cs
new file mode 100644
index 000000000..19a997f43
--- /dev/null
+++ b/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/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/NvHostCtrlDeviceFile.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/NvHostCtrlDeviceFile.cs
new file mode 100644
index 000000000..e740350ed
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/NvHostCtrlDeviceFile.cs
@@ -0,0 +1,401 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl.Types;
+using Ryujinx.HLE.HOS.Services.Nv.Types;
+using Ryujinx.HLE.HOS.Services.Settings;
+
+using System;
+using System.Text;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl
+{
+    internal class NvHostCtrlDeviceFile : NvDeviceFile
+    {
+        private const int EventsCount = 64;
+
+        private bool          _isProductionMode;
+        private NvHostSyncpt  _syncpt;
+        private NvHostEvent[] _events;
+        private KEvent        _dummyEvent;
+
+        public NvHostCtrlDeviceFile(ServiceCtx context) : base(context)
+        {
+            if (NxSettings.Settings.TryGetValue("nv!rmos_set_production_mode", out object productionModeSetting))
+            {
+                _isProductionMode = ((string)productionModeSetting) != "0"; // Default value is ""
+            }
+            else
+            {
+                _isProductionMode = true;
+            }
+
+            _syncpt     = new NvHostSyncpt();
+            _events     = new NvHostEvent[EventsCount];
+            _dummyEvent = new KEvent(context.Device.System);
+        }
+
+        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 0x1d:
+                        result = CallIoctlMethod<EventWaitArguments>(EventWait, arguments);
+                        break;
+                    case 0x1e:
+                        result = CallIoctlMethod<EventWaitArguments>(EventWaitAsync, arguments);
+                        break;
+                    case 0x1f:
+                        result = CallIoctlMethod<uint>(EventRegister, arguments);
+                        break;
+                }
+            }
+
+            return result;
+        }
+
+        public override NvInternalResult QueryEvent(out int eventHandle, uint eventId)
+        {
+            // TODO: implement SyncPts <=> KEvent logic accurately. For now we return a dummy event.
+            KEvent targetEvent = _dummyEvent;
+
+            if (targetEvent != null)
+            {
+                if (Owner.HandleTable.GenerateHandle(targetEvent.ReadableEvent, out eventHandle) != KernelResult.Success)
+                {
+                    throw new InvalidOperationException("Out of handles!");
+                }
+            }
+            else
+            {
+                eventHandle = 0;
+
+                return NvInternalResult.InvalidInput;
+            }
+
+            return NvInternalResult.Success;
+        }
+
+        private NvInternalResult SyncptRead(ref NvFence arguments)
+        {
+            return SyncptReadMinOrMax(ref arguments, max: false);
+        }
+
+        private NvInternalResult SyncptIncr(ref uint id)
+        {
+            if (id >= NvHostSyncpt.SyncptsCount)
+            {
+                return NvInternalResult.InvalidInput;
+            }
+
+            _syncpt.Increment((int)id);
+
+            return NvInternalResult.Success;
+        }
+
+        private NvInternalResult SyncptWait(ref SyncptWaitArguments arguments)
+        {
+            return SyncptWait(ref arguments, out _);
+        }
+
+        private NvInternalResult SyncptWaitEx(ref SyncptWaitExArguments arguments)
+        {
+            return SyncptWait(ref arguments.Input, out arguments.Value);
+        }
+
+        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.PrintError(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.PrintDebug(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, async: false);
+        }
+
+        private NvInternalResult EventWaitAsync(ref EventWaitArguments arguments)
+        {
+            return EventWait(ref arguments, async: true);
+        }
+
+        private NvInternalResult EventRegister(ref uint userEventId)
+        {
+            Logger.PrintStub(LogClass.ServiceNv);
+
+            return NvInternalResult.Success;
+        }
+
+        private NvInternalResult SyncptReadMinOrMax(ref NvFence arguments, bool max)
+        {
+            if (arguments.Id >= NvHostSyncpt.SyncptsCount)
+            {
+                return NvInternalResult.InvalidInput;
+            }
+
+            if (max)
+            {
+                arguments.Value = (uint)_syncpt.GetMax((int)arguments.Id);
+            }
+            else
+            {
+                arguments.Value = (uint)_syncpt.GetMin((int)arguments.Id);
+            }
+
+            return NvInternalResult.Success;
+        }
+
+        private NvInternalResult SyncptWait(ref SyncptWaitArguments arguments, out int value)
+        {
+            if (arguments.Id >= NvHostSyncpt.SyncptsCount)
+            {
+                value = 0;
+
+                return NvInternalResult.InvalidInput;
+            }
+
+            NvInternalResult result;
+
+            if (_syncpt.MinCompare((int)arguments.Id, arguments.Thresh))
+            {
+                result = NvInternalResult.Success;
+            }
+            else if (arguments.Timeout == 0)
+            {
+                result = NvInternalResult.TryAgain;
+            }
+            else
+            {
+                Logger.PrintDebug(LogClass.ServiceNv, $"Waiting syncpt with timeout of {arguments.Timeout}ms...");
+
+                using (ManualResetEvent waitEvent = new ManualResetEvent(false))
+                {
+                    _syncpt.AddWaiter(arguments.Thresh, waitEvent);
+
+                    // Note: Negative (> INT_MAX) timeouts aren't valid on .NET,
+                    // in this case we just use the maximum timeout possible.
+                    int timeout = arguments.Timeout;
+
+                    if (timeout < -1)
+                    {
+                        timeout = int.MaxValue;
+                    }
+
+                    if (timeout == -1)
+                    {
+                        waitEvent.WaitOne();
+
+                        result = NvInternalResult.Success;
+                    }
+                    else if (waitEvent.WaitOne(timeout))
+                    {
+                        result = NvInternalResult.Success;
+                    }
+                    else
+                    {
+                        result = NvInternalResult.TimedOut;
+                    }
+                }
+
+                Logger.PrintDebug(LogClass.ServiceNv, "Resuming...");
+            }
+
+            value = _syncpt.GetMin((int)arguments.Id);
+
+            return result;
+        }
+
+        private NvInternalResult EventWait(ref EventWaitArguments arguments, bool async)
+        {
+            if (arguments.Id >= NvHostSyncpt.SyncptsCount)
+            {
+                return NvInternalResult.InvalidInput;
+            }
+
+            if (_syncpt.MinCompare(arguments.Id, arguments.Thresh))
+            {
+                arguments.Value = _syncpt.GetMin(arguments.Id);
+
+                return NvInternalResult.Success;
+            }
+
+            if (!async)
+            {
+                arguments.Value = 0;
+            }
+
+            if (arguments.Timeout == 0)
+            {
+                return NvInternalResult.TryAgain;
+            }
+
+            NvHostEvent Event;
+
+            NvInternalResult result;
+
+            int eventIndex;
+
+            if (async)
+            {
+                eventIndex = arguments.Value;
+
+                if ((uint)eventIndex >= EventsCount)
+                {
+                    return NvInternalResult.InvalidInput;
+                }
+
+                Event = _events[eventIndex];
+            }
+            else
+            {
+                Event = GetFreeEvent(arguments.Id, out eventIndex);
+            }
+
+            if (Event != null &&
+               (Event.State == NvHostEventState.Registered ||
+                Event.State == NvHostEventState.Free))
+            {
+                Event.Id     = arguments.Id;
+                Event.Thresh = arguments.Thresh;
+
+                Event.State = NvHostEventState.Waiting;
+
+                if (!async)
+                {
+                    arguments.Value = ((arguments.Id & 0xfff) << 16) | 0x10000000;
+                }
+                else
+                {
+                    arguments.Value = arguments.Id << 4;
+                }
+
+                arguments.Value |= eventIndex;
+
+                result = NvInternalResult.TryAgain;
+            }
+            else
+            {
+                result = NvInternalResult.InvalidInput;
+            }
+
+            return result;
+        }
+
+        private NvHostEvent GetFreeEvent(int id, out int eventIndex)
+        {
+            eventIndex = EventsCount;
+
+            int nullIndex = EventsCount;
+
+            for (int index = 0; index < EventsCount; index++)
+            {
+                NvHostEvent Event = _events[index];
+
+                if (Event != null)
+                {
+                    if (Event.State == NvHostEventState.Registered ||
+                        Event.State == NvHostEventState.Free)
+                    {
+                        eventIndex = index;
+
+                        if (Event.Id == id)
+                        {
+                            return Event;
+                        }
+                    }
+                }
+                else if (nullIndex == EventsCount)
+                {
+                    nullIndex = index;
+                }
+            }
+
+            if (nullIndex < EventsCount)
+            {
+                eventIndex = nullIndex;
+
+                return _events[nullIndex] = new NvHostEvent();
+            }
+
+            if (eventIndex < EventsCount)
+            {
+                return _events[eventIndex];
+            }
+
+            return null;
+        }
+
+        public override void Close() { }
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/NvHostCtrlIoctl.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/NvHostCtrlIoctl.cs
deleted file mode 100644
index 346e2dc76..000000000
--- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/NvHostCtrlIoctl.cs
+++ /dev/null
@@ -1,400 +0,0 @@
-using ARMeilleure.Memory;
-using Ryujinx.Common.Logging;
-using Ryujinx.HLE.HOS.Kernel.Process;
-using Ryujinx.HLE.HOS.Services.Settings;
-using System;
-using System.Collections.Concurrent;
-using System.Text;
-using System.Threading;
-
-namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl
-{
-    class NvHostCtrlIoctl
-    {
-        private static ConcurrentDictionary<KProcess, NvHostCtrlUserCtx> _userCtxs;
-
-        private static bool _isProductionMode = true;
-
-        static NvHostCtrlIoctl()
-        {
-            _userCtxs = new ConcurrentDictionary<KProcess, NvHostCtrlUserCtx>();
-
-            if (NxSettings.Settings.TryGetValue("nv!rmos_set_production_mode", out object productionModeSetting))
-            {
-                _isProductionMode = ((string)productionModeSetting) != "0"; // Default value is ""
-            }
-        }
-
-        public static int ProcessIoctl(ServiceCtx context, int cmd)
-        {
-            switch (cmd & 0xffff)
-            {
-                case 0x0014: return SyncptRead    (context);
-                case 0x0015: return SyncptIncr    (context);
-                case 0x0016: return SyncptWait    (context);
-                case 0x0019: return SyncptWaitEx  (context);
-                case 0x001a: return SyncptReadMax (context);
-                case 0x001b: return GetConfig     (context);
-                case 0x001d: return EventWait     (context);
-                case 0x001e: return EventWaitAsync(context);
-                case 0x001f: return EventRegister (context);
-            }
-
-            throw new NotImplementedException(cmd.ToString("x8"));
-        }
-
-        private static int SyncptRead(ServiceCtx context)
-        {
-            return SyncptReadMinOrMax(context, max: false);
-        }
-
-        private static int SyncptIncr(ServiceCtx context)
-        {
-            long inputPosition = context.Request.GetBufferType0x21().Position;
-
-            int id = context.Memory.ReadInt32(inputPosition);
-
-            if ((uint)id >= NvHostSyncpt.SyncptsCount)
-            {
-                return NvResult.InvalidInput;
-            }
-
-            GetUserCtx(context).Syncpt.Increment(id);
-
-            return NvResult.Success;
-        }
-
-        private static int SyncptWait(ServiceCtx context)
-        {
-            return SyncptWait(context, extended: false);
-        }
-
-        private static int SyncptWaitEx(ServiceCtx context)
-        {
-            return SyncptWait(context, extended: true);
-        }
-
-        private static int SyncptReadMax(ServiceCtx context)
-        {
-            return SyncptReadMinOrMax(context, max: true);
-        }
-
-        private static int GetConfig(ServiceCtx context)
-        {
-            if (!_isProductionMode)
-            {
-                long inputPosition  = context.Request.GetBufferType0x21().Position;
-                long outputPosition = context.Request.GetBufferType0x22().Position;
-
-                string domain = MemoryHelper.ReadAsciiString(context.Memory, inputPosition + 0, 0x41);
-                string name   = MemoryHelper.ReadAsciiString(context.Memory, inputPosition + 0x41, 0x41);
-
-                if (NxSettings.Settings.TryGetValue($"{domain}!{name}", out object nvSetting))
-                {
-                    byte[] settingBuffer = new byte[0x101];
-
-                    if (nvSetting is string stringValue)
-                    {
-                        if (stringValue.Length > 0x100)
-                        {
-                            Logger.PrintError(LogClass.ServiceNv, $"{domain}!{name} String value size is too big!");
-                        }
-                        else
-                        {
-                            settingBuffer = Encoding.ASCII.GetBytes(stringValue + "\0");
-                        }
-                    }
-
-                    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);
-                    }
-
-                    context.Memory.WriteBytes(outputPosition + 0x82, settingBuffer);
-
-                    Logger.PrintDebug(LogClass.ServiceNv, $"Got setting {domain}!{name}");
-                }
-
-                return NvResult.Success;
-            }
-
-            return NvResult.NotAvailableInProduction;
-        }
-
-        private static int EventWait(ServiceCtx context)
-        {
-            return EventWait(context, async: false);
-        }
-
-        private static int EventWaitAsync(ServiceCtx context)
-        {
-            return EventWait(context, async: true);
-        }
-
-        private static int EventRegister(ServiceCtx context)
-        {
-            long inputPosition  = context.Request.GetBufferType0x21().Position;
-            long outputPosition = context.Request.GetBufferType0x22().Position;
-
-            int eventId = context.Memory.ReadInt32(inputPosition);
-
-            Logger.PrintStub(LogClass.ServiceNv);
-
-            return NvResult.Success;
-        }
-
-        private static int SyncptReadMinOrMax(ServiceCtx context, bool max)
-        {
-            long inputPosition  = context.Request.GetBufferType0x21().Position;
-            long outputPosition = context.Request.GetBufferType0x22().Position;
-
-            NvHostCtrlSyncptRead args = MemoryHelper.Read<NvHostCtrlSyncptRead>(context.Memory, inputPosition);
-
-            if ((uint)args.Id >= NvHostSyncpt.SyncptsCount)
-            {
-                return NvResult.InvalidInput;
-            }
-
-            if (max)
-            {
-                args.Value = GetUserCtx(context).Syncpt.GetMax(args.Id);
-            }
-            else
-            {
-                args.Value = GetUserCtx(context).Syncpt.GetMin(args.Id);
-            }
-
-            MemoryHelper.Write(context.Memory, outputPosition, args);
-
-            return NvResult.Success;
-        }
-
-        private static int SyncptWait(ServiceCtx context, bool extended)
-        {
-            long inputPosition  = context.Request.GetBufferType0x21().Position;
-            long outputPosition = context.Request.GetBufferType0x22().Position;
-
-            NvHostCtrlSyncptWait args = MemoryHelper.Read<NvHostCtrlSyncptWait>(context.Memory, inputPosition);
-
-            NvHostSyncpt syncpt = GetUserCtx(context).Syncpt;
-
-            if ((uint)args.Id >= NvHostSyncpt.SyncptsCount)
-            {
-                return NvResult.InvalidInput;
-            }
-
-            int result;
-
-            if (syncpt.MinCompare(args.Id, args.Thresh))
-            {
-                result = NvResult.Success;
-            }
-            else if (args.Timeout == 0)
-            {
-                result = NvResult.TryAgain;
-            }
-            else
-            {
-                Logger.PrintDebug(LogClass.ServiceNv, "Waiting syncpt with timeout of " + args.Timeout + "ms...");
-
-                using (ManualResetEvent waitEvent = new ManualResetEvent(false))
-                {
-                    syncpt.AddWaiter(args.Thresh, waitEvent);
-
-                    // Note: Negative (> INT_MAX) timeouts aren't valid on .NET,
-                    // in this case we just use the maximum timeout possible.
-                    int timeout = args.Timeout;
-
-                    if (timeout < -1)
-                    {
-                        timeout = int.MaxValue;
-                    }
-
-                    if (timeout == -1)
-                    {
-                        waitEvent.WaitOne();
-
-                        result = NvResult.Success;
-                    }
-                    else if (waitEvent.WaitOne(timeout))
-                    {
-                        result = NvResult.Success;
-                    }
-                    else
-                    {
-                        result = NvResult.TimedOut;
-                    }
-                }
-
-                Logger.PrintDebug(LogClass.ServiceNv, "Resuming...");
-            }
-
-            if (extended)
-            {
-                context.Memory.WriteInt32(outputPosition + 0xc, syncpt.GetMin(args.Id));
-            }
-
-            return result;
-        }
-
-        private static int EventWait(ServiceCtx context, bool async)
-        {
-            long inputPosition  = context.Request.GetBufferType0x21().Position;
-            long outputPosition = context.Request.GetBufferType0x22().Position;
-
-            NvHostCtrlSyncptWaitEx args = MemoryHelper.Read<NvHostCtrlSyncptWaitEx>(context.Memory, inputPosition);
-
-            if ((uint)args.Id >= NvHostSyncpt.SyncptsCount)
-            {
-                return NvResult.InvalidInput;
-            }
-
-            void WriteArgs()
-            {
-                MemoryHelper.Write(context.Memory, outputPosition, args);
-            }
-
-            NvHostSyncpt syncpt = GetUserCtx(context).Syncpt;
-
-            if (syncpt.MinCompare(args.Id, args.Thresh))
-            {
-                args.Value = syncpt.GetMin(args.Id);
-
-                WriteArgs();
-
-                return NvResult.Success;
-            }
-
-            if (!async)
-            {
-                args.Value = 0;
-            }
-
-            if (args.Timeout == 0)
-            {
-                WriteArgs();
-
-                return NvResult.TryAgain;
-            }
-
-            NvHostEvent Event;
-
-            int result, eventIndex;
-
-            if (async)
-            {
-                eventIndex = args.Value;
-
-                if ((uint)eventIndex >= NvHostCtrlUserCtx.EventsCount)
-                {
-                    return NvResult.InvalidInput;
-                }
-
-                Event = GetUserCtx(context).Events[eventIndex];
-            }
-            else
-            {
-                Event = GetFreeEvent(context, syncpt, args.Id, out eventIndex);
-            }
-
-            if (Event != null &&
-               (Event.State == NvHostEventState.Registered ||
-                Event.State == NvHostEventState.Free))
-            {
-                Event.Id     = args.Id;
-                Event.Thresh = args.Thresh;
-
-                Event.State = NvHostEventState.Waiting;
-
-                if (!async)
-                {
-                    args.Value = ((args.Id & 0xfff) << 16) | 0x10000000;
-                }
-                else
-                {
-                    args.Value = args.Id << 4;
-                }
-
-                args.Value |= eventIndex;
-
-                result = NvResult.TryAgain;
-            }
-            else
-            {
-                result = NvResult.InvalidInput;
-            }
-
-            WriteArgs();
-
-            return result;
-        }
-
-        private static NvHostEvent GetFreeEvent(
-            ServiceCtx   context,
-            NvHostSyncpt syncpt,
-            int          id,
-            out int      eventIndex)
-        {
-            NvHostEvent[] events = GetUserCtx(context).Events;
-
-            eventIndex = NvHostCtrlUserCtx.EventsCount;
-
-            int nullIndex = NvHostCtrlUserCtx.EventsCount;
-
-            for (int index = 0; index < NvHostCtrlUserCtx.EventsCount; index++)
-            {
-                NvHostEvent Event = events[index];
-
-                if (Event != null)
-                {
-                    if (Event.State == NvHostEventState.Registered ||
-                        Event.State == NvHostEventState.Free)
-                    {
-                        eventIndex = index;
-
-                        if (Event.Id == id)
-                        {
-                            return Event;
-                        }
-                    }
-                }
-                else if (nullIndex == NvHostCtrlUserCtx.EventsCount)
-                {
-                    nullIndex = index;
-                }
-            }
-
-            if (nullIndex < NvHostCtrlUserCtx.EventsCount)
-            {
-                eventIndex = nullIndex;
-
-                return events[nullIndex] = new NvHostEvent();
-            }
-
-            if (eventIndex < NvHostCtrlUserCtx.EventsCount)
-            {
-                return events[eventIndex];
-            }
-
-            return null;
-        }
-
-        public static NvHostCtrlUserCtx GetUserCtx(ServiceCtx context)
-        {
-            return _userCtxs.GetOrAdd(context.Process, (key) => new NvHostCtrlUserCtx());
-        }
-
-        public static void UnloadProcess(KProcess process)
-        {
-            _userCtxs.TryRemove(process, out _);
-        }
-    }
-}
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/EventWaitArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/EventWaitArguments.cs
new file mode 100644
index 000000000..3f97da1f7
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/EventWaitArguments.cs
@@ -0,0 +1,13 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl.Types
+{
+    [StructLayout(LayoutKind.Sequential)]
+    struct EventWaitArguments
+    {
+        public int Id;
+        public int Thresh;
+        public int Timeout;
+        public int Value;
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/GetConfigurationArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/GetConfigurationArguments.cs
new file mode 100644
index 000000000..3ee318a37
--- /dev/null
+++ b/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/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostCtrlSyncPtRead.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostCtrlSyncPtRead.cs
deleted file mode 100644
index 8cfac5719..000000000
--- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostCtrlSyncPtRead.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl
-{
-    struct NvHostCtrlSyncptRead
-    {
-        public int Id;
-        public int Value;
-    }
-}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostCtrlSyncPtWait.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostCtrlSyncPtWait.cs
deleted file mode 100644
index 401884c46..000000000
--- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostCtrlSyncPtWait.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl
-{
-    struct NvHostCtrlSyncptWait
-    {
-        public int Id;
-        public int Thresh;
-        public int Timeout;
-    }
-}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostCtrlSyncPtWaitEx.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostCtrlSyncPtWaitEx.cs
deleted file mode 100644
index 49f573e26..000000000
--- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostCtrlSyncPtWaitEx.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl
-{
-    struct NvHostCtrlSyncptWaitEx
-    {
-        public int Id;
-        public int Thresh;
-        public int Timeout;
-        public int Value;
-    }
-}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostCtrlUserCtx.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostCtrlUserCtx.cs
deleted file mode 100644
index 0b9d85cf9..000000000
--- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostCtrlUserCtx.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl
-{
-    class NvHostCtrlUserCtx
-    {
-        public const int LocksCount  = 16;
-        public const int EventsCount = 64;
-
-        public NvHostSyncpt Syncpt { get; private set; }
-
-        public NvHostEvent[] Events { get; private set; }
-
-        public NvHostCtrlUserCtx()
-        {
-            Syncpt = new NvHostSyncpt();
-
-            Events = new NvHostEvent[EventsCount];
-        }
-    }
-}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/SyncptWaitArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/SyncptWaitArguments.cs
new file mode 100644
index 000000000..13ea89be0
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/SyncptWaitArguments.cs
@@ -0,0 +1,12 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl.Types
+{
+    [StructLayout(LayoutKind.Sequential)]
+    struct SyncptWaitArguments
+    {
+        public uint Id;
+        public int  Thresh;
+        public int  Timeout;
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/SyncptWaitExArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/SyncptWaitExArguments.cs
new file mode 100644
index 000000000..d04748ba3
--- /dev/null
+++ b/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 int                 Value;
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/NvHostCtrlGpuDeviceFile.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/NvHostCtrlGpuDeviceFile.cs
new file mode 100644
index 000000000..ac7092a6a
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/NvHostCtrlGpuDeviceFile.cs
@@ -0,0 +1,239 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.HLE.HOS.Kernel.Process;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types;
+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) : base(context)
+        {
+            _errorEvent   = new KEvent(context.Device.System);
+            _unknownEvent = new KEvent(context.Device.System);
+        }
+
+        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 (Owner.HandleTable.GenerateHandle(targetEvent.ReadableEvent, out eventHandle) != KernelResult.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.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.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/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetActiveSlotMaskArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetActiveSlotMaskArguments.cs
new file mode 100644
index 000000000..fd73be9e7
--- /dev/null
+++ b/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/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvGpuGpu/Types/NvGpuGpuGetCharacteristics.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetCharacteristicsArguments.cs
similarity index 74%
rename from Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvGpuGpu/Types/NvGpuGpuGetCharacteristics.cs
rename to Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetCharacteristicsArguments.cs
index 76aef2a78..5b44109a1 100644
--- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvGpuGpu/Types/NvGpuGpuGetCharacteristics.cs
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetCharacteristicsArguments.cs
@@ -1,9 +1,10 @@
-namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvGpuGpu
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types
 {
-    struct NvGpuGpuGetCharacteristics
+    [StructLayout(LayoutKind.Sequential)]
+    struct GpuCharacteristics
     {
-        public long BufferSize;
-        public long BufferAddress;
         public int  Arch;
         public int  Impl;
         public int  Rev;
@@ -40,4 +41,17 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvGpuGpu
         public long ChipName;
         public long GrCompbitStoreBaseHw;
     }
-}
\ No newline at end of file
+
+    struct CharacteristicsHeader
+    {
+        public long BufferSize;
+        public long BufferAddress;
+    }
+
+    [StructLayout(LayoutKind.Sequential)]
+    struct GetCharacteristicsArguments
+    {
+        public CharacteristicsHeader Header;
+        public GpuCharacteristics    Characteristics;
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetGpuTimeArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetGpuTimeArguments.cs
new file mode 100644
index 000000000..084ef71fb
--- /dev/null
+++ b/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/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetTpcMasksArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetTpcMasksArguments.cs
new file mode 100644
index 000000000..16ef2d6e1
--- /dev/null
+++ b/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/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZbcSetTableArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZbcSetTableArguments.cs
new file mode 100644
index 000000000..e21e437e8
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZbcSetTableArguments.cs
@@ -0,0 +1,10 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types
+{
+    [StructLayout(LayoutKind.Sequential)]
+    struct ZbcSetTableArguments
+    {
+        // TODO
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZcullGetCtxSizeArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZcullGetCtxSizeArguments.cs
new file mode 100644
index 000000000..1e668f867
--- /dev/null
+++ b/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/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvGpuGpu/Types/NvGpuGpuZcullGetInfo.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZcullGetInfoArguments.cs
similarity index 68%
rename from Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvGpuGpu/Types/NvGpuGpuZcullGetInfo.cs
rename to Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZcullGetInfoArguments.cs
index ab17ca8b9..d0d152a3d 100644
--- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvGpuGpu/Types/NvGpuGpuZcullGetInfo.cs
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZcullGetInfoArguments.cs
@@ -1,6 +1,9 @@
-namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvGpuGpu
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types
 {
-    struct NvGpuGpuZcullGetInfo
+    [StructLayout(LayoutKind.Sequential)]
+    struct ZcullGetInfoArguments
     {
         public int WidthAlignPixels;
         public int HeightAlignPixels;
@@ -13,4 +16,4 @@
         public int SubregionHeightAlignPixels;
         public int SubregionCount;
     }
-}
\ No newline at end of file
+}
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvInternalResult.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvInternalResult.cs
new file mode 100644
index 000000000..9345baeb5
--- /dev/null
+++ b/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/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/NvMapDeviceFile.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/NvMapDeviceFile.cs
new file mode 100644
index 000000000..2ca847a31
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/NvMapDeviceFile.cs
@@ -0,0 +1,271 @@
+using Ryujinx.Common;
+using Ryujinx.Common.Logging;
+using Ryujinx.Graphics.Memory;
+using Ryujinx.HLE.HOS.Kernel.Process;
+using System;
+using System.Collections.Concurrent;
+
+namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
+{
+    internal class NvMapDeviceFile : NvDeviceFile
+    {
+        private const int FlagNotFreedYet = 1;
+
+        private static ConcurrentDictionary<KProcess, IdDictionary> _maps = new ConcurrentDictionary<KProcess, IdDictionary>();
+
+        public NvMapDeviceFile(ServiceCtx context) : base(context)
+        {
+            IdDictionary dict = _maps.GetOrAdd(Owner, (key) => new IdDictionary());
+
+            dict.Add(0, new NvMapHandle());
+        }
+
+        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.PrintWarning(LogClass.ServiceNv, $"Invalid size 0x{arguments.Size:x8}!");
+
+                return NvInternalResult.InvalidInput;
+            }
+
+            int size = BitUtils.AlignUp(arguments.Size, NvGpuVmm.PageSize);
+
+            arguments.Handle = CreateHandleFromMap(new NvMapHandle(size));
+
+            Logger.PrintInfo(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.PrintWarning(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.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{arguments.Handle:x8}!");
+
+                return NvInternalResult.InvalidInput;
+            }
+
+            if ((arguments.Align & (arguments.Align - 1)) != 0)
+            {
+                Logger.PrintWarning(LogClass.ServiceNv, $"Invalid alignment 0x{arguments.Align:x8}!");
+
+                return NvInternalResult.InvalidInput;
+            }
+
+            if ((uint)arguments.Align < NvGpuVmm.PageSize)
+            {
+                arguments.Align = NvGpuVmm.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, NvGpuVmm.PageSize);
+
+                long 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.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{arguments.Handle:x8}!");
+
+                return NvInternalResult.InvalidInput;
+            }
+
+            if (map.DecrementRefCount() <= 0)
+            {
+                DeleteMapWithHandle(arguments.Handle);
+
+                Logger.PrintInfo(LogClass.ServiceNv, $"Deleted map {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.PrintWarning(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.PrintWarning(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)
+        {
+            IdDictionary dict = _maps.GetOrAdd(Owner, (key) =>
+            {
+                IdDictionary newDict = new IdDictionary();
+
+                newDict.Add(0, new NvMapHandle());
+
+                return newDict;
+            });
+
+            return dict.Add(map);
+        }
+
+        private bool DeleteMapWithHandle(int handle)
+        {
+            if (_maps.TryGetValue(Owner, out IdDictionary dict))
+            {
+                return dict.Delete(handle) != null;
+            }
+
+            return false;
+        }
+
+        public static NvMapHandle GetMapFromHandle(KProcess process, int handle, bool allowHandleZero = false)
+        {
+            if ((allowHandleZero || handle != 0) && _maps.TryGetValue(process, out IdDictionary dict))
+            {
+                return dict.GetData<NvMapHandle>(handle);
+            }
+
+            return null;
+        }
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/NvMapIoctl.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/NvMapIoctl.cs
deleted file mode 100644
index e46da4fde..000000000
--- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/NvMapIoctl.cs
+++ /dev/null
@@ -1,300 +0,0 @@
-using ARMeilleure.Memory;
-using Ryujinx.Common;
-using Ryujinx.Common.Logging;
-using Ryujinx.Graphics.Memory;
-using Ryujinx.HLE.HOS.Kernel.Process;
-using System.Collections.Concurrent;
-
-namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
-{
-    class NvMapIoctl
-    {
-        private const int FlagNotFreedYet = 1;
-
-        private static ConcurrentDictionary<KProcess, IdDictionary> _maps;
-
-        static NvMapIoctl()
-        {
-            _maps = new ConcurrentDictionary<KProcess, IdDictionary>();
-        }
-
-        public static int ProcessIoctl(ServiceCtx context, int cmd)
-        {
-            switch (cmd & 0xffff)
-            {
-                case 0x0101: return Create(context);
-                case 0x0103: return FromId(context);
-                case 0x0104: return Alloc (context);
-                case 0x0105: return Free  (context);
-                case 0x0109: return Param (context);
-                case 0x010e: return GetId (context);
-            }
-
-            Logger.PrintWarning(LogClass.ServiceNv, $"Unsupported Ioctl command 0x{cmd:x8}!");
-
-            return NvResult.NotSupported;
-        }
-
-        private static int Create(ServiceCtx context)
-        {
-            long inputPosition  = context.Request.GetBufferType0x21().Position;
-            long outputPosition = context.Request.GetBufferType0x22().Position;
-
-            NvMapCreate args = MemoryHelper.Read<NvMapCreate>(context.Memory, inputPosition);
-
-            if (args.Size == 0)
-            {
-                Logger.PrintWarning(LogClass.ServiceNv, $"Invalid size 0x{args.Size:x8}!");
-
-                return NvResult.InvalidInput;
-            }
-
-            int size = BitUtils.AlignUp(args.Size, NvGpuVmm.PageSize);
-
-            args.Handle = AddNvMap(context, new NvMapHandle(size));
-
-            Logger.PrintInfo(LogClass.ServiceNv, $"Created map {args.Handle} with size 0x{size:x8}!");
-
-            MemoryHelper.Write(context.Memory, outputPosition, args);
-
-            return NvResult.Success;
-        }
-
-        private static int FromId(ServiceCtx context)
-        {
-            long inputPosition  = context.Request.GetBufferType0x21().Position;
-            long outputPosition = context.Request.GetBufferType0x22().Position;
-
-            NvMapFromId args = MemoryHelper.Read<NvMapFromId>(context.Memory, inputPosition);
-
-            NvMapHandle map = GetNvMap(context, args.Id);
-
-            if (map == null)
-            {
-                Logger.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{args.Handle:x8}!");
-
-                return NvResult.InvalidInput;
-            }
-
-            map.IncrementRefCount();
-
-            args.Handle = args.Id;
-
-            MemoryHelper.Write(context.Memory, outputPosition, args);
-
-            return NvResult.Success;
-        }
-
-        private static int Alloc(ServiceCtx context)
-        {
-            long inputPosition  = context.Request.GetBufferType0x21().Position;
-            long outputPosition = context.Request.GetBufferType0x22().Position;
-
-            NvMapAlloc args = MemoryHelper.Read<NvMapAlloc>(context.Memory, inputPosition);
-
-            NvMapHandle map = GetNvMap(context, args.Handle);
-
-            if (map == null)
-            {
-                Logger.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{args.Handle:x8}!");
-
-                return NvResult.InvalidInput;
-            }
-
-            if ((args.Align & (args.Align - 1)) != 0)
-            {
-                Logger.PrintWarning(LogClass.ServiceNv, $"Invalid alignment 0x{args.Align:x8}!");
-
-                return NvResult.InvalidInput;
-            }
-
-            if ((uint)args.Align < NvGpuVmm.PageSize)
-            {
-                args.Align = NvGpuVmm.PageSize;
-            }
-
-            int result = NvResult.Success;
-
-            if (!map.Allocated)
-            {
-                map.Allocated = true;
-
-                map.Align =       args.Align;
-                map.Kind  = (byte)args.Kind;
-
-                int size = BitUtils.AlignUp(map.Size, NvGpuVmm.PageSize);
-
-                long address = args.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 = NvResult.OutOfMemory;
-                }
-
-                if (result == NvResult.Success)
-                {
-                    map.Size    = size;
-                    map.Address = address;
-                }
-            }
-
-            MemoryHelper.Write(context.Memory, outputPosition, args);
-
-            return result;
-        }
-
-        private static int Free(ServiceCtx context)
-        {
-            long inputPosition  = context.Request.GetBufferType0x21().Position;
-            long outputPosition = context.Request.GetBufferType0x22().Position;
-
-            NvMapFree args = MemoryHelper.Read<NvMapFree>(context.Memory, inputPosition);
-
-            NvMapHandle map = GetNvMap(context, args.Handle);
-
-            if (map == null)
-            {
-                Logger.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{args.Handle:x8}!");
-
-                return NvResult.InvalidInput;
-            }
-
-            if (map.DecrementRefCount() <= 0)
-            {
-                DeleteNvMap(context, args.Handle);
-
-                Logger.PrintInfo(LogClass.ServiceNv, $"Deleted map {args.Handle}!");
-
-                args.Address = map.Address;
-                args.Flags   = 0;
-            }
-            else
-            {
-                args.Address = 0;
-                args.Flags   = FlagNotFreedYet;
-            }
-
-            args.Size = map.Size;
-
-            MemoryHelper.Write(context.Memory, outputPosition, args);
-
-            return NvResult.Success;
-        }
-
-        private static int Param(ServiceCtx context)
-        {
-            long inputPosition  = context.Request.GetBufferType0x21().Position;
-            long outputPosition = context.Request.GetBufferType0x22().Position;
-
-            NvMapParam args = MemoryHelper.Read<NvMapParam>(context.Memory, inputPosition);
-
-            NvMapHandle map = GetNvMap(context, args.Handle);
-
-            if (map == null)
-            {
-                Logger.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{args.Handle:x8}!");
-
-                return NvResult.InvalidInput;
-            }
-
-            switch ((NvMapHandleParam)args.Param)
-            {
-                case NvMapHandleParam.Size:  args.Result = map.Size;   break;
-                case NvMapHandleParam.Align: args.Result = map.Align;  break;
-                case NvMapHandleParam.Heap:  args.Result = 0x40000000; break;
-                case NvMapHandleParam.Kind:  args.Result = map.Kind;   break;
-                case NvMapHandleParam.Compr: args.Result = 0;          break;
-
-                // Note: Base is not supported and returns an error.
-                // Any other value also returns an error.
-                default: return NvResult.InvalidInput;
-            }
-
-            MemoryHelper.Write(context.Memory, outputPosition, args);
-
-            return NvResult.Success;
-        }
-
-        private static int GetId(ServiceCtx context)
-        {
-            long inputPosition  = context.Request.GetBufferType0x21().Position;
-            long outputPosition = context.Request.GetBufferType0x22().Position;
-
-            NvMapGetId args = MemoryHelper.Read<NvMapGetId>(context.Memory, inputPosition);
-
-            NvMapHandle map = GetNvMap(context, args.Handle);
-
-            if (map == null)
-            {
-                Logger.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{args.Handle:x8}!");
-
-                return NvResult.InvalidInput;
-            }
-
-            args.Id = args.Handle;
-
-            MemoryHelper.Write(context.Memory, outputPosition, args);
-
-            return NvResult.Success;
-        }
-
-        private static int AddNvMap(ServiceCtx context, NvMapHandle map)
-        {
-            IdDictionary dict = _maps.GetOrAdd(context.Process, (key) =>
-            {
-                IdDictionary newDict = new IdDictionary();
-
-                newDict.Add(0, new NvMapHandle());
-
-                return newDict;
-            });
-
-            return dict.Add(map);
-        }
-
-        private static bool DeleteNvMap(ServiceCtx context, int handle)
-        {
-            if (_maps.TryGetValue(context.Process, out IdDictionary dict))
-            {
-                return dict.Delete(handle) != null;
-            }
-
-            return false;
-        }
-
-        public static void InitializeNvMap(ServiceCtx context)
-        {
-            IdDictionary dict = _maps.GetOrAdd(context.Process, (key) =>new IdDictionary());
-
-            dict.Add(0, new NvMapHandle());
-        }
-
-        public static NvMapHandle GetNvMapWithFb(ServiceCtx context, int handle)
-        {
-            if (_maps.TryGetValue(context.Process, out IdDictionary dict))
-            {
-                return dict.GetData<NvMapHandle>(handle);
-            }
-
-            return null;
-        }
-
-        public static NvMapHandle GetNvMap(ServiceCtx context, int handle)
-        {
-            if (handle != 0 && _maps.TryGetValue(context.Process, out IdDictionary dict))
-            {
-                return dict.GetData<NvMapHandle>(handle);
-            }
-
-            return null;
-        }
-
-        public static void UnloadProcess(KProcess process)
-        {
-            _maps.TryRemove(process, out _);
-        }
-    }
-}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapAlloc.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapAlloc.cs
index f449b6067..efc0f2aa3 100644
--- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapAlloc.cs
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapAlloc.cs
@@ -1,5 +1,8 @@
+using System.Runtime.InteropServices;
+
 namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
 {
+    [StructLayout(LayoutKind.Sequential)]
     struct NvMapAlloc
     {
         public int  Handle;
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapCreate.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapCreate.cs
index b1ccf1bc9..b47e46294 100644
--- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapCreate.cs
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapCreate.cs
@@ -1,5 +1,8 @@
+using System.Runtime.InteropServices;
+
 namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
 {
+    [StructLayout(LayoutKind.Sequential)]
     struct NvMapCreate
     {
         public int Size;
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapFree.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapFree.cs
index 1d17c3a7d..d142b9f30 100644
--- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapFree.cs
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapFree.cs
@@ -1,5 +1,8 @@
+using System.Runtime.InteropServices;
+
 namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
 {
+    [StructLayout(LayoutKind.Sequential)]
     struct NvMapFree
     {
         public int  Handle;
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapFromId.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapFromId.cs
index 7f7f83ab8..2e559534d 100644
--- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapFromId.cs
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapFromId.cs
@@ -1,5 +1,8 @@
+using System.Runtime.InteropServices;
+
 namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
 {
+    [StructLayout(LayoutKind.Sequential)]
     struct NvMapFromId
     {
         public int Id;
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapGetId.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapGetId.cs
index df8fff538..fe574eea5 100644
--- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapGetId.cs
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapGetId.cs
@@ -1,5 +1,8 @@
+using System.Runtime.InteropServices;
+
 namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
 {
+    [StructLayout(LayoutKind.Sequential)]
     struct NvMapGetId
     {
         public int Id;
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapHandleParam.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapHandleParam.cs
index 9eb7efff9..61b73cba2 100644
--- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapHandleParam.cs
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapHandleParam.cs
@@ -1,6 +1,6 @@
 namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
 {
-    enum NvMapHandleParam
+    enum NvMapHandleParam : int
     {
         Size  = 1,
         Align = 2,
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapParam.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapParam.cs
index c873a0d24..de5bab770 100644
--- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapParam.cs
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapParam.cs
@@ -1,9 +1,12 @@
+using System.Runtime.InteropServices;
+
 namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
 {
+    [StructLayout(LayoutKind.Sequential)]
     struct NvMapParam
     {
-        public int Handle;
-        public int Param;
-        public int Result;
+        public int              Handle;
+        public NvMapHandleParam Param;
+        public int              Result;
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvIoctl.cs b/Ryujinx.HLE/HOS/Services/Nv/NvIoctl.cs
new file mode 100644
index 000000000..058586949
--- /dev/null
+++ b/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/Ryujinx.HLE/HOS/Services/Nv/Types/NvFd.cs b/Ryujinx.HLE/HOS/Services/Nv/Types/NvFd.cs
deleted file mode 100644
index b6c654e4e..000000000
--- a/Ryujinx.HLE/HOS/Services/Nv/Types/NvFd.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-namespace Ryujinx.HLE.HOS.Services.Nv
-{
-    class NvFd
-    {
-        public string Name { get; private set; }
-
-        public NvFd(string name)
-        {
-            Name = name;
-        }
-    }
-}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/Types/NvFence.cs b/Ryujinx.HLE/HOS/Services/Nv/Types/NvFence.cs
new file mode 100644
index 000000000..1458f482f
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/Types/NvFence.cs
@@ -0,0 +1,11 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Nv.Types
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x8)]
+    internal struct NvFence
+    {
+        public uint Id;
+        public uint Value;
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Nv/Types/NvIoctlNotImplementedException.cs b/Ryujinx.HLE/HOS/Services/Nv/Types/NvIoctlNotImplementedException.cs
new file mode 100644
index 000000000..9404c18ce
--- /dev/null
+++ b/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/Ryujinx.HLE/HOS/Services/Nv/Types/NvQueryEventNotImplementedException.cs b/Ryujinx.HLE/HOS/Services/Nv/Types/NvQueryEventNotImplementedException.cs
new file mode 100644
index 000000000..b7a72eba0
--- /dev/null
+++ b/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/Ryujinx.HLE/HOS/Services/Nv/Types/NvResult.cs b/Ryujinx.HLE/HOS/Services/Nv/Types/NvResult.cs
index 362a04502..1c9cae8ca 100644
--- a/Ryujinx.HLE/HOS/Services/Nv/Types/NvResult.cs
+++ b/Ryujinx.HLE/HOS/Services/Nv/Types/NvResult.cs
@@ -1,14 +1,30 @@
 namespace Ryujinx.HLE.HOS.Services.Nv
 {
-    static class NvResult
+    enum NvResult : uint
     {
-        public const int NotAvailableInProduction = 196614;
-        public const int Success                  = 0;
-        public const int TryAgain                 = -11;
-        public const int OutOfMemory              = -12;
-        public const int InvalidInput             = -22;
-        public const int NotSupported             = -25;
-        public const int Restart                  = -85;
-        public const int TimedOut                 = -110;
+        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