Move solution and projects to src

This commit is contained in:
TSR Berry 2023-04-08 01:22:00 +02:00 committed by Mary
parent cd124bda58
commit cee7121058
3466 changed files with 55 additions and 55 deletions

View file

@ -0,0 +1,32 @@
using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Host1x;
using Ryujinx.Graphics.Nvdec;
using Ryujinx.Graphics.Vic;
using System;
using GpuContext = Ryujinx.Graphics.Gpu.GpuContext;
namespace Ryujinx.HLE.HOS.Services.Nv
{
class Host1xContext : IDisposable
{
public MemoryManager Smmu { get; }
public NvMemoryAllocator MemoryAllocator { get; }
public Host1xDevice Host1x { get;}
public Host1xContext(GpuContext gpu, ulong pid)
{
MemoryAllocator = new NvMemoryAllocator();
Host1x = new Host1xDevice(gpu.Synchronization);
Smmu = gpu.CreateMemoryManager(pid);
var nvdec = new NvdecDevice(Smmu);
var vic = new VicDevice(Smmu);
Host1x.RegisterDevice(ClassId.Nvdec, nvdec);
Host1x.RegisterDevice(ClassId.Vic, vic);
}
public void Dispose()
{
Host1x.Dispose();
}
}
}

View file

@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Nv
{
[Service("nvdrvdbg")]
class INvDrvDebugFSServices : IpcService
{
public INvDrvDebugFSServices(ServiceCtx context) { }
}
}

View file

@ -0,0 +1,598 @@
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.Cpu;
using Ryujinx.HLE.Exceptions;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices;
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu;
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel;
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl;
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu;
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostDbgGpu;
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostProfGpu;
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap;
using Ryujinx.HLE.HOS.Services.Nv.Types;
using Ryujinx.Memory;
using System;
using System.Collections.Generic;
using System.Reflection;
namespace Ryujinx.HLE.HOS.Services.Nv
{
[Service("nvdrv")]
[Service("nvdrv:a")]
[Service("nvdrv:s")]
[Service("nvdrv:t")]
class INvDrvServices : IpcService
{
private static readonly List<string> _deviceFileDebugRegistry = new List<string>()
{
"/dev/nvhost-dbg-gpu",
"/dev/nvhost-prof-gpu"
};
private static readonly Dictionary<string, Type> _deviceFileRegistry = new Dictionary<string, Type>()
{
{ "/dev/nvmap", typeof(NvMapDeviceFile) },
{ "/dev/nvhost-ctrl", typeof(NvHostCtrlDeviceFile) },
{ "/dev/nvhost-ctrl-gpu", typeof(NvHostCtrlGpuDeviceFile) },
{ "/dev/nvhost-as-gpu", typeof(NvHostAsGpuDeviceFile) },
{ "/dev/nvhost-gpu", typeof(NvHostGpuDeviceFile) },
//{ "/dev/nvhost-msenc", typeof(NvHostChannelDeviceFile) },
{ "/dev/nvhost-nvdec", typeof(NvHostChannelDeviceFile) },
//{ "/dev/nvhost-nvjpg", typeof(NvHostChannelDeviceFile) },
{ "/dev/nvhost-vic", typeof(NvHostChannelDeviceFile) },
//{ "/dev/nvhost-display", typeof(NvHostChannelDeviceFile) },
{ "/dev/nvhost-dbg-gpu", typeof(NvHostDbgGpuDeviceFile) },
{ "/dev/nvhost-prof-gpu", typeof(NvHostProfGpuDeviceFile) },
};
public static IdDictionary DeviceFileIdRegistry = new IdDictionary();
private IVirtualMemoryManager _clientMemory;
private ulong _owner;
private bool _transferMemInitialized = false;
// TODO: This should call set:sys::GetDebugModeFlag
private bool _debugModeEnabled = false;
public INvDrvServices(ServiceCtx context) : base(context.Device.System.NvDrvServer)
{
_owner = 0;
}
private NvResult Open(ServiceCtx context, string path, out int fd)
{
fd = -1;
if (!_debugModeEnabled && _deviceFileDebugRegistry.Contains(path))
{
return NvResult.NotSupported;
}
if (_deviceFileRegistry.TryGetValue(path, out Type deviceFileClass))
{
ConstructorInfo constructor = deviceFileClass.GetConstructor(new Type[] { typeof(ServiceCtx), typeof(IVirtualMemoryManager), typeof(ulong) });
NvDeviceFile deviceFile = (NvDeviceFile)constructor.Invoke(new object[] { context, _clientMemory, _owner });
deviceFile.Path = path;
fd = DeviceFileIdRegistry.Add(deviceFile);
return NvResult.Success;
}
Logger.Warning?.Print(LogClass.ServiceNv, $"Cannot find file device \"{path}\"!");
return NvResult.FileOperationFailed;
}
private NvResult GetIoctlArgument(ServiceCtx context, NvIoctl ioctlCommand, out Span<byte> arguments)
{
(ulong inputDataPosition, ulong inputDataSize) = context.Request.GetBufferType0x21(0);
(ulong outputDataPosition, ulong outputDataSize) = context.Request.GetBufferType0x22(0);
NvIoctl.Direction ioctlDirection = ioctlCommand.DirectionValue;
uint ioctlSize = ioctlCommand.Size;
bool isRead = (ioctlDirection & NvIoctl.Direction.Read) != 0;
bool isWrite = (ioctlDirection & NvIoctl.Direction.Write) != 0;
if ((isWrite && ioctlSize > outputDataSize) || (isRead && ioctlSize > inputDataSize))
{
arguments = null;
Logger.Warning?.Print(LogClass.ServiceNv, "Ioctl size inconsistency found!");
return NvResult.InvalidSize;
}
if (isRead && isWrite)
{
if (outputDataSize < inputDataSize)
{
arguments = null;
Logger.Warning?.Print(LogClass.ServiceNv, "Ioctl size inconsistency found!");
return NvResult.InvalidSize;
}
byte[] outputData = new byte[outputDataSize];
byte[] temp = new byte[inputDataSize];
context.Memory.Read(inputDataPosition, temp);
Buffer.BlockCopy(temp, 0, outputData, 0, temp.Length);
arguments = new Span<byte>(outputData);
}
else if (isWrite)
{
byte[] outputData = new byte[outputDataSize];
arguments = new Span<byte>(outputData);
}
else
{
byte[] temp = new byte[inputDataSize];
context.Memory.Read(inputDataPosition, temp);
arguments = new Span<byte>(temp);
}
return NvResult.Success;
}
private NvResult GetDeviceFileFromFd(int fd, out NvDeviceFile deviceFile)
{
deviceFile = null;
if (fd < 0)
{
return NvResult.InvalidParameter;
}
deviceFile = DeviceFileIdRegistry.GetData<NvDeviceFile>(fd);
if (deviceFile == null)
{
Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid file descriptor {fd}");
return NvResult.NotImplemented;
}
if (deviceFile.Owner != _owner)
{
return NvResult.AccessDenied;
}
return NvResult.Success;
}
private NvResult EnsureInitialized()
{
if (_owner == 0)
{
Logger.Warning?.Print(LogClass.ServiceNv, "INvDrvServices is not initialized!");
return NvResult.NotInitialized;
}
return NvResult.Success;
}
private static NvResult ConvertInternalErrorCode(NvInternalResult errorCode)
{
switch (errorCode)
{
case NvInternalResult.Success:
return NvResult.Success;
case NvInternalResult.Unknown0x72:
return NvResult.AlreadyAllocated;
case NvInternalResult.TimedOut:
case NvInternalResult.TryAgain:
case NvInternalResult.Interrupted:
return NvResult.Timeout;
case NvInternalResult.InvalidAddress:
return NvResult.InvalidAddress;
case NvInternalResult.NotSupported:
case NvInternalResult.Unknown0x18:
return NvResult.NotSupported;
case NvInternalResult.InvalidState:
return NvResult.InvalidState;
case NvInternalResult.ReadOnlyAttribute:
return NvResult.ReadOnlyAttribute;
case NvInternalResult.NoSpaceLeft:
case NvInternalResult.FileTooBig:
return NvResult.InvalidSize;
case NvInternalResult.FileTableOverflow:
case NvInternalResult.BadFileNumber:
return NvResult.FileOperationFailed;
case NvInternalResult.InvalidInput:
return NvResult.InvalidValue;
case NvInternalResult.NotADirectory:
return NvResult.DirectoryOperationFailed;
case NvInternalResult.Busy:
return NvResult.Busy;
case NvInternalResult.BadAddress:
return NvResult.InvalidAddress;
case NvInternalResult.AccessDenied:
case NvInternalResult.OperationNotPermitted:
return NvResult.AccessDenied;
case NvInternalResult.OutOfMemory:
return NvResult.InsufficientMemory;
case NvInternalResult.DeviceNotFound:
return NvResult.ModuleNotPresent;
case NvInternalResult.IoError:
return NvResult.ResourceError;
default:
return NvResult.IoctlFailed;
}
}
[CommandCmif(0)]
// Open(buffer<bytes, 5> path) -> (s32 fd, u32 error_code)
public ResultCode Open(ServiceCtx context)
{
NvResult errorCode = EnsureInitialized();
int fd = -1;
if (errorCode == NvResult.Success)
{
ulong pathPtr = context.Request.SendBuff[0].Position;
ulong pathSize = context.Request.SendBuff[0].Size;
string path = MemoryHelper.ReadAsciiString(context.Memory, pathPtr, (long)pathSize);
errorCode = Open(context, path, out fd);
}
context.ResponseData.Write(fd);
context.ResponseData.Write((uint)errorCode);
return ResultCode.Success;
}
[CommandCmif(1)]
// Ioctl(s32 fd, u32 ioctl_cmd, buffer<bytes, 0x21> in_args) -> (u32 error_code, buffer<bytes, 0x22> out_args)
public ResultCode Ioctl(ServiceCtx context)
{
NvResult errorCode = EnsureInitialized();
if (errorCode == NvResult.Success)
{
int fd = context.RequestData.ReadInt32();
NvIoctl ioctlCommand = context.RequestData.ReadStruct<NvIoctl>();
errorCode = GetIoctlArgument(context, ioctlCommand, out Span<byte> arguments);
if (errorCode == NvResult.Success)
{
errorCode = GetDeviceFileFromFd(fd, out NvDeviceFile deviceFile);
if (errorCode == NvResult.Success)
{
NvInternalResult internalResult = deviceFile.Ioctl(ioctlCommand, arguments);
if (internalResult == NvInternalResult.NotImplemented)
{
throw new NvIoctlNotImplementedException(context, deviceFile, ioctlCommand);
}
errorCode = ConvertInternalErrorCode(internalResult);
if ((ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0)
{
context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments.ToArray());
}
}
}
}
context.ResponseData.Write((uint)errorCode);
return ResultCode.Success;
}
[CommandCmif(2)]
// Close(s32 fd) -> u32 error_code
public ResultCode Close(ServiceCtx context)
{
NvResult errorCode = EnsureInitialized();
if (errorCode == NvResult.Success)
{
int fd = context.RequestData.ReadInt32();
errorCode = GetDeviceFileFromFd(fd, out NvDeviceFile deviceFile);
if (errorCode == NvResult.Success)
{
deviceFile.Close();
DeviceFileIdRegistry.Delete(fd);
}
}
context.ResponseData.Write((uint)errorCode);
return ResultCode.Success;
}
[CommandCmif(3)]
// Initialize(u32 transfer_memory_size, handle<copy, process> current_process, handle<copy, transfer_memory> transfer_memory) -> u32 error_code
public ResultCode Initialize(ServiceCtx context)
{
long transferMemSize = context.RequestData.ReadInt64();
int transferMemHandle = context.Request.HandleDesc.ToCopy[1];
// TODO: When transfer memory will be implemented, this could be removed.
_transferMemInitialized = true;
int clientHandle = context.Request.HandleDesc.ToCopy[0];
_clientMemory = context.Process.HandleTable.GetKProcess(clientHandle).CpuMemory;
context.Device.System.KernelContext.Syscall.GetProcessId(out _owner, clientHandle);
context.ResponseData.Write((uint)NvResult.Success);
// Close the process and transfer memory handles immediately as we don't use them.
context.Device.System.KernelContext.Syscall.CloseHandle(clientHandle);
context.Device.System.KernelContext.Syscall.CloseHandle(transferMemHandle);
return ResultCode.Success;
}
[CommandCmif(4)]
// QueryEvent(s32 fd, u32 event_id) -> (u32, handle<copy, event>)
public ResultCode QueryEvent(ServiceCtx context)
{
NvResult errorCode = EnsureInitialized();
if (errorCode == NvResult.Success)
{
int fd = context.RequestData.ReadInt32();
uint eventId = context.RequestData.ReadUInt32();
errorCode = GetDeviceFileFromFd(fd, out NvDeviceFile deviceFile);
if (errorCode == NvResult.Success)
{
NvInternalResult internalResult = deviceFile.QueryEvent(out int eventHandle, eventId);
if (internalResult == NvInternalResult.NotImplemented)
{
throw new NvQueryEventNotImplementedException(context, deviceFile, eventId);
}
errorCode = ConvertInternalErrorCode(internalResult);
if (errorCode == NvResult.Success)
{
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(eventHandle);
}
}
}
context.ResponseData.Write((uint)errorCode);
return ResultCode.Success;
}
[CommandCmif(5)]
// MapSharedMemory(s32 fd, u32 argument, handle<copy, shared_memory>) -> u32 error_code
public ResultCode MapSharedMemory(ServiceCtx context)
{
NvResult errorCode = EnsureInitialized();
if (errorCode == NvResult.Success)
{
int fd = context.RequestData.ReadInt32();
uint argument = context.RequestData.ReadUInt32();
int sharedMemoryHandle = context.Request.HandleDesc.ToCopy[0];
errorCode = GetDeviceFileFromFd(fd, out NvDeviceFile deviceFile);
if (errorCode == NvResult.Success)
{
errorCode = ConvertInternalErrorCode(deviceFile.MapSharedMemory(sharedMemoryHandle, argument));
}
}
context.ResponseData.Write((uint)errorCode);
return ResultCode.Success;
}
[CommandCmif(6)]
// GetStatus() -> (unknown<0x20>, u32 error_code)
public ResultCode GetStatus(ServiceCtx context)
{
// TODO: When transfer memory will be implemented, check if it's mapped instead.
if (_transferMemInitialized)
{
// TODO: Populate values when more RE will be done.
NvStatus nvStatus = new NvStatus
{
MemoryValue1 = 0, // GetMemStats(transfer_memory + 0x60, 3)
MemoryValue2 = 0, // GetMemStats(transfer_memory + 0x60, 5)
MemoryValue3 = 0, // transfer_memory + 0x78
MemoryValue4 = 0 // transfer_memory + 0x80
};
context.ResponseData.WriteStruct(nvStatus);
context.ResponseData.Write((uint)NvResult.Success);
Logger.Stub?.PrintStub(LogClass.ServiceNv);
}
else
{
context.ResponseData.Write((uint)NvResult.NotInitialized);
}
return ResultCode.Success;
}
[CommandCmif(7)]
// ForceSetClientPid(u64) -> u32 error_code
public ResultCode ForceSetClientPid(ServiceCtx context)
{
throw new ServiceNotImplementedException(this, context);
}
[CommandCmif(8)]
// SetClientPID(u64, pid) -> u32 error_code
public ResultCode SetClientPid(ServiceCtx context)
{
long pid = context.RequestData.ReadInt64();
context.ResponseData.Write(0);
return ResultCode.Success;
}
[CommandCmif(9)]
// DumpGraphicsMemoryInfo()
public ResultCode DumpGraphicsMemoryInfo(ServiceCtx context)
{
Logger.Stub?.PrintStub(LogClass.ServiceNv);
return ResultCode.Success;
}
[CommandCmif(10)] // 3.0.0+
// InitializeDevtools(u32, handle<copy>) -> u32 error_code;
public ResultCode InitializeDevtools(ServiceCtx context)
{
throw new ServiceNotImplementedException(this, context);
}
[CommandCmif(11)] // 3.0.0+
// Ioctl2(s32 fd, u32 ioctl_cmd, buffer<bytes, 0x21> in_args, buffer<bytes, 0x21> inline_in_buffer) -> (u32 error_code, buffer<bytes, 0x22> out_args)
public ResultCode Ioctl2(ServiceCtx context)
{
NvResult errorCode = EnsureInitialized();
if (errorCode == NvResult.Success)
{
int fd = context.RequestData.ReadInt32();
NvIoctl ioctlCommand = context.RequestData.ReadStruct<NvIoctl>();
(ulong inlineInBufferPosition, ulong inlineInBufferSize) = context.Request.GetBufferType0x21(1);
errorCode = GetIoctlArgument(context, ioctlCommand, out Span<byte> arguments);
byte[] temp = new byte[inlineInBufferSize];
context.Memory.Read(inlineInBufferPosition, temp);
Span<byte> inlineInBuffer = new Span<byte>(temp);
if (errorCode == NvResult.Success)
{
errorCode = GetDeviceFileFromFd(fd, out NvDeviceFile deviceFile);
if (errorCode == NvResult.Success)
{
NvInternalResult internalResult = deviceFile.Ioctl2(ioctlCommand, arguments, inlineInBuffer);
if (internalResult == NvInternalResult.NotImplemented)
{
throw new NvIoctlNotImplementedException(context, deviceFile, ioctlCommand);
}
errorCode = ConvertInternalErrorCode(internalResult);
if ((ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0)
{
context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments.ToArray());
}
}
}
}
context.ResponseData.Write((uint)errorCode);
return ResultCode.Success;
}
[CommandCmif(12)] // 3.0.0+
// Ioctl3(s32 fd, u32 ioctl_cmd, buffer<bytes, 0x21> in_args) -> (u32 error_code, buffer<bytes, 0x22> out_args, buffer<bytes, 0x22> inline_out_buffer)
public ResultCode Ioctl3(ServiceCtx context)
{
NvResult errorCode = EnsureInitialized();
if (errorCode == NvResult.Success)
{
int fd = context.RequestData.ReadInt32();
NvIoctl ioctlCommand = context.RequestData.ReadStruct<NvIoctl>();
(ulong inlineOutBufferPosition, ulong inlineOutBufferSize) = context.Request.GetBufferType0x22(1);
errorCode = GetIoctlArgument(context, ioctlCommand, out Span<byte> arguments);
byte[] temp = new byte[inlineOutBufferSize];
context.Memory.Read(inlineOutBufferPosition, temp);
Span<byte> inlineOutBuffer = new Span<byte>(temp);
if (errorCode == NvResult.Success)
{
errorCode = GetDeviceFileFromFd(fd, out NvDeviceFile deviceFile);
if (errorCode == NvResult.Success)
{
NvInternalResult internalResult = deviceFile.Ioctl3(ioctlCommand, arguments, inlineOutBuffer);
if (internalResult == NvInternalResult.NotImplemented)
{
throw new NvIoctlNotImplementedException(context, deviceFile, ioctlCommand);
}
errorCode = ConvertInternalErrorCode(internalResult);
if ((ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0)
{
context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments.ToArray());
context.Memory.Write(inlineOutBufferPosition, inlineOutBuffer.ToArray());
}
}
}
}
context.ResponseData.Write((uint)errorCode);
return ResultCode.Success;
}
[CommandCmif(13)] // 3.0.0+
// FinishInitialize(unknown<8>)
public ResultCode FinishInitialize(ServiceCtx context)
{
Logger.Stub?.PrintStub(LogClass.ServiceNv);
return ResultCode.Success;
}
public static void Destroy()
{
NvHostChannelDeviceFile.Destroy();
foreach (object entry in DeviceFileIdRegistry.Values)
{
NvDeviceFile deviceFile = (NvDeviceFile)entry;
deviceFile.Close();
}
DeviceFileIdRegistry.Clear();
}
}
}

View file

@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Nv
{
[Service("nvgem:c")]
class INvGemControl : IpcService
{
public INvGemControl(ServiceCtx context) { }
}
}

View file

@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Nv
{
[Service("nvgem:cd")]
class INvGemCoreDump : IpcService
{
public INvGemCoreDump(ServiceCtx context) { }
}
}

View file

@ -0,0 +1,94 @@
using Ryujinx.Common.Logging;
using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices
{
abstract class NvDeviceFile
{
public readonly ServiceCtx Context;
public readonly ulong Owner;
public string Path;
public NvDeviceFile(ServiceCtx context, ulong owner)
{
Context = context;
Owner = owner;
}
public virtual NvInternalResult QueryEvent(out int eventHandle, uint eventId)
{
eventHandle = 0;
return NvInternalResult.NotImplemented;
}
public virtual NvInternalResult MapSharedMemory(int sharedMemoryHandle, uint argument)
{
// Close shared memory immediately as we don't use it.
Context.Device.System.KernelContext.Syscall.CloseHandle(sharedMemoryHandle);
return NvInternalResult.NotImplemented;
}
public virtual NvInternalResult Ioctl(NvIoctl command, Span<byte> arguments)
{
return NvInternalResult.NotImplemented;
}
public virtual NvInternalResult Ioctl2(NvIoctl command, Span<byte> arguments, Span<byte> inlineInBuffer)
{
return NvInternalResult.NotImplemented;
}
public virtual NvInternalResult Ioctl3(NvIoctl command, Span<byte> arguments, Span<byte> inlineOutBuffer)
{
return NvInternalResult.NotImplemented;
}
protected delegate NvInternalResult IoctlProcessor<T>(ref T arguments);
protected delegate NvInternalResult IoctlProcessorSpan<T>(Span<T> arguments);
protected delegate NvInternalResult IoctlProcessorInline<T, T1>(ref T arguments, ref T1 inlineData);
protected delegate NvInternalResult IoctlProcessorInlineSpan<T, T1>(ref T arguments, Span<T1> inlineData);
private static NvInternalResult PrintResult(MethodInfo info, NvInternalResult result)
{
Logger.Debug?.Print(LogClass.ServiceNv, $"{info.Name} returned result {result}");
return result;
}
protected static NvInternalResult CallIoctlMethod<T>(IoctlProcessor<T> callback, Span<byte> arguments) where T : struct
{
Debug.Assert(arguments.Length == Unsafe.SizeOf<T>());
return PrintResult(callback.Method, callback(ref MemoryMarshal.Cast<byte, T>(arguments)[0]));
}
protected static NvInternalResult CallIoctlMethod<T, T1>(IoctlProcessorInline<T, T1> callback, Span<byte> arguments, Span<byte> inlineBuffer) where T : struct where T1 : struct
{
Debug.Assert(arguments.Length == Unsafe.SizeOf<T>());
Debug.Assert(inlineBuffer.Length == Unsafe.SizeOf<T1>());
return PrintResult(callback.Method, callback(ref MemoryMarshal.Cast<byte, T>(arguments)[0], ref MemoryMarshal.Cast<byte, T1>(inlineBuffer)[0]));
}
protected static NvInternalResult CallIoctlMethod<T>(IoctlProcessorSpan<T> callback, Span<byte> arguments) where T : struct
{
return PrintResult(callback.Method, callback(MemoryMarshal.Cast<byte, T>(arguments)));
}
protected static NvInternalResult CallIoctlMethod<T, T1>(IoctlProcessorInlineSpan<T, T1> callback, Span<byte> arguments, Span<byte> inlineBuffer) where T : struct where T1 : struct
{
Debug.Assert(arguments.Length == Unsafe.SizeOf<T>());
return PrintResult(callback.Method, callback(ref MemoryMarshal.Cast<byte, T>(arguments)[0], MemoryMarshal.Cast<byte, T1>(inlineBuffer)));
}
public abstract void Close();
}
}

View file

@ -0,0 +1,401 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types;
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel;
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap;
using Ryujinx.Memory;
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu
{
class NvHostAsGpuDeviceFile : NvDeviceFile
{
private const uint SmallPageSize = 0x1000;
private const uint BigPageSize = 0x10000;
private static readonly uint[] _pageSizes = new uint[] { SmallPageSize, BigPageSize };
private const ulong SmallRegionLimit = 0x400000000UL; // 16 GiB
private const ulong DefaultUserSize = 1UL << 37;
private readonly struct VmRegion
{
public ulong Start { get; }
public ulong Limit { get; }
public VmRegion(ulong start, ulong limit)
{
Start = start;
Limit = limit;
}
}
private static readonly VmRegion[] _vmRegions = new VmRegion[]
{
new VmRegion((ulong)BigPageSize << 16, SmallRegionLimit),
new VmRegion(SmallRegionLimit, DefaultUserSize)
};
private readonly AddressSpaceContext _asContext;
private readonly NvMemoryAllocator _memoryAllocator;
public NvHostAsGpuDeviceFile(ServiceCtx context, IVirtualMemoryManager memory, ulong owner) : base(context, owner)
{
_asContext = new AddressSpaceContext(context.Device.Gpu.CreateMemoryManager(owner));
_memoryAllocator = new NvMemoryAllocator();
}
public override NvInternalResult Ioctl(NvIoctl command, Span<byte> arguments)
{
NvInternalResult result = NvInternalResult.NotImplemented;
if (command.Type == NvIoctl.NvGpuAsMagic)
{
switch (command.Number)
{
case 0x01:
result = CallIoctlMethod<BindChannelArguments>(BindChannel, arguments);
break;
case 0x02:
result = CallIoctlMethod<AllocSpaceArguments>(AllocSpace, arguments);
break;
case 0x03:
result = CallIoctlMethod<FreeSpaceArguments>(FreeSpace, arguments);
break;
case 0x05:
result = CallIoctlMethod<UnmapBufferArguments>(UnmapBuffer, arguments);
break;
case 0x06:
result = CallIoctlMethod<MapBufferExArguments>(MapBufferEx, arguments);
break;
case 0x08:
result = CallIoctlMethod<GetVaRegionsArguments>(GetVaRegions, arguments);
break;
case 0x09:
result = CallIoctlMethod<InitializeExArguments>(InitializeEx, arguments);
break;
case 0x14:
result = CallIoctlMethod<RemapArguments>(Remap, arguments);
break;
}
}
return result;
}
public override NvInternalResult Ioctl3(NvIoctl command, Span<byte> arguments, Span<byte> inlineOutBuffer)
{
NvInternalResult result = NvInternalResult.NotImplemented;
if (command.Type == NvIoctl.NvGpuAsMagic)
{
switch (command.Number)
{
case 0x08:
// This is the same as the one in ioctl as inlineOutBuffer is empty.
result = CallIoctlMethod<GetVaRegionsArguments>(GetVaRegions, arguments);
break;
}
}
return result;
}
private NvInternalResult BindChannel(ref BindChannelArguments arguments)
{
var channelDeviceFile = INvDrvServices.DeviceFileIdRegistry.GetData<NvHostChannelDeviceFile>(arguments.Fd);
if (channelDeviceFile == null)
{
// TODO: Return invalid Fd error.
}
channelDeviceFile.Channel.BindMemory(_asContext.Gmm);
return NvInternalResult.Success;
}
private NvInternalResult AllocSpace(ref AllocSpaceArguments arguments)
{
ulong size = (ulong)arguments.Pages * (ulong)arguments.PageSize;
NvInternalResult result = NvInternalResult.Success;
lock (_asContext)
{
// Note: When the fixed offset flag is not set,
// the Offset field holds the alignment size instead.
if ((arguments.Flags & AddressSpaceFlags.FixedOffset) != 0)
{
bool regionInUse = _memoryAllocator.IsRegionInUse(arguments.Offset, size, out ulong freeAddressStartPosition);
ulong address;
if (!regionInUse)
{
_memoryAllocator.AllocateRange(arguments.Offset, size, freeAddressStartPosition);
address = freeAddressStartPosition;
}
else
{
address = NvMemoryAllocator.PteUnmapped;
}
arguments.Offset = address;
}
else
{
ulong address = _memoryAllocator.GetFreeAddress(size, out ulong freeAddressStartPosition, arguments.Offset);
if (address != NvMemoryAllocator.PteUnmapped)
{
_memoryAllocator.AllocateRange(address, size, freeAddressStartPosition);
}
arguments.Offset = address;
}
if (arguments.Offset == NvMemoryAllocator.PteUnmapped)
{
arguments.Offset = 0;
Logger.Warning?.Print(LogClass.ServiceNv, $"Failed to allocate size {size:x16}!");
result = NvInternalResult.OutOfMemory;
}
else
{
_asContext.AddReservation(arguments.Offset, size);
}
}
return result;
}
private NvInternalResult FreeSpace(ref FreeSpaceArguments arguments)
{
ulong size = (ulong)arguments.Pages * (ulong)arguments.PageSize;
NvInternalResult result = NvInternalResult.Success;
lock (_asContext)
{
if (_asContext.RemoveReservation(arguments.Offset))
{
_memoryAllocator.DeallocateRange(arguments.Offset, size);
_asContext.Gmm.Unmap(arguments.Offset, size);
}
else
{
Logger.Warning?.Print(LogClass.ServiceNv,
$"Failed to free offset 0x{arguments.Offset:x16} size 0x{size:x16}!");
result = NvInternalResult.InvalidInput;
}
}
return result;
}
private NvInternalResult UnmapBuffer(ref UnmapBufferArguments arguments)
{
lock (_asContext)
{
if (_asContext.RemoveMap(arguments.Offset, out ulong size))
{
if (size != 0)
{
_memoryAllocator.DeallocateRange(arguments.Offset, size);
_asContext.Gmm.Unmap(arguments.Offset, size);
}
}
else
{
Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid buffer offset {arguments.Offset:x16}!");
}
}
return NvInternalResult.Success;
}
private NvInternalResult MapBufferEx(ref MapBufferExArguments arguments)
{
const string MapErrorMsg = "Failed to map fixed buffer with offset 0x{0:x16}, size 0x{1:x16} and alignment 0x{2:x16}!";
ulong physicalAddress;
if ((arguments.Flags & AddressSpaceFlags.RemapSubRange) != 0)
{
lock (_asContext)
{
if (_asContext.TryGetMapPhysicalAddress(arguments.Offset, out physicalAddress))
{
ulong virtualAddress = arguments.Offset + arguments.BufferOffset;
physicalAddress += arguments.BufferOffset;
_asContext.Gmm.Map(physicalAddress, virtualAddress, arguments.MappingSize, (PteKind)arguments.Kind);
return NvInternalResult.Success;
}
else
{
Logger.Warning?.Print(LogClass.ServiceNv, $"Address 0x{arguments.Offset:x16} not mapped!");
return NvInternalResult.InvalidInput;
}
}
}
NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, arguments.NvMapHandle);
if (map == null)
{
Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid NvMap handle 0x{arguments.NvMapHandle:x8}!");
return NvInternalResult.InvalidInput;
}
ulong pageSize = (ulong)arguments.PageSize;
if (pageSize == 0)
{
pageSize = (ulong)map.Align;
}
physicalAddress = map.Address + arguments.BufferOffset;
ulong size = arguments.MappingSize;
if (size == 0)
{
size = (uint)map.Size;
}
NvInternalResult result = NvInternalResult.Success;
lock (_asContext)
{
// Note: When the fixed offset flag is not set,
// the Offset field holds the alignment size instead.
bool virtualAddressAllocated = (arguments.Flags & AddressSpaceFlags.FixedOffset) == 0;
if (!virtualAddressAllocated)
{
if (_asContext.ValidateFixedBuffer(arguments.Offset, size, pageSize))
{
_asContext.Gmm.Map(physicalAddress, arguments.Offset, size, (PteKind)arguments.Kind);
}
else
{
string message = string.Format(MapErrorMsg, arguments.Offset, size, pageSize);
Logger.Warning?.Print(LogClass.ServiceNv, message);
result = NvInternalResult.InvalidInput;
}
}
else
{
ulong va = _memoryAllocator.GetFreeAddress(size, out ulong freeAddressStartPosition, pageSize);
if (va != NvMemoryAllocator.PteUnmapped)
{
_memoryAllocator.AllocateRange(va, size, freeAddressStartPosition);
}
_asContext.Gmm.Map(physicalAddress, va, size, (PteKind)arguments.Kind);
arguments.Offset = va;
}
if (arguments.Offset == NvMemoryAllocator.PteUnmapped)
{
arguments.Offset = 0;
Logger.Warning?.Print(LogClass.ServiceNv, $"Failed to map size 0x{size:x16}!");
result = NvInternalResult.InvalidInput;
}
else
{
_asContext.AddMap(arguments.Offset, size, physicalAddress, virtualAddressAllocated);
}
}
return result;
}
private NvInternalResult GetVaRegions(ref GetVaRegionsArguments arguments)
{
int vaRegionStructSize = Unsafe.SizeOf<VaRegion>();
Debug.Assert(vaRegionStructSize == 0x18);
Debug.Assert(_pageSizes.Length == 2);
uint writeEntries = (uint)(arguments.BufferSize / vaRegionStructSize);
if (writeEntries > _pageSizes.Length)
{
writeEntries = (uint)_pageSizes.Length;
}
for (uint i = 0; i < writeEntries; i++)
{
ref var region = ref arguments.Regions[(int)i];
var vmRegion = _vmRegions[i];
uint pageSize = _pageSizes[i];
region.PageSize = pageSize;
region.Offset = vmRegion.Start;
region.Pages = (vmRegion.Limit - vmRegion.Start) / pageSize;
region.Padding = 0;
}
arguments.BufferSize = (uint)(_pageSizes.Length * vaRegionStructSize);
return NvInternalResult.Success;
}
private NvInternalResult InitializeEx(ref InitializeExArguments arguments)
{
Logger.Stub?.PrintStub(LogClass.ServiceNv);
return NvInternalResult.Success;
}
private NvInternalResult Remap(Span<RemapArguments> arguments)
{
MemoryManager gmm = _asContext.Gmm;
for (int index = 0; index < arguments.Length; index++)
{
ref RemapArguments argument = ref arguments[index];
ulong gpuVa = (ulong)argument.GpuOffset << 16;
ulong size = (ulong)argument.Pages << 16;
int nvmapHandle = argument.NvMapHandle;
if (nvmapHandle == 0)
{
gmm.Unmap(gpuVa, size);
}
else
{
ulong mapOffs = (ulong)argument.MapOffset << 16;
PteKind kind = (PteKind)argument.Kind;
NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, nvmapHandle);
if (map == null)
{
Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid NvMap handle 0x{nvmapHandle:x8}!");
return NvInternalResult.InvalidInput;
}
gmm.Map(mapOffs + map.Address, gpuVa, size, kind);
}
}
return NvInternalResult.Success;
}
public override void Close() { }
}
}

View file

@ -0,0 +1,190 @@
using Ryujinx.Graphics.Gpu.Memory;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types
{
class AddressSpaceContext
{
private class Range
{
public ulong Start { get; }
public ulong End { get; }
public Range(ulong address, ulong size)
{
Start = address;
End = size + Start;
}
}
private class MappedMemory : Range
{
public ulong PhysicalAddress { get; }
public bool VaAllocated { get; }
public MappedMemory(ulong address, ulong size, ulong physicalAddress, bool vaAllocated) : base(address, size)
{
PhysicalAddress = physicalAddress;
VaAllocated = vaAllocated;
}
}
public MemoryManager Gmm { get; }
private readonly SortedList<ulong, Range> _maps;
private readonly SortedList<ulong, Range> _reservations;
public AddressSpaceContext(MemoryManager gmm)
{
Gmm = gmm;
_maps = new SortedList<ulong, Range>();
_reservations = new SortedList<ulong, Range>();
}
public bool ValidateFixedBuffer(ulong address, ulong size, ulong alignment)
{
ulong mapEnd = address + size;
// Check if size is valid (0 is also not allowed).
if (mapEnd <= address)
{
return false;
}
// Check if address is aligned.
if ((address & (alignment - 1)) != 0)
{
return false;
}
// Check if region is reserved.
if (BinarySearch(_reservations, address) == null)
{
return false;
}
// Check for overlap with already mapped buffers.
Range map = BinarySearchLt(_maps, mapEnd);
if (map != null && map.End > address)
{
return false;
}
return true;
}
public void AddMap(ulong gpuVa, ulong size, ulong physicalAddress, bool vaAllocated)
{
_maps.Add(gpuVa, new MappedMemory(gpuVa, size, physicalAddress, vaAllocated));
}
public bool RemoveMap(ulong gpuVa, out ulong size)
{
size = 0;
if (_maps.Remove(gpuVa, out Range value))
{
MappedMemory map = (MappedMemory)value;
if (map.VaAllocated)
{
size = (map.End - map.Start);
}
return true;
}
return false;
}
public bool TryGetMapPhysicalAddress(ulong gpuVa, out ulong physicalAddress)
{
Range map = BinarySearch(_maps, gpuVa);
if (map != null)
{
physicalAddress = ((MappedMemory)map).PhysicalAddress;
return true;
}
physicalAddress = 0;
return false;
}
public void AddReservation(ulong gpuVa, ulong size)
{
_reservations.Add(gpuVa, new Range(gpuVa, size));
}
public bool RemoveReservation(ulong gpuVa)
{
return _reservations.Remove(gpuVa);
}
private static Range BinarySearch(SortedList<ulong, Range> list, ulong address)
{
int left = 0;
int right = list.Count - 1;
while (left <= right)
{
int size = right - left;
int middle = left + (size >> 1);
Range rg = list.Values[middle];
if (address >= rg.Start && address < rg.End)
{
return rg;
}
if (address < rg.Start)
{
right = middle - 1;
}
else
{
left = middle + 1;
}
}
return null;
}
private static Range BinarySearchLt(SortedList<ulong, Range> list, ulong address)
{
Range ltRg = null;
int left = 0;
int right = list.Count - 1;
while (left <= right)
{
int size = right - left;
int middle = left + (size >> 1);
Range rg = list.Values[middle];
if (address < rg.Start)
{
right = middle - 1;
}
else
{
left = middle + 1;
if (address > rg.Start)
{
ltRg = rg;
}
}
}
return ltRg;
}
}
}

View file

@ -0,0 +1,11 @@
using System;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types
{
[Flags]
enum AddressSpaceFlags : uint
{
FixedOffset = 1,
RemapSubRange = 0x100,
}
}

View file

@ -0,0 +1,14 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types
{
[StructLayout(LayoutKind.Sequential)]
struct AllocSpaceArguments
{
public uint Pages;
public uint PageSize;
public AddressSpaceFlags Flags;
public uint Padding;
public ulong Offset;
}
}

View file

@ -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;
}
}

View file

@ -0,0 +1,12 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types
{
[StructLayout(LayoutKind.Sequential)]
struct FreeSpaceArguments
{
public ulong Offset;
public uint Pages;
public uint PageSize;
}
}

View file

@ -0,0 +1,23 @@
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types
{
[StructLayout(LayoutKind.Sequential)]
struct VaRegion
{
public ulong Offset;
public uint PageSize;
public uint Padding;
public ulong Pages;
}
[StructLayout(LayoutKind.Sequential)]
struct GetVaRegionsArguments
{
public ulong Unused;
public uint BufferSize;
public uint Padding;
public Array2<VaRegion> Regions;
}
}

View file

@ -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;
}
}

View file

@ -0,0 +1,16 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types
{
[StructLayout(LayoutKind.Sequential)]
struct MapBufferExArguments
{
public AddressSpaceFlags Flags;
public int Kind;
public int NvMapHandle;
public int PageSize;
public ulong BufferOffset;
public ulong MappingSize;
public ulong Offset;
}
}

View file

@ -0,0 +1,15 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types
{
[StructLayout(LayoutKind.Sequential)]
struct RemapArguments
{
public ushort Flags;
public ushort Kind;
public int NvMapHandle;
public uint MapOffset;
public uint GpuOffset;
public uint Pages;
}
}

View file

@ -0,0 +1,9 @@
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types
{
struct UnmapBufferArguments
{
#pragma warning disable CS0649
public ulong Offset;
#pragma warning restore CS0649
}
}

View file

@ -0,0 +1,574 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.Gpu;
using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types;
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl;
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap;
using Ryujinx.HLE.HOS.Services.Nv.Types;
using Ryujinx.Memory;
using System;
using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel
{
class NvHostChannelDeviceFile : NvDeviceFile
{
private static readonly ConcurrentDictionary<ulong, Host1xContext> _host1xContextRegistry = new();
private const uint MaxModuleSyncpoint = 16;
private uint _timeout;
private uint _submitTimeout;
private uint _timeslice;
private readonly Switch _device;
private readonly IVirtualMemoryManager _memory;
private readonly Host1xContext _host1xContext;
private readonly long _contextId;
public GpuChannel Channel { get; }
public enum ResourcePolicy
{
Device,
Channel
}
protected static uint[] DeviceSyncpoints = new uint[MaxModuleSyncpoint];
protected uint[] ChannelSyncpoints;
protected static ResourcePolicy ChannelResourcePolicy = ResourcePolicy.Device;
private NvFence _channelSyncpoint;
public NvHostChannelDeviceFile(ServiceCtx context, IVirtualMemoryManager memory, ulong owner) : base(context, owner)
{
_device = context.Device;
_memory = memory;
_timeout = 3000;
_submitTimeout = 0;
_timeslice = 0;
_host1xContext = GetHost1XContext(context.Device.Gpu, owner);
_contextId = _host1xContext.Host1x.CreateContext();
Channel = _device.Gpu.CreateChannel();
ChannelInitialization.InitializeState(Channel);
ChannelSyncpoints = new uint[MaxModuleSyncpoint];
_channelSyncpoint.Id = _device.System.HostSyncpoint.AllocateSyncpoint(false);
_channelSyncpoint.UpdateValue(_device.System.HostSyncpoint);
}
public override NvInternalResult Ioctl(NvIoctl command, Span<byte> arguments)
{
NvInternalResult result = NvInternalResult.NotImplemented;
if (command.Type == NvIoctl.NvHostCustomMagic)
{
switch (command.Number)
{
case 0x01:
result = Submit(arguments);
break;
case 0x02:
result = CallIoctlMethod<GetParameterArguments>(GetSyncpoint, arguments);
break;
case 0x03:
result = CallIoctlMethod<GetParameterArguments>(GetWaitBase, arguments);
break;
case 0x07:
result = CallIoctlMethod<uint>(SetSubmitTimeout, arguments);
break;
case 0x09:
result = MapCommandBuffer(arguments);
break;
case 0x0a:
result = UnmapCommandBuffer(arguments);
break;
}
}
else if (command.Type == NvIoctl.NvHostMagic)
{
switch (command.Number)
{
case 0x01:
result = CallIoctlMethod<int>(SetNvMapFd, arguments);
break;
case 0x03:
result = CallIoctlMethod<uint>(SetTimeout, arguments);
break;
case 0x08:
result = SubmitGpfifo(arguments);
break;
case 0x09:
result = CallIoctlMethod<AllocObjCtxArguments>(AllocObjCtx, arguments);
break;
case 0x0b:
result = CallIoctlMethod<ZcullBindArguments>(ZcullBind, arguments);
break;
case 0x0c:
result = CallIoctlMethod<SetErrorNotifierArguments>(SetErrorNotifier, arguments);
break;
case 0x0d:
result = CallIoctlMethod<NvChannelPriority>(SetPriority, arguments);
break;
case 0x18:
result = CallIoctlMethod<AllocGpfifoExArguments>(AllocGpfifoEx, arguments);
break;
case 0x1a:
result = CallIoctlMethod<AllocGpfifoExArguments>(AllocGpfifoEx2, arguments);
break;
case 0x1d:
result = CallIoctlMethod<uint>(SetTimeslice, arguments);
break;
}
}
else if (command.Type == NvIoctl.NvGpuMagic)
{
switch (command.Number)
{
case 0x14:
result = CallIoctlMethod<ulong>(SetUserData, arguments);
break;
}
}
return result;
}
private NvInternalResult Submit(Span<byte> arguments)
{
SubmitArguments submitHeader = GetSpanAndSkip<SubmitArguments>(ref arguments, 1)[0];
Span<CommandBuffer> commandBuffers = GetSpanAndSkip<CommandBuffer>(ref arguments, submitHeader.CmdBufsCount);
Span<Reloc> relocs = GetSpanAndSkip<Reloc>(ref arguments, submitHeader.RelocsCount);
Span<uint> relocShifts = GetSpanAndSkip<uint>(ref arguments, submitHeader.RelocsCount);
Span<SyncptIncr> syncptIncrs = GetSpanAndSkip<SyncptIncr>(ref arguments, submitHeader.SyncptIncrsCount);
Span<uint> fenceThresholds = GetSpanAndSkip<uint>(ref arguments, submitHeader.FencesCount);
lock (_device)
{
for (int i = 0; i < syncptIncrs.Length; i++)
{
SyncptIncr syncptIncr = syncptIncrs[i];
uint id = syncptIncr.Id;
fenceThresholds[i] = Context.Device.System.HostSyncpoint.IncrementSyncpointMax(id, syncptIncr.Incrs);
}
foreach (CommandBuffer commandBuffer in commandBuffers)
{
NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, commandBuffer.Mem);
var data = _memory.GetSpan(map.Address + commandBuffer.Offset, commandBuffer.WordsCount * 4);
_host1xContext.Host1x.Submit(MemoryMarshal.Cast<byte, int>(data), _contextId);
}
}
return NvInternalResult.Success;
}
private Span<T> GetSpanAndSkip<T>(ref Span<byte> arguments, int count) where T : unmanaged
{
Span<T> output = MemoryMarshal.Cast<byte, T>(arguments).Slice(0, count);
arguments = arguments.Slice(Unsafe.SizeOf<T>() * count);
return output;
}
private NvInternalResult GetSyncpoint(ref GetParameterArguments arguments)
{
if (arguments.Parameter >= MaxModuleSyncpoint)
{
return NvInternalResult.InvalidInput;
}
if (ChannelResourcePolicy == ResourcePolicy.Device)
{
arguments.Value = GetSyncpointDevice(_device.System.HostSyncpoint, arguments.Parameter, false);
}
else
{
arguments.Value = GetSyncpointChannel(arguments.Parameter, false);
}
if (arguments.Value == 0)
{
return NvInternalResult.TryAgain;
}
return NvInternalResult.Success;
}
private NvInternalResult GetWaitBase(ref GetParameterArguments arguments)
{
arguments.Value = 0;
Logger.Stub?.PrintStub(LogClass.ServiceNv);
return NvInternalResult.Success;
}
private NvInternalResult SetSubmitTimeout(ref uint submitTimeout)
{
_submitTimeout = submitTimeout;
Logger.Stub?.PrintStub(LogClass.ServiceNv);
return NvInternalResult.Success;
}
private NvInternalResult MapCommandBuffer(Span<byte> arguments)
{
int headerSize = Unsafe.SizeOf<MapCommandBufferArguments>();
MapCommandBufferArguments commandBufferHeader = MemoryMarshal.Cast<byte, MapCommandBufferArguments>(arguments)[0];
Span<CommandBufferHandle> commandBufferEntries = MemoryMarshal.Cast<byte, CommandBufferHandle>(arguments.Slice(headerSize)).Slice(0, commandBufferHeader.NumEntries);
foreach (ref CommandBufferHandle commandBufferEntry in commandBufferEntries)
{
NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, commandBufferEntry.MapHandle);
if (map == null)
{
Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{commandBufferEntry.MapHandle:x8}!");
return NvInternalResult.InvalidInput;
}
lock (map)
{
if (map.DmaMapAddress == 0)
{
ulong va = _host1xContext.MemoryAllocator.GetFreeAddress((ulong)map.Size, out ulong freeAddressStartPosition, 1, MemoryManager.PageSize);
if (va != NvMemoryAllocator.PteUnmapped && va <= uint.MaxValue && (va + (uint)map.Size) <= uint.MaxValue)
{
_host1xContext.MemoryAllocator.AllocateRange(va, (uint)map.Size, freeAddressStartPosition);
_host1xContext.Smmu.Map(map.Address, va, (uint)map.Size, PteKind.Pitch); // FIXME: This should not use the GMMU.
map.DmaMapAddress = va;
}
else
{
map.DmaMapAddress = NvMemoryAllocator.PteUnmapped;
}
}
commandBufferEntry.MapAddress = (int)map.DmaMapAddress;
}
}
return NvInternalResult.Success;
}
private NvInternalResult UnmapCommandBuffer(Span<byte> arguments)
{
int headerSize = Unsafe.SizeOf<MapCommandBufferArguments>();
MapCommandBufferArguments commandBufferHeader = MemoryMarshal.Cast<byte, MapCommandBufferArguments>(arguments)[0];
Span<CommandBufferHandle> commandBufferEntries = MemoryMarshal.Cast<byte, CommandBufferHandle>(arguments.Slice(headerSize)).Slice(0, commandBufferHeader.NumEntries);
foreach (ref CommandBufferHandle commandBufferEntry in commandBufferEntries)
{
NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, commandBufferEntry.MapHandle);
if (map == null)
{
Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{commandBufferEntry.MapHandle:x8}!");
return NvInternalResult.InvalidInput;
}
lock (map)
{
if (map.DmaMapAddress != 0)
{
// FIXME:
// To make unmapping work, we need separate address space per channel.
// Right now NVDEC and VIC share the GPU address space which is not correct at all.
// _host1xContext.MemoryAllocator.Free((ulong)map.DmaMapAddress, (uint)map.Size);
// map.DmaMapAddress = 0;
}
}
}
return NvInternalResult.Success;
}
private NvInternalResult SetNvMapFd(ref int nvMapFd)
{
Logger.Stub?.PrintStub(LogClass.ServiceNv);
return NvInternalResult.Success;
}
private NvInternalResult SetTimeout(ref uint timeout)
{
_timeout = timeout;
Logger.Stub?.PrintStub(LogClass.ServiceNv);
return NvInternalResult.Success;
}
private NvInternalResult SubmitGpfifo(Span<byte> arguments)
{
int headerSize = Unsafe.SizeOf<SubmitGpfifoArguments>();
SubmitGpfifoArguments gpfifoSubmissionHeader = MemoryMarshal.Cast<byte, SubmitGpfifoArguments>(arguments)[0];
Span<ulong> gpfifoEntries = MemoryMarshal.Cast<byte, ulong>(arguments.Slice(headerSize)).Slice(0, gpfifoSubmissionHeader.NumEntries);
return SubmitGpfifo(ref gpfifoSubmissionHeader, gpfifoEntries);
}
private NvInternalResult AllocObjCtx(ref AllocObjCtxArguments arguments)
{
Logger.Stub?.PrintStub(LogClass.ServiceNv);
return NvInternalResult.Success;
}
private NvInternalResult ZcullBind(ref ZcullBindArguments arguments)
{
Logger.Stub?.PrintStub(LogClass.ServiceNv);
return NvInternalResult.Success;
}
private NvInternalResult SetErrorNotifier(ref SetErrorNotifierArguments arguments)
{
Logger.Stub?.PrintStub(LogClass.ServiceNv);
return NvInternalResult.Success;
}
private NvInternalResult SetPriority(ref NvChannelPriority priority)
{
switch (priority)
{
case NvChannelPriority.Low:
_timeslice = 1300; // Timeslice low priority in micro-seconds
break;
case NvChannelPriority.Medium:
_timeslice = 2600; // Timeslice medium priority in micro-seconds
break;
case NvChannelPriority.High:
_timeslice = 5200; // Timeslice high priority in micro-seconds
break;
default:
return NvInternalResult.InvalidInput;
}
Logger.Stub?.PrintStub(LogClass.ServiceNv);
// TODO: disable and preempt channel when GPU scheduler will be implemented.
return NvInternalResult.Success;
}
private NvInternalResult AllocGpfifoEx(ref AllocGpfifoExArguments arguments)
{
_channelSyncpoint.UpdateValue(_device.System.HostSyncpoint);
arguments.Fence = _channelSyncpoint;
Logger.Stub?.PrintStub(LogClass.ServiceNv);
return NvInternalResult.Success;
}
private NvInternalResult AllocGpfifoEx2(ref AllocGpfifoExArguments arguments)
{
_channelSyncpoint.UpdateValue(_device.System.HostSyncpoint);
arguments.Fence = _channelSyncpoint;
Logger.Stub?.PrintStub(LogClass.ServiceNv);
return NvInternalResult.Success;
}
private NvInternalResult SetTimeslice(ref uint timeslice)
{
if (timeslice < 1000 || timeslice > 50000)
{
return NvInternalResult.InvalidInput;
}
_timeslice = timeslice; // in micro-seconds
Logger.Stub?.PrintStub(LogClass.ServiceNv);
// TODO: disable and preempt channel when GPU scheduler will be implemented.
return NvInternalResult.Success;
}
private NvInternalResult SetUserData(ref ulong userData)
{
Logger.Stub?.PrintStub(LogClass.ServiceNv);
return NvInternalResult.Success;
}
protected NvInternalResult SubmitGpfifo(ref SubmitGpfifoArguments header, Span<ulong> entries)
{
if (header.Flags.HasFlag(SubmitGpfifoFlags.FenceWait) && header.Flags.HasFlag(SubmitGpfifoFlags.IncrementWithValue))
{
return NvInternalResult.InvalidInput;
}
if (header.Flags.HasFlag(SubmitGpfifoFlags.FenceWait) && !_device.System.HostSyncpoint.IsSyncpointExpired(header.Fence.Id, header.Fence.Value))
{
Channel.PushHostCommandBuffer(CreateWaitCommandBuffer(header.Fence));
}
header.Fence.Id = _channelSyncpoint.Id;
if (header.Flags.HasFlag(SubmitGpfifoFlags.FenceIncrement) || header.Flags.HasFlag(SubmitGpfifoFlags.IncrementWithValue))
{
uint incrementCount = header.Flags.HasFlag(SubmitGpfifoFlags.FenceIncrement) ? 2u : 0u;
if (header.Flags.HasFlag(SubmitGpfifoFlags.IncrementWithValue))
{
incrementCount += header.Fence.Value;
}
header.Fence.Value = _device.System.HostSyncpoint.IncrementSyncpointMaxExt(header.Fence.Id, (int)incrementCount);
}
else
{
header.Fence.Value = _device.System.HostSyncpoint.ReadSyncpointMaxValue(header.Fence.Id);
}
Channel.PushEntries(entries);
if (header.Flags.HasFlag(SubmitGpfifoFlags.FenceIncrement))
{
Channel.PushHostCommandBuffer(CreateIncrementCommandBuffer(ref header.Fence, header.Flags));
}
header.Flags = SubmitGpfifoFlags.None;
_device.Gpu.GPFifo.SignalNewEntries();
return NvInternalResult.Success;
}
public uint GetSyncpointChannel(uint index, bool isClientManaged)
{
if (ChannelSyncpoints[index] != 0)
{
return ChannelSyncpoints[index];
}
ChannelSyncpoints[index] = _device.System.HostSyncpoint.AllocateSyncpoint(isClientManaged);
return ChannelSyncpoints[index];
}
public static uint GetSyncpointDevice(NvHostSyncpt syncpointManager, uint index, bool isClientManaged)
{
if (DeviceSyncpoints[index] != 0)
{
return DeviceSyncpoints[index];
}
DeviceSyncpoints[index] = syncpointManager.AllocateSyncpoint(isClientManaged);
return DeviceSyncpoints[index];
}
private static int[] CreateWaitCommandBuffer(NvFence fence)
{
int[] commandBuffer = new int[4];
// SyncpointValue = fence.Value;
commandBuffer[0] = 0x2001001C;
commandBuffer[1] = (int)fence.Value;
// SyncpointAction(fence.id, increment: false, switch_en: true);
commandBuffer[2] = 0x2001001D;
commandBuffer[3] = (((int)fence.Id << 8) | (0 << 0) | (1 << 4));
return commandBuffer;
}
private int[] CreateIncrementCommandBuffer(ref NvFence fence, SubmitGpfifoFlags flags)
{
bool hasWfi = !flags.HasFlag(SubmitGpfifoFlags.SuppressWfi);
int[] commandBuffer;
int offset = 0;
if (hasWfi)
{
commandBuffer = new int[8];
// WaitForInterrupt(handle)
commandBuffer[offset++] = 0x2001001E;
commandBuffer[offset++] = 0x0;
}
else
{
commandBuffer = new int[6];
}
// SyncpointValue = 0x0;
commandBuffer[offset++] = 0x2001001C;
commandBuffer[offset++] = 0x0;
// Increment the syncpoint 2 times. (mitigate a hardware bug)
// SyncpointAction(fence.id, increment: true, switch_en: false);
commandBuffer[offset++] = 0x2001001D;
commandBuffer[offset++] = (((int)fence.Id << 8) | (1 << 0) | (0 << 4));
// SyncpointAction(fence.id, increment: true, switch_en: false);
commandBuffer[offset++] = 0x2001001D;
commandBuffer[offset++] = (((int)fence.Id << 8) | (1 << 0) | (0 << 4));
return commandBuffer;
}
public override void Close()
{
_host1xContext.Host1x.DestroyContext(_contextId);
Channel.Dispose();
for (int i = 0; i < MaxModuleSyncpoint; i++)
{
if (ChannelSyncpoints[i] != 0)
{
_device.System.HostSyncpoint.ReleaseSyncpoint(ChannelSyncpoints[i]);
ChannelSyncpoints[i] = 0;
}
}
_device.System.HostSyncpoint.ReleaseSyncpoint(_channelSyncpoint.Id);
_channelSyncpoint.Id = 0;
}
private static Host1xContext GetHost1XContext(GpuContext gpu, ulong pid)
{
return _host1xContextRegistry.GetOrAdd(pid, (ulong key) => new Host1xContext(gpu, key));
}
public static void Destroy()
{
foreach (Host1xContext host1xContext in _host1xContextRegistry.Values)
{
host1xContext.Dispose();
}
_host1xContextRegistry.Clear();
}
}
}

View file

@ -0,0 +1,105 @@
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types;
using Ryujinx.Horizon.Common;
using Ryujinx.Memory;
using System;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel
{
internal class NvHostGpuDeviceFile : NvHostChannelDeviceFile
{
private KEvent _smExceptionBptIntReportEvent;
private KEvent _smExceptionBptPauseReportEvent;
private KEvent _errorNotifierEvent;
private int _smExceptionBptIntReportEventHandle;
private int _smExceptionBptPauseReportEventHandle;
private int _errorNotifierEventHandle;
public NvHostGpuDeviceFile(ServiceCtx context, IVirtualMemoryManager memory, ulong owner) : base(context, memory, owner)
{
_smExceptionBptIntReportEvent = CreateEvent(context, out _smExceptionBptIntReportEventHandle);
_smExceptionBptPauseReportEvent = CreateEvent(context, out _smExceptionBptPauseReportEventHandle);
_errorNotifierEvent = CreateEvent(context, out _errorNotifierEventHandle);
}
private static KEvent CreateEvent(ServiceCtx context, out int handle)
{
KEvent evnt = new KEvent(context.Device.System.KernelContext);
if (context.Process.HandleTable.GenerateHandle(evnt.ReadableEvent, out handle) != Result.Success)
{
throw new InvalidOperationException("Out of handles!");
}
return evnt;
}
public override NvInternalResult Ioctl2(NvIoctl command, Span<byte> arguments, Span<byte> inlineInBuffer)
{
NvInternalResult result = NvInternalResult.NotImplemented;
if (command.Type == NvIoctl.NvHostMagic)
{
switch (command.Number)
{
case 0x1b:
result = CallIoctlMethod<SubmitGpfifoArguments, ulong>(SubmitGpfifoEx, arguments, inlineInBuffer);
break;
}
}
return result;
}
public override NvInternalResult QueryEvent(out int eventHandle, uint eventId)
{
// TODO: accurately represent and implement those events.
switch (eventId)
{
case 0x1:
eventHandle = _smExceptionBptIntReportEventHandle;
break;
case 0x2:
eventHandle = _smExceptionBptPauseReportEventHandle;
break;
case 0x3:
eventHandle = _errorNotifierEventHandle;
break;
default:
eventHandle = 0;
break;
}
return eventHandle != 0 ? NvInternalResult.Success : NvInternalResult.InvalidInput;
}
private NvInternalResult SubmitGpfifoEx(ref SubmitGpfifoArguments arguments, Span<ulong> inlineData)
{
return SubmitGpfifo(ref arguments, inlineData);
}
public override void Close()
{
if (_smExceptionBptIntReportEventHandle != 0)
{
Context.Process.HandleTable.CloseHandle(_smExceptionBptIntReportEventHandle);
_smExceptionBptIntReportEventHandle = 0;
}
if (_smExceptionBptPauseReportEventHandle != 0)
{
Context.Process.HandleTable.CloseHandle(_smExceptionBptPauseReportEventHandle);
_smExceptionBptPauseReportEventHandle = 0;
}
if (_errorNotifierEventHandle != 0)
{
Context.Process.HandleTable.CloseHandle(_errorNotifierEventHandle);
_errorNotifierEventHandle = 0;
}
base.Close();
}
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -0,0 +1,11 @@
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel
{
class NvChannel
{
#pragma warning disable CS0649
public int Timeout;
public int SubmitTimeout;
public int Timeslice;
#pragma warning restore CS0649
}
}

View file

@ -0,0 +1,9 @@
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel
{
enum NvChannelPriority : uint
{
Low = 50,
Medium = 100,
High = 150
}
}

View file

@ -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;
}
}

View file

@ -0,0 +1,40 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types
{
[StructLayout(LayoutKind.Sequential)]
struct CommandBuffer
{
public int Mem;
public uint Offset;
public int WordsCount;
}
[StructLayout(LayoutKind.Sequential)]
struct Reloc
{
public int CmdbufMem;
public int CmdbufOffset;
public int Target;
public int TargetOffset;
}
[StructLayout(LayoutKind.Sequential)]
struct SyncptIncr
{
public uint Id;
public uint Incrs;
public uint Reserved1;
public uint Reserved2;
public uint Reserved3;
}
[StructLayout(LayoutKind.Sequential)]
struct SubmitArguments
{
public int CmdBufsCount;
public int RelocsCount;
public int SyncptIncrsCount;
public int FencesCount;
}
}

View file

@ -0,0 +1,14 @@
using Ryujinx.HLE.HOS.Services.Nv.Types;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types
{
[StructLayout(LayoutKind.Sequential)]
struct SubmitGpfifoArguments
{
public long Address;
public int NumEntries;
public SubmitGpfifoFlags Flags;
public NvFence Fence;
}
}

View file

@ -0,0 +1,15 @@
using System;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types
{
[Flags]
enum SubmitGpfifoFlags : uint
{
None,
FenceWait = 1 << 0,
FenceIncrement = 1 << 1,
HwFormat = 1 << 2,
SuppressWfi = 1 << 4,
IncrementWithValue = 1 << 8,
}
}

View file

@ -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;
}
}

View file

@ -0,0 +1,540 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.Gpu.Synchronization;
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl.Types;
using Ryujinx.HLE.HOS.Services.Nv.Types;
using Ryujinx.HLE.HOS.Services.Settings;
using Ryujinx.Memory;
using System;
using System.Text;
using System.Threading;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl
{
internal class NvHostCtrlDeviceFile : NvDeviceFile
{
public const int EventsCount = 64;
private bool _isProductionMode;
private Switch _device;
private NvHostEvent[] _events;
public NvHostCtrlDeviceFile(ServiceCtx context, IVirtualMemoryManager memory, ulong owner) : base(context, owner)
{
if (NxSettings.Settings.TryGetValue("nv!rmos_set_production_mode", out object productionModeSetting))
{
_isProductionMode = ((string)productionModeSetting) != "0"; // Default value is ""
}
else
{
_isProductionMode = true;
}
_device = context.Device;
_events = new NvHostEvent[EventsCount];
}
public override NvInternalResult Ioctl(NvIoctl command, Span<byte> arguments)
{
NvInternalResult result = NvInternalResult.NotImplemented;
if (command.Type == NvIoctl.NvHostCustomMagic)
{
switch (command.Number)
{
case 0x14:
result = CallIoctlMethod<NvFence>(SyncptRead, arguments);
break;
case 0x15:
result = CallIoctlMethod<uint>(SyncptIncr, arguments);
break;
case 0x16:
result = CallIoctlMethod<SyncptWaitArguments>(SyncptWait, arguments);
break;
case 0x19:
result = CallIoctlMethod<SyncptWaitExArguments>(SyncptWaitEx, arguments);
break;
case 0x1a:
result = CallIoctlMethod<NvFence>(SyncptReadMax, arguments);
break;
case 0x1b:
// As Marshal cannot handle unaligned arrays, we do everything by hand here.
GetConfigurationArguments configArgument = GetConfigurationArguments.FromSpan(arguments);
result = GetConfig(configArgument);
if (result == NvInternalResult.Success)
{
configArgument.CopyTo(arguments);
}
break;
case 0x1c:
result = CallIoctlMethod<uint>(EventSignal, arguments);
break;
case 0x1d:
result = CallIoctlMethod<EventWaitArguments>(EventWait, arguments);
break;
case 0x1e:
result = CallIoctlMethod<EventWaitArguments>(EventWaitAsync, arguments);
break;
case 0x1f:
result = CallIoctlMethod<uint>(EventRegister, arguments);
break;
case 0x20:
result = CallIoctlMethod<uint>(EventUnregister, arguments);
break;
case 0x21:
result = CallIoctlMethod<ulong>(EventKill, arguments);
break;
}
}
return result;
}
private int QueryEvent(uint eventId)
{
lock (_events)
{
uint eventSlot;
uint syncpointId;
if ((eventId >> 28) == 1)
{
eventSlot = eventId & 0xFFFF;
syncpointId = (eventId >> 16) & 0xFFF;
}
else
{
eventSlot = eventId & 0xFF;
syncpointId = eventId >> 4;
}
if (eventSlot >= EventsCount || _events[eventSlot] == null || _events[eventSlot].Fence.Id != syncpointId)
{
return 0;
}
return _events[eventSlot].EventHandle;
}
}
public override NvInternalResult QueryEvent(out int eventHandle, uint eventId)
{
eventHandle = QueryEvent(eventId);
return eventHandle != 0 ? NvInternalResult.Success : NvInternalResult.InvalidInput;
}
private NvInternalResult SyncptRead(ref NvFence arguments)
{
return SyncptReadMinOrMax(ref arguments, max: false);
}
private NvInternalResult SyncptIncr(ref uint id)
{
if (id >= SynchronizationManager.MaxHardwareSyncpoints)
{
return NvInternalResult.InvalidInput;
}
_device.System.HostSyncpoint.Increment(id);
return NvInternalResult.Success;
}
private NvInternalResult SyncptWait(ref SyncptWaitArguments arguments)
{
uint dummyValue = 0;
return EventWait(ref arguments.Fence, ref dummyValue, arguments.Timeout, isWaitEventAsyncCmd: false, isWaitEventCmd: false);
}
private NvInternalResult SyncptWaitEx(ref SyncptWaitExArguments arguments)
{
return EventWait(ref arguments.Input.Fence, ref arguments.Value, arguments.Input.Timeout, isWaitEventAsyncCmd: false, isWaitEventCmd: false);
}
private NvInternalResult SyncptReadMax(ref NvFence arguments)
{
return SyncptReadMinOrMax(ref arguments, max: true);
}
private NvInternalResult GetConfig(GetConfigurationArguments arguments)
{
if (!_isProductionMode && NxSettings.Settings.TryGetValue($"{arguments.Domain}!{arguments.Parameter}".ToLower(), out object nvSetting))
{
byte[] settingBuffer = new byte[0x101];
if (nvSetting is string stringValue)
{
if (stringValue.Length > 0x100)
{
Logger.Error?.Print(LogClass.ServiceNv, $"{arguments.Domain}!{arguments.Parameter} String value size is too big!");
}
else
{
settingBuffer = Encoding.ASCII.GetBytes(stringValue + "\0");
}
}
else if (nvSetting is int intValue)
{
settingBuffer = BitConverter.GetBytes(intValue);
}
else if (nvSetting is bool boolValue)
{
settingBuffer[0] = boolValue ? (byte)1 : (byte)0;
}
else
{
throw new NotImplementedException(nvSetting.GetType().Name);
}
Logger.Debug?.Print(LogClass.ServiceNv, $"Got setting {arguments.Domain}!{arguments.Parameter}");
arguments.Configuration = settingBuffer;
return NvInternalResult.Success;
}
// NOTE: This actually return NotAvailableInProduction but this is directly translated as a InvalidInput before returning the ioctl.
//return NvInternalResult.NotAvailableInProduction;
return NvInternalResult.InvalidInput;
}
private NvInternalResult EventWait(ref EventWaitArguments arguments)
{
return EventWait(ref arguments.Fence, ref arguments.Value, arguments.Timeout, isWaitEventAsyncCmd: false, isWaitEventCmd: true);
}
private NvInternalResult EventWaitAsync(ref EventWaitArguments arguments)
{
return EventWait(ref arguments.Fence, ref arguments.Value, arguments.Timeout, isWaitEventAsyncCmd: true, isWaitEventCmd: false);
}
private NvInternalResult EventRegister(ref uint userEventId)
{
lock (_events)
{
NvInternalResult result = EventUnregister(ref userEventId);
if (result == NvInternalResult.Success)
{
_events[userEventId] = new NvHostEvent(_device.System.HostSyncpoint, userEventId, _device.System);
}
return result;
}
}
private NvInternalResult EventUnregister(ref uint userEventId)
{
lock (_events)
{
if (userEventId >= EventsCount)
{
return NvInternalResult.InvalidInput;
}
NvHostEvent hostEvent = _events[userEventId];
if (hostEvent == null)
{
return NvInternalResult.Success;
}
if (hostEvent.State == NvHostEventState.Available ||
hostEvent.State == NvHostEventState.Cancelled ||
hostEvent.State == NvHostEventState.Signaled)
{
_events[userEventId].CloseEvent(Context);
_events[userEventId] = null;
return NvInternalResult.Success;
}
return NvInternalResult.Busy;
}
}
private NvInternalResult EventKill(ref ulong eventMask)
{
lock (_events)
{
NvInternalResult result = NvInternalResult.Success;
for (uint eventId = 0; eventId < EventsCount; eventId++)
{
if ((eventMask & (1UL << (int)eventId)) != 0)
{
NvInternalResult tmp = EventUnregister(ref eventId);
if (tmp != NvInternalResult.Success)
{
result = tmp;
}
}
}
return result;
}
}
private NvInternalResult EventSignal(ref uint userEventId)
{
uint eventId = userEventId & ushort.MaxValue;
if (eventId >= EventsCount)
{
return NvInternalResult.InvalidInput;
}
lock (_events)
{
NvHostEvent hostEvent = _events[eventId];
if (hostEvent == null)
{
return NvInternalResult.InvalidInput;
}
hostEvent.Cancel(_device.Gpu);
_device.System.HostSyncpoint.UpdateMin(hostEvent.Fence.Id);
return NvInternalResult.Success;
}
}
private NvInternalResult SyncptReadMinOrMax(ref NvFence arguments, bool max)
{
if (arguments.Id >= SynchronizationManager.MaxHardwareSyncpoints)
{
return NvInternalResult.InvalidInput;
}
if (max)
{
arguments.Value = _device.System.HostSyncpoint.ReadSyncpointMaxValue(arguments.Id);
}
else
{
arguments.Value = _device.System.HostSyncpoint.ReadSyncpointValue(arguments.Id);
}
return NvInternalResult.Success;
}
private NvInternalResult EventWait(ref NvFence fence, ref uint value, int timeout, bool isWaitEventAsyncCmd, bool isWaitEventCmd)
{
if (fence.Id >= SynchronizationManager.MaxHardwareSyncpoints)
{
return NvInternalResult.InvalidInput;
}
// First try to check if the syncpoint is already expired on the CPU side
if (_device.System.HostSyncpoint.IsSyncpointExpired(fence.Id, fence.Value))
{
value = _device.System.HostSyncpoint.ReadSyncpointMinValue(fence.Id);
return NvInternalResult.Success;
}
// Try to invalidate the CPU cache and check for expiration again.
uint newCachedSyncpointValue = _device.System.HostSyncpoint.UpdateMin(fence.Id);
// Has the fence already expired?
if (_device.System.HostSyncpoint.IsSyncpointExpired(fence.Id, fence.Value))
{
value = newCachedSyncpointValue;
return NvInternalResult.Success;
}
// If the timeout is 0, directly return.
if (timeout == 0)
{
return NvInternalResult.TryAgain;
}
// The syncpoint value isn't at the fence yet, we need to wait.
if (!isWaitEventAsyncCmd)
{
value = 0;
}
NvHostEvent hostEvent;
NvInternalResult result;
uint eventIndex;
lock (_events)
{
if (isWaitEventAsyncCmd)
{
eventIndex = value;
if (eventIndex >= EventsCount)
{
return NvInternalResult.InvalidInput;
}
hostEvent = _events[eventIndex];
}
else
{
hostEvent = GetFreeEventLocked(fence.Id, out eventIndex);
}
if (hostEvent != null)
{
lock (hostEvent.Lock)
{
if (hostEvent.State == NvHostEventState.Available ||
hostEvent.State == NvHostEventState.Signaled ||
hostEvent.State == NvHostEventState.Cancelled)
{
bool timedOut = hostEvent.Wait(_device.Gpu, fence);
if (timedOut)
{
if (isWaitEventCmd)
{
value = ((fence.Id & 0xfff) << 16) | 0x10000000;
}
else
{
value = fence.Id << 4;
}
value |= eventIndex;
result = NvInternalResult.TryAgain;
}
else
{
value = fence.Value;
return NvInternalResult.Success;
}
}
else
{
Logger.Error?.Print(LogClass.ServiceNv, $"Invalid Event at index {eventIndex} (isWaitEventAsyncCmd: {isWaitEventAsyncCmd}, isWaitEventCmd: {isWaitEventCmd})");
if (hostEvent != null)
{
Logger.Error?.Print(LogClass.ServiceNv, hostEvent.DumpState(_device.Gpu));
}
result = NvInternalResult.InvalidInput;
}
}
}
else
{
Logger.Error?.Print(LogClass.ServiceNv, $"Invalid Event at index {eventIndex} (isWaitEventAsyncCmd: {isWaitEventAsyncCmd}, isWaitEventCmd: {isWaitEventCmd})");
result = NvInternalResult.InvalidInput;
}
}
return result;
}
private NvHostEvent GetFreeEventLocked(uint id, out uint eventIndex)
{
eventIndex = EventsCount;
uint nullIndex = EventsCount;
for (uint index = 0; index < EventsCount; index++)
{
NvHostEvent Event = _events[index];
if (Event != null)
{
if (Event.State == NvHostEventState.Available ||
Event.State == NvHostEventState.Signaled ||
Event.State == NvHostEventState.Cancelled)
{
eventIndex = index;
if (Event.Fence.Id == id)
{
return Event;
}
}
}
else if (nullIndex == EventsCount)
{
nullIndex = index;
}
}
if (nullIndex < EventsCount)
{
eventIndex = nullIndex;
EventRegister(ref eventIndex);
return _events[nullIndex];
}
if (eventIndex < EventsCount)
{
return _events[eventIndex];
}
return null;
}
public override void Close()
{
Logger.Warning?.Print(LogClass.ServiceNv, "Closing channel");
lock (_events)
{
// If the device file need to be closed, cancel all user events and dispose events.
for (int i = 0; i < _events.Length; i++)
{
NvHostEvent evnt = _events[i];
if (evnt != null)
{
lock (evnt.Lock)
{
if (evnt.State == NvHostEventState.Waiting)
{
evnt.State = NvHostEventState.Cancelling;
evnt.Cancel(_device.Gpu);
}
else if (evnt.State == NvHostEventState.Signaling)
{
// Wait at max 9ms if the guest app is trying to signal the event while closing it..
int retryCount = 0;
do
{
if (retryCount++ > 9)
{
break;
}
// TODO: This should be handled by the kernel (reschedule the current thread ect), waiting for Kernel decoupling work.
Thread.Sleep(1);
} while (evnt.State != NvHostEventState.Signaled);
}
evnt.CloseEvent(Context);
_events[i] = null;
}
}
}
}
}
}
}

View file

@ -0,0 +1,13 @@
using Ryujinx.HLE.HOS.Services.Nv.Types;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl.Types
{
[StructLayout(LayoutKind.Sequential)]
struct EventWaitArguments
{
public NvFence Fence;
public int Timeout;
public uint Value;
}
}

View file

@ -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));
}
}
}

View file

@ -0,0 +1,185 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.Gpu;
using Ryujinx.Graphics.Gpu.Synchronization;
using Ryujinx.HLE.HOS.Kernel;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Nv.Types;
using Ryujinx.Horizon.Common;
using System;
using System.Threading;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl
{
class NvHostEvent
{
public NvFence Fence;
public NvHostEventState State;
public KEvent Event;
public int EventHandle;
private uint _eventId;
private NvHostSyncpt _syncpointManager;
private SyncpointWaiterHandle _waiterInformation;
private NvFence _previousFailingFence;
private uint _failingCount;
public readonly object Lock = new object();
/// <summary>
/// Max failing count until waiting on CPU.
/// FIXME: This seems enough for most of the cases, reduce if needed.
/// </summary>
private const uint FailingCountMax = 2;
public NvHostEvent(NvHostSyncpt syncpointManager, uint eventId, Horizon system)
{
Fence.Id = 0;
State = NvHostEventState.Available;
Event = new KEvent(system.KernelContext);
if (KernelStatic.GetCurrentProcess().HandleTable.GenerateHandle(Event.ReadableEvent, out EventHandle) != Result.Success)
{
throw new InvalidOperationException("Out of handles!");
}
_eventId = eventId;
_syncpointManager = syncpointManager;
ResetFailingState();
}
private void ResetFailingState()
{
_previousFailingFence.Id = NvFence.InvalidSyncPointId;
_previousFailingFence.Value = 0;
_failingCount = 0;
}
private void Signal()
{
lock (Lock)
{
NvHostEventState oldState = State;
State = NvHostEventState.Signaling;
if (oldState == NvHostEventState.Waiting)
{
Event.WritableEvent.Signal();
}
State = NvHostEventState.Signaled;
}
}
private void GpuSignaled(SyncpointWaiterHandle waiterInformation)
{
lock (Lock)
{
// If the signal does not match our current waiter,
// then it is from a past fence and we should just ignore it.
if (waiterInformation != null && waiterInformation != _waiterInformation)
{
return;
}
ResetFailingState();
Signal();
}
}
public void Cancel(GpuContext gpuContext)
{
lock (Lock)
{
NvHostEventState oldState = State;
State = NvHostEventState.Cancelling;
if (oldState == NvHostEventState.Waiting && _waiterInformation != null)
{
gpuContext.Synchronization.UnregisterCallback(Fence.Id, _waiterInformation);
_waiterInformation = null;
if (_previousFailingFence.Id == Fence.Id && _previousFailingFence.Value == Fence.Value)
{
_failingCount++;
}
else
{
_failingCount = 1;
_previousFailingFence = Fence;
}
}
State = NvHostEventState.Cancelled;
Event.WritableEvent.Clear();
}
}
public bool Wait(GpuContext gpuContext, NvFence fence)
{
lock (Lock)
{
// NOTE: nvservices code should always wait on the GPU side.
// If we do this, we may get an abort or undefined behaviour when the GPU processing thread is blocked for a long period (for example, during shader compilation).
// The reason for this is that the NVN code will try to wait until giving up.
// This is done by trying to wait and signal multiple times until aborting after you are past the timeout.
// As such, if it fails too many time, we enforce a wait on the CPU side indefinitely.
// This allows to keep GPU and CPU in sync when we are slow.
if (_failingCount == FailingCountMax)
{
Logger.Warning?.Print(LogClass.ServiceNv, "GPU processing thread is too slow, waiting on CPU...");
Fence.Wait(gpuContext, Timeout.InfiniteTimeSpan);
ResetFailingState();
return false;
}
else
{
Fence = fence;
State = NvHostEventState.Waiting;
_waiterInformation = gpuContext.Synchronization.RegisterCallbackOnSyncpoint(Fence.Id, Fence.Value, GpuSignaled);
return true;
}
}
}
public string DumpState(GpuContext gpuContext)
{
string res = $"\nNvHostEvent {_eventId}:\n";
res += $"\tState: {State}\n";
if (State == NvHostEventState.Waiting)
{
res += "\tFence:\n";
res += $"\t\tId : {Fence.Id}\n";
res += $"\t\tThreshold : {Fence.Value}\n";
res += $"\t\tCurrent Value : {gpuContext.Synchronization.GetSyncpointValue(Fence.Id)}\n";
res += $"\t\tWaiter Valid : {_waiterInformation != null}\n";
}
return res;
}
public void CloseEvent(ServiceCtx context)
{
if (EventHandle != 0)
{
context.Process.HandleTable.CloseHandle(EventHandle);
EventHandle = 0;
}
}
}
}

View file

@ -0,0 +1,12 @@
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl
{
enum NvHostEventState
{
Available = 0,
Waiting = 1,
Cancelling = 2,
Signaling = 3,
Signaled = 4,
Cancelled = 5
}
}

View file

@ -0,0 +1,199 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.Gpu.Synchronization;
using System;
using System.Threading;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl
{
class NvHostSyncpt
{
public const int VBlank0SyncpointId = 26;
public const int VBlank1SyncpointId = 27;
private int[] _counterMin;
private int[] _counterMax;
private bool[] _clientManaged;
private bool[] _assigned;
private Switch _device;
private object _syncpointAllocatorLock = new object();
public NvHostSyncpt(Switch device)
{
_device = device;
_counterMin = new int[SynchronizationManager.MaxHardwareSyncpoints];
_counterMax = new int[SynchronizationManager.MaxHardwareSyncpoints];
_clientManaged = new bool[SynchronizationManager.MaxHardwareSyncpoints];
_assigned = new bool[SynchronizationManager.MaxHardwareSyncpoints];
// Reserve VBLANK syncpoints
ReserveSyncpointLocked(VBlank0SyncpointId, true);
ReserveSyncpointLocked(VBlank1SyncpointId, true);
}
private void ReserveSyncpointLocked(uint id, bool isClientManaged)
{
if (id >= SynchronizationManager.MaxHardwareSyncpoints || _assigned[id])
{
throw new ArgumentOutOfRangeException(nameof(id));
}
_assigned[id] = true;
_clientManaged[id] = isClientManaged;
}
public uint AllocateSyncpoint(bool isClientManaged)
{
lock (_syncpointAllocatorLock)
{
for (uint i = 1; i < SynchronizationManager.MaxHardwareSyncpoints; i++)
{
if (!_assigned[i])
{
ReserveSyncpointLocked(i, isClientManaged);
return i;
}
}
}
Logger.Error?.Print(LogClass.ServiceNv, "Cannot allocate a new syncpoint!");
return 0;
}
public void ReleaseSyncpoint(uint id)
{
if (id == 0)
{
return;
}
lock (_syncpointAllocatorLock)
{
if (id >= SynchronizationManager.MaxHardwareSyncpoints || !_assigned[id])
{
throw new ArgumentOutOfRangeException(nameof(id));
}
_assigned[id] = false;
_clientManaged[id] = false;
SetSyncpointMinEqualSyncpointMax(id);
}
}
public void SetSyncpointMinEqualSyncpointMax(uint id)
{
if (id >= SynchronizationManager.MaxHardwareSyncpoints)
{
throw new ArgumentOutOfRangeException(nameof(id));
}
int value = (int)ReadSyncpointValue(id);
Interlocked.Exchange(ref _counterMax[id], value);
}
public uint ReadSyncpointValue(uint id)
{
return UpdateMin(id);
}
public uint ReadSyncpointMinValue(uint id)
{
return (uint)_counterMin[id];
}
public uint ReadSyncpointMaxValue(uint id)
{
return (uint)_counterMax[id];
}
private bool IsClientManaged(uint id)
{
if (id >= SynchronizationManager.MaxHardwareSyncpoints)
{
return false;
}
return _clientManaged[id];
}
public void Increment(uint id)
{
if (IsClientManaged(id))
{
IncrementSyncpointMax(id);
}
IncrementSyncpointGPU(id);
}
public uint UpdateMin(uint id)
{
uint newValue = _device.Gpu.Synchronization.GetSyncpointValue(id);
Interlocked.Exchange(ref _counterMin[id], (int)newValue);
return newValue;
}
private void IncrementSyncpointGPU(uint id)
{
_device.Gpu.Synchronization.IncrementSyncpoint(id);
}
public void IncrementSyncpointMin(uint id)
{
Interlocked.Increment(ref _counterMin[id]);
}
public uint IncrementSyncpointMaxExt(uint id, int count)
{
if (count == 0)
{
return ReadSyncpointMaxValue(id);
}
uint result = 0;
for (int i = 0; i < count; i++)
{
result = IncrementSyncpointMax(id);
}
return result;
}
private uint IncrementSyncpointMax(uint id)
{
return (uint)Interlocked.Increment(ref _counterMax[id]);
}
public uint IncrementSyncpointMax(uint id, uint incrs)
{
return (uint)Interlocked.Add(ref _counterMax[id], (int)incrs);
}
public bool IsSyncpointExpired(uint id, uint threshold)
{
return MinCompare(id, _counterMin[id], _counterMax[id], (int)threshold);
}
private bool MinCompare(uint id, int min, int max, int threshold)
{
int minDiff = min - threshold;
int maxDiff = max - threshold;
if (IsClientManaged(id))
{
return minDiff >= 0;
}
else
{
return (uint)maxDiff >= (uint)minDiff;
}
}
}
}

View file

@ -0,0 +1,12 @@
using Ryujinx.HLE.HOS.Services.Nv.Types;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl.Types
{
[StructLayout(LayoutKind.Sequential)]
struct SyncptWaitArguments
{
public NvFence Fence;
public int Timeout;
}
}

View file

@ -0,0 +1,11 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl.Types
{
[StructLayout(LayoutKind.Sequential)]
struct SyncptWaitExArguments
{
public SyncptWaitArguments Input;
public uint Value;
}
}

View file

@ -0,0 +1,239 @@
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types;
using Ryujinx.Horizon.Common;
using Ryujinx.Memory;
using System;
using System.Diagnostics;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu
{
class NvHostCtrlGpuDeviceFile : NvDeviceFile
{
private static Stopwatch _pTimer = new Stopwatch();
private static double _ticksToNs = (1.0 / Stopwatch.Frequency) * 1_000_000_000;
private KEvent _errorEvent;
private KEvent _unknownEvent;
public NvHostCtrlGpuDeviceFile(ServiceCtx context, IVirtualMemoryManager memory, ulong owner) : base(context, owner)
{
_errorEvent = new KEvent(context.Device.System.KernelContext);
_unknownEvent = new KEvent(context.Device.System.KernelContext);
}
static NvHostCtrlGpuDeviceFile()
{
_pTimer.Start();
}
public override NvInternalResult Ioctl(NvIoctl command, Span<byte> arguments)
{
NvInternalResult result = NvInternalResult.NotImplemented;
if (command.Type == NvIoctl.NvGpuMagic)
{
switch (command.Number)
{
case 0x01:
result = CallIoctlMethod<ZcullGetCtxSizeArguments>(ZcullGetCtxSize, arguments);
break;
case 0x02:
result = CallIoctlMethod<ZcullGetInfoArguments>(ZcullGetInfo, arguments);
break;
case 0x03:
result = CallIoctlMethod<ZbcSetTableArguments>(ZbcSetTable, arguments);
break;
case 0x05:
result = CallIoctlMethod<GetCharacteristicsArguments>(GetCharacteristics, arguments);
break;
case 0x06:
result = CallIoctlMethod<GetTpcMasksArguments>(GetTpcMasks, arguments);
break;
case 0x14:
result = CallIoctlMethod<GetActiveSlotMaskArguments>(GetActiveSlotMask, arguments);
break;
case 0x1c:
result = CallIoctlMethod<GetGpuTimeArguments>(GetGpuTime, arguments);
break;
}
}
return result;
}
public override NvInternalResult Ioctl3(NvIoctl command, Span<byte> arguments, Span<byte> inlineOutBuffer)
{
NvInternalResult result = NvInternalResult.NotImplemented;
if (command.Type == NvIoctl.NvGpuMagic)
{
switch (command.Number)
{
case 0x05:
result = CallIoctlMethod<GetCharacteristicsArguments, GpuCharacteristics>(GetCharacteristics, arguments, inlineOutBuffer);
break;
case 0x06:
result = CallIoctlMethod<GetTpcMasksArguments, int>(GetTpcMasks, arguments, inlineOutBuffer);
break;
}
}
return result;
}
public override NvInternalResult QueryEvent(out int eventHandle, uint eventId)
{
// TODO: accurately represent and implement those events.
KEvent targetEvent = null;
switch (eventId)
{
case 0x1:
targetEvent = _errorEvent;
break;
case 0x2:
targetEvent = _unknownEvent;
break;
}
if (targetEvent != null)
{
if (Context.Process.HandleTable.GenerateHandle(targetEvent.ReadableEvent, out eventHandle) != Result.Success)
{
throw new InvalidOperationException("Out of handles!");
}
}
else
{
eventHandle = 0;
return NvInternalResult.InvalidInput;
}
return NvInternalResult.Success;
}
public override void Close() { }
private NvInternalResult ZcullGetCtxSize(ref ZcullGetCtxSizeArguments arguments)
{
arguments.Size = 1;
return NvInternalResult.Success;
}
private NvInternalResult ZcullGetInfo(ref ZcullGetInfoArguments arguments)
{
arguments.WidthAlignPixels = 0x20;
arguments.HeightAlignPixels = 0x20;
arguments.PixelSquaresByAliquots = 0x400;
arguments.AliquotTotal = 0x800;
arguments.RegionByteMultiplier = 0x20;
arguments.RegionHeaderSize = 0x20;
arguments.SubregionHeaderSize = 0xc0;
arguments.SubregionWidthAlignPixels = 0x20;
arguments.SubregionHeightAlignPixels = 0x40;
arguments.SubregionCount = 0x10;
return NvInternalResult.Success;
}
private NvInternalResult ZbcSetTable(ref ZbcSetTableArguments arguments)
{
Logger.Stub?.PrintStub(LogClass.ServiceNv);
return NvInternalResult.Success;
}
private NvInternalResult GetCharacteristics(ref GetCharacteristicsArguments arguments)
{
return GetCharacteristics(ref arguments, ref arguments.Characteristics);
}
private NvInternalResult GetCharacteristics(ref GetCharacteristicsArguments arguments, ref GpuCharacteristics characteristics)
{
arguments.Header.BufferSize = 0xa0;
characteristics.Arch = 0x120;
characteristics.Impl = 0xb;
characteristics.Rev = 0xa1;
characteristics.NumGpc = 0x1;
characteristics.L2CacheSize = 0x40000;
characteristics.OnBoardVideoMemorySize = 0x0;
characteristics.NumTpcPerGpc = 0x2;
characteristics.BusType = 0x20;
characteristics.BigPageSize = 0x20000;
characteristics.CompressionPageSize = 0x20000;
characteristics.PdeCoverageBitCount = 0x1b;
characteristics.AvailableBigPageSizes = 0x30000;
characteristics.GpcMask = 0x1;
characteristics.SmArchSmVersion = 0x503;
characteristics.SmArchSpaVersion = 0x503;
characteristics.SmArchWarpCount = 0x80;
characteristics.GpuVaBitCount = 0x28;
characteristics.Reserved = 0x0;
characteristics.Flags = 0x55;
characteristics.TwodClass = 0x902d;
characteristics.ThreedClass = 0xb197;
characteristics.ComputeClass = 0xb1c0;
characteristics.GpfifoClass = 0xb06f;
characteristics.InlineToMemoryClass = 0xa140;
characteristics.DmaCopyClass = 0xb0b5;
characteristics.MaxFbpsCount = 0x1;
characteristics.FbpEnMask = 0x0;
characteristics.MaxLtcPerFbp = 0x2;
characteristics.MaxLtsPerLtc = 0x1;
characteristics.MaxTexPerTpc = 0x0;
characteristics.MaxGpcCount = 0x1;
characteristics.RopL2EnMask0 = 0x21d70;
characteristics.RopL2EnMask1 = 0x0;
characteristics.ChipName = 0x6230326d67;
characteristics.GrCompbitStoreBaseHw = 0x0;
arguments.Characteristics = characteristics;
return NvInternalResult.Success;
}
private NvInternalResult GetTpcMasks(ref GetTpcMasksArguments arguments)
{
return GetTpcMasks(ref arguments, ref arguments.TpcMask);
}
private NvInternalResult GetTpcMasks(ref GetTpcMasksArguments arguments, ref int tpcMask)
{
if (arguments.MaskBufferSize != 0)
{
tpcMask = 3;
arguments.TpcMask = tpcMask;
}
return NvInternalResult.Success;
}
private NvInternalResult GetActiveSlotMask(ref GetActiveSlotMaskArguments arguments)
{
Logger.Stub?.PrintStub(LogClass.ServiceNv);
arguments.Slot = 0x07;
arguments.Mask = 0x01;
return NvInternalResult.Success;
}
private NvInternalResult GetGpuTime(ref GetGpuTimeArguments arguments)
{
arguments.Timestamp = GetPTimerNanoSeconds();
return NvInternalResult.Success;
}
private static ulong GetPTimerNanoSeconds()
{
double ticks = _pTimer.ElapsedTicks;
return (ulong)(ticks * _ticksToNs) & 0xff_ffff_ffff_ffff;
}
}
}

View file

@ -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;
}
}

View file

@ -0,0 +1,59 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types
{
[StructLayout(LayoutKind.Sequential)]
struct GpuCharacteristics
{
public int Arch;
public int Impl;
public int Rev;
public int NumGpc;
public long L2CacheSize;
public long OnBoardVideoMemorySize;
public int NumTpcPerGpc;
public int BusType;
public int BigPageSize;
public int CompressionPageSize;
public int PdeCoverageBitCount;
public int AvailableBigPageSizes;
public int GpcMask;
public int SmArchSmVersion;
public int SmArchSpaVersion;
public int SmArchWarpCount;
public int GpuVaBitCount;
public int Reserved;
public long Flags;
public int TwodClass;
public int ThreedClass;
public int ComputeClass;
public int GpfifoClass;
public int InlineToMemoryClass;
public int DmaCopyClass;
public int MaxFbpsCount;
public int FbpEnMask;
public int MaxLtcPerFbp;
public int MaxLtsPerLtc;
public int MaxTexPerTpc;
public int MaxGpcCount;
public int RopL2EnMask0;
public int RopL2EnMask1;
public long ChipName;
public long GrCompbitStoreBaseHw;
}
struct CharacteristicsHeader
{
#pragma warning disable CS0649
public long BufferSize;
public long BufferAddress;
#pragma warning restore CS0649
}
[StructLayout(LayoutKind.Sequential)]
struct GetCharacteristicsArguments
{
public CharacteristicsHeader Header;
public GpuCharacteristics Characteristics;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -0,0 +1,49 @@
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types
{
[StructLayout(LayoutKind.Sequential)]
struct ZbcColorArray
{
private uint element0;
private uint element1;
private uint element2;
private uint element3;
public uint this[int index]
{
get
{
if (index == 0)
{
return element0;
}
else if (index == 1)
{
return element1;
}
else if (index == 2)
{
return element2;
}
else if (index == 2)
{
return element3;
}
throw new IndexOutOfRangeException();
}
}
}
[StructLayout(LayoutKind.Sequential)]
struct ZbcSetTableArguments
{
public ZbcColorArray ColorDs;
public ZbcColorArray ColorL2;
public uint Depth;
public uint Format;
public uint Type;
}
}

View file

@ -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;
}
}

View file

@ -0,0 +1,19 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types
{
[StructLayout(LayoutKind.Sequential)]
struct ZcullGetInfoArguments
{
public int WidthAlignPixels;
public int HeightAlignPixels;
public int PixelSquaresByAliquots;
public int AliquotTotal;
public int RegionByteMultiplier;
public int RegionHeaderSize;
public int SubregionHeaderSize;
public int SubregionWidthAlignPixels;
public int SubregionHeightAlignPixels;
public int SubregionCount;
}
}

View file

@ -0,0 +1,11 @@
using Ryujinx.Memory;
using System;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostDbgGpu
{
class NvHostDbgGpuDeviceFile : NvDeviceFile
{
public NvHostDbgGpuDeviceFile(ServiceCtx context, IVirtualMemoryManager memory, ulong owner) : base(context, owner) { }
public override void Close() { }
}
}

View file

@ -0,0 +1,11 @@
using Ryujinx.Memory;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostProfGpu
{
class NvHostProfGpuDeviceFile : NvDeviceFile
{
public NvHostProfGpuDeviceFile(ServiceCtx context, IVirtualMemoryManager memory, ulong owner) : base(context, owner) { }
public override void Close() { }
}
}

View file

@ -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,
}
}

View file

@ -0,0 +1,272 @@
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Memory;
using System;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
{
internal class NvMapDeviceFile : NvDeviceFile
{
private const int FlagNotFreedYet = 1;
private static NvMapIdDictionary _maps = new NvMapIdDictionary();
public NvMapDeviceFile(ServiceCtx context, IVirtualMemoryManager memory, ulong owner) : base(context, owner)
{
}
public override NvInternalResult Ioctl(NvIoctl command, Span<byte> arguments)
{
NvInternalResult result = NvInternalResult.NotImplemented;
if (command.Type == NvIoctl.NvMapCustomMagic)
{
switch (command.Number)
{
case 0x01:
result = CallIoctlMethod<NvMapCreate>(Create, arguments);
break;
case 0x03:
result = CallIoctlMethod<NvMapFromId>(FromId, arguments);
break;
case 0x04:
result = CallIoctlMethod<NvMapAlloc>(Alloc, arguments);
break;
case 0x05:
result = CallIoctlMethod<NvMapFree>(Free, arguments);
break;
case 0x09:
result = CallIoctlMethod<NvMapParam>(Param, arguments);
break;
case 0x0e:
result = CallIoctlMethod<NvMapGetId>(GetId, arguments);
break;
case 0x02:
case 0x06:
case 0x07:
case 0x08:
case 0x0a:
case 0x0c:
case 0x0d:
case 0x0f:
case 0x10:
case 0x11:
result = NvInternalResult.NotSupported;
break;
}
}
return result;
}
private NvInternalResult Create(ref NvMapCreate arguments)
{
if (arguments.Size == 0)
{
Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid size 0x{arguments.Size:x8}!");
return NvInternalResult.InvalidInput;
}
int size = BitUtils.AlignUp(arguments.Size, (int)MemoryManager.PageSize);
arguments.Handle = CreateHandleFromMap(new NvMapHandle(size));
Logger.Debug?.Print(LogClass.ServiceNv, $"Created map {arguments.Handle} with size 0x{size:x8}!");
return NvInternalResult.Success;
}
private NvInternalResult FromId(ref NvMapFromId arguments)
{
NvMapHandle map = GetMapFromHandle(Owner, arguments.Id);
if (map == null)
{
Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{arguments.Handle:x8}!");
return NvInternalResult.InvalidInput;
}
map.IncrementRefCount();
arguments.Handle = arguments.Id;
return NvInternalResult.Success;
}
private NvInternalResult Alloc(ref NvMapAlloc arguments)
{
NvMapHandle map = GetMapFromHandle(Owner, arguments.Handle);
if (map == null)
{
Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{arguments.Handle:x8}!");
return NvInternalResult.InvalidInput;
}
if ((arguments.Align & (arguments.Align - 1)) != 0)
{
Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid alignment 0x{arguments.Align:x8}!");
return NvInternalResult.InvalidInput;
}
if ((uint)arguments.Align < MemoryManager.PageSize)
{
arguments.Align = (int)MemoryManager.PageSize;
}
NvInternalResult result = NvInternalResult.Success;
if (!map.Allocated)
{
map.Allocated = true;
map.Align = arguments.Align;
map.Kind = (byte)arguments.Kind;
int size = BitUtils.AlignUp(map.Size, (int)MemoryManager.PageSize);
ulong address = arguments.Address;
if (address == 0)
{
// When the address is zero, we need to allocate
// our own backing memory for the NvMap.
// TODO: Is this allocation inside the transfer memory?
result = NvInternalResult.OutOfMemory;
}
if (result == NvInternalResult.Success)
{
map.Size = size;
map.Address = address;
}
}
return result;
}
private NvInternalResult Free(ref NvMapFree arguments)
{
NvMapHandle map = GetMapFromHandle(Owner, arguments.Handle);
if (map == null)
{
Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{arguments.Handle:x8}!");
return NvInternalResult.InvalidInput;
}
if (DecrementMapRefCount(Owner, arguments.Handle))
{
arguments.Address = map.Address;
arguments.Flags = 0;
}
else
{
arguments.Address = 0;
arguments.Flags = FlagNotFreedYet;
}
arguments.Size = map.Size;
return NvInternalResult.Success;
}
private NvInternalResult Param(ref NvMapParam arguments)
{
NvMapHandle map = GetMapFromHandle(Owner, arguments.Handle);
if (map == null)
{
Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{arguments.Handle:x8}!");
return NvInternalResult.InvalidInput;
}
switch (arguments.Param)
{
case NvMapHandleParam.Size: arguments.Result = map.Size; break;
case NvMapHandleParam.Align: arguments.Result = map.Align; break;
case NvMapHandleParam.Heap: arguments.Result = 0x40000000; break;
case NvMapHandleParam.Kind: arguments.Result = map.Kind; break;
case NvMapHandleParam.Compr: arguments.Result = 0; break;
// Note: Base is not supported and returns an error.
// Any other value also returns an error.
default: return NvInternalResult.InvalidInput;
}
return NvInternalResult.Success;
}
private NvInternalResult GetId(ref NvMapGetId arguments)
{
NvMapHandle map = GetMapFromHandle(Owner, arguments.Handle);
if (map == null)
{
Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{arguments.Handle:x8}!");
return NvInternalResult.InvalidInput;
}
arguments.Id = arguments.Handle;
return NvInternalResult.Success;
}
public override void Close()
{
// TODO: refcount NvMapDeviceFile instances and remove when closing
// _maps.TryRemove(GetOwner(), out _);
}
private int CreateHandleFromMap(NvMapHandle map)
{
return _maps.Add(map);
}
private static bool DeleteMapWithHandle(ulong pid, int handle)
{
return _maps.Delete(handle) != null;
}
public static void IncrementMapRefCount(ulong pid, int handle)
{
GetMapFromHandle(pid, handle)?.IncrementRefCount();
}
public static bool DecrementMapRefCount(ulong pid, int handle)
{
NvMapHandle map = GetMapFromHandle(pid, handle);
if (map == null)
{
return false;
}
if (map.DecrementRefCount() <= 0)
{
DeleteMapWithHandle(pid, handle);
Logger.Debug?.Print(LogClass.ServiceNv, $"Deleted map {handle}!");
return true;
}
else
{
return false;
}
}
public static NvMapHandle GetMapFromHandle(ulong pid, int handle)
{
return _maps.Get(handle);
}
}
}

View file

@ -0,0 +1,15 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
{
[StructLayout(LayoutKind.Sequential)]
struct NvMapAlloc
{
public int Handle;
public int HeapMask;
public int Flags;
public int Align;
public long Kind;
public ulong Address;
}
}

View file

@ -0,0 +1,11 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
{
[StructLayout(LayoutKind.Sequential)]
struct NvMapCreate
{
public int Size;
public int Handle;
}
}

View file

@ -0,0 +1,14 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
{
[StructLayout(LayoutKind.Sequential)]
struct NvMapFree
{
public int Handle;
public int Padding;
public ulong Address;
public int Size;
public int Flags;
}
}

View file

@ -0,0 +1,11 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
{
[StructLayout(LayoutKind.Sequential)]
struct NvMapFromId
{
public int Id;
public int Handle;
}
}

View file

@ -0,0 +1,11 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
{
[StructLayout(LayoutKind.Sequential)]
struct NvMapGetId
{
public int Id;
public int Handle;
}
}

View file

@ -0,0 +1,40 @@
using System.Threading;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
{
class NvMapHandle
{
#pragma warning disable CS0649
public int Handle;
public int Id;
#pragma warning restore CS0649
public int Size;
public int Align;
public int Kind;
public ulong Address;
public bool Allocated;
public ulong DmaMapAddress;
private long _dupes;
public NvMapHandle()
{
_dupes = 1;
}
public NvMapHandle(int size) : this()
{
Size = size;
}
public void IncrementRefCount()
{
Interlocked.Increment(ref _dupes);
}
public long DecrementRefCount()
{
return Interlocked.Decrement(ref _dupes);
}
}
}

View file

@ -0,0 +1,12 @@
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
{
enum NvMapHandleParam : int
{
Size = 1,
Align = 2,
Base = 3,
Heap = 4,
Kind = 5,
Compr = 6
}
}

View file

@ -0,0 +1,61 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
{
class NvMapIdDictionary
{
private readonly ConcurrentDictionary<int, NvMapHandle> _nvmapHandles;
private int _id;
public ICollection<NvMapHandle> Values => _nvmapHandles.Values;
public NvMapIdDictionary()
{
_nvmapHandles = new ConcurrentDictionary<int, NvMapHandle>();
}
public int Add(NvMapHandle handle)
{
int id = Interlocked.Add(ref _id, 4);
if (id != 0 && _nvmapHandles.TryAdd(id, handle))
{
return id;
}
throw new InvalidOperationException("NvMap ID overflow.");
}
public NvMapHandle Get(int id)
{
if (_nvmapHandles.TryGetValue(id, out NvMapHandle handle))
{
return handle;
}
return null;
}
public NvMapHandle Delete(int id)
{
if (_nvmapHandles.TryRemove(id, out NvMapHandle handle))
{
return handle;
}
return null;
}
public ICollection<NvMapHandle> Clear()
{
ICollection<NvMapHandle> values = _nvmapHandles.Values;
_nvmapHandles.Clear();
return values;
}
}
}

View file

@ -0,0 +1,12 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
{
[StructLayout(LayoutKind.Sequential)]
struct NvMapParam
{
public int Handle;
public NvMapHandleParam Param;
public int Result;
}
}

View file

@ -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);
}
}

View file

@ -0,0 +1,310 @@
using Ryujinx.Common.Collections;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.Gpu.Memory;
using System;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.Services.Nv
{
class NvMemoryAllocator
{
private const ulong AddressSpaceSize = 1UL << 40;
private const ulong DefaultStart = 1UL << 32;
private const ulong InvalidAddress = 0;
private const ulong PageSize = MemoryManager.PageSize;
private const ulong PageMask = MemoryManager.PageMask;
public const ulong PteUnmapped = MemoryManager.PteUnmapped;
// Key --> Start Address of Region
// Value --> End Address of Region
private readonly TreeDictionary<ulong, ulong> _tree = new TreeDictionary<ulong, ulong>();
private readonly Dictionary<ulong, LinkedListNode<ulong>> _dictionary = new Dictionary<ulong, LinkedListNode<ulong>>();
private readonly LinkedList<ulong> _list = new LinkedList<ulong>();
public NvMemoryAllocator()
{
_tree.Add(PageSize, AddressSpaceSize);
LinkedListNode<ulong> node = _list.AddFirst(PageSize);
_dictionary[PageSize] = node;
}
/// <summary>
/// Marks a range of memory as consumed by removing it from the tree.
/// This function will split memory regions if there is available space.
/// </summary>
/// <param name="va">Virtual address at which to allocate</param>
/// <param name="size">Size of the allocation in bytes</param>
/// <param name="referenceAddress">Reference to the address of memory where the allocation can take place</param>
#region Memory Allocation
public void AllocateRange(ulong va, ulong size, ulong referenceAddress = InvalidAddress)
{
lock (_tree)
{
Logger.Debug?.Print(LogClass.ServiceNv, $"Allocating range from 0x{va:X} to 0x{(va + size):X}.");
if (referenceAddress != InvalidAddress)
{
ulong endAddress = va + size;
ulong referenceEndAddress = _tree.Get(referenceAddress);
if (va >= referenceAddress)
{
// Need Left Node
if (va > referenceAddress)
{
ulong leftEndAddress = va;
// Overwrite existing block with its new smaller range.
_tree.Add(referenceAddress, leftEndAddress);
Logger.Debug?.Print(LogClass.ServiceNv, $"Created smaller address range from 0x{referenceAddress:X} to 0x{leftEndAddress:X}.");
}
else
{
// We need to get rid of the large chunk.
_tree.Remove(referenceAddress);
}
ulong rightSize = referenceEndAddress - endAddress;
// If leftover space, create a right node.
if (rightSize > 0)
{
Logger.Debug?.Print(LogClass.ServiceNv, $"Created smaller address range from 0x{endAddress:X} to 0x{referenceEndAddress:X}.");
_tree.Add(endAddress, referenceEndAddress);
LinkedListNode<ulong> node = _list.AddAfter(_dictionary[referenceAddress], endAddress);
_dictionary[endAddress] = node;
}
if (va == referenceAddress)
{
_list.Remove(_dictionary[referenceAddress]);
_dictionary.Remove(referenceAddress);
}
}
}
}
}
/// <summary>
/// Marks a range of memory as free by adding it to the tree.
/// This function will automatically compact the tree when it determines there are multiple ranges of free memory adjacent to each other.
/// </summary>
/// <param name="va">Virtual address at which to deallocate</param>
/// <param name="size">Size of the allocation in bytes</param>
public void DeallocateRange(ulong va, ulong size)
{
lock (_tree)
{
Logger.Debug?.Print(LogClass.ServiceNv, $"Deallocating address range from 0x{va:X} to 0x{(va + size):X}.");
ulong freeAddressStartPosition = _tree.Floor(va);
if (freeAddressStartPosition != InvalidAddress)
{
LinkedListNode<ulong> node = _dictionary[freeAddressStartPosition];
ulong targetPrevAddress = _dictionary[freeAddressStartPosition].Previous != null ? _dictionary[_dictionary[freeAddressStartPosition].Previous.Value].Value : InvalidAddress;
ulong targetNextAddress = _dictionary[freeAddressStartPosition].Next != null ? _dictionary[_dictionary[freeAddressStartPosition].Next.Value].Value : InvalidAddress;
ulong expandedStart = va;
ulong expandedEnd = va + size;
while (targetPrevAddress != InvalidAddress)
{
ulong prevAddress = targetPrevAddress;
ulong prevEndAddress = _tree.Get(targetPrevAddress);
if (prevEndAddress >= expandedStart)
{
expandedStart = targetPrevAddress;
LinkedListNode<ulong> prevPtr = _dictionary[prevAddress];
if (prevPtr.Previous != null)
{
targetPrevAddress = prevPtr.Previous.Value;
}
else
{
targetPrevAddress = InvalidAddress;
}
node = node.Previous;
_tree.Remove(prevAddress);
_list.Remove(_dictionary[prevAddress]);
_dictionary.Remove(prevAddress);
}
else
{
break;
}
}
while (targetNextAddress != InvalidAddress)
{
ulong nextAddress = targetNextAddress;
ulong nextEndAddress = _tree.Get(targetNextAddress);
if (nextAddress <= expandedEnd)
{
expandedEnd = Math.Max(expandedEnd, nextEndAddress);
LinkedListNode<ulong> nextPtr = _dictionary[nextAddress];
if (nextPtr.Next != null)
{
targetNextAddress = nextPtr.Next.Value;
}
else
{
targetNextAddress = InvalidAddress;
}
_tree.Remove(nextAddress);
_list.Remove(_dictionary[nextAddress]);
_dictionary.Remove(nextAddress);
}
else
{
break;
}
}
Logger.Debug?.Print(LogClass.ServiceNv, $"Deallocation resulted in new free range from 0x{expandedStart:X} to 0x{expandedEnd:X}.");
_tree.Add(expandedStart, expandedEnd);
LinkedListNode<ulong> nodePtr = _list.AddAfter(node, expandedStart);
_dictionary[expandedStart] = nodePtr;
}
}
}
/// <summary>
/// Gets the address of an unused (free) region of the specified size.
/// </summary>
/// <param name="size">Size of the region in bytes</param>
/// <param name="freeAddressStartPosition">Position at which memory can be allocated</param>
/// <param name="alignment">Required alignment of the region address in bytes</param>
/// <param name="start">Start address of the search on the address space</param>
/// <returns>GPU virtual address of the allocation, or an all ones mask in case of failure</returns>
public ulong GetFreeAddress(ulong size, out ulong freeAddressStartPosition, ulong alignment = 1, ulong start = DefaultStart)
{
// Note: Address 0 is not considered valid by the driver,
// when 0 is returned it's considered a mapping error.
lock (_tree)
{
Logger.Debug?.Print(LogClass.ServiceNv, $"Searching for a free address @ 0x{start:X} of size 0x{size:X}.");
ulong address = start;
if (alignment == 0)
{
alignment = 1;
}
alignment = (alignment + PageMask) & ~PageMask;
if (address < AddressSpaceSize)
{
bool reachedEndOfAddresses = false;
ulong targetAddress;
if (start == DefaultStart)
{
Logger.Debug?.Print(LogClass.ServiceNv, $"Target address set to start of the last available range: 0x{_list.Last.Value:X}.");
targetAddress = _list.Last.Value;
}
else
{
targetAddress = _tree.Floor(address);
Logger.Debug?.Print(LogClass.ServiceNv, $"Target address set to floor of 0x{address:X}; resulted in 0x{targetAddress:X}.");
if (targetAddress == InvalidAddress)
{
targetAddress = _tree.Ceiling(address);
Logger.Debug?.Print(LogClass.ServiceNv, $"Target address was invalid, set to ceiling of 0x{address:X}; resulted in 0x{targetAddress:X}");
}
}
while (address < AddressSpaceSize)
{
if (targetAddress != InvalidAddress)
{
if (address >= targetAddress)
{
if (address + size <= _tree.Get(targetAddress))
{
Logger.Debug?.Print(LogClass.ServiceNv, $"Found a suitable free address range from 0x{targetAddress:X} to 0x{_tree.Get(targetAddress):X} for 0x{address:X}.");
freeAddressStartPosition = targetAddress;
return address;
}
else
{
Logger.Debug?.Print(LogClass.ServiceNv, "Address requirements exceeded the available space in the target range.");
LinkedListNode<ulong> nextPtr = _dictionary[targetAddress];
if (nextPtr.Next != null)
{
targetAddress = nextPtr.Next.Value;
Logger.Debug?.Print(LogClass.ServiceNv, $"Moved search to successor range starting at 0x{targetAddress:X}.");
}
else
{
if (reachedEndOfAddresses)
{
Logger.Debug?.Print(LogClass.ServiceNv, "Exiting loop, a full pass has already been completed w/ no suitable free address range.");
break;
}
else
{
reachedEndOfAddresses = true;
address = start;
targetAddress = _tree.Floor(address);
Logger.Debug?.Print(LogClass.ServiceNv, $"Reached the end of the available free ranges, restarting loop @ 0x{targetAddress:X} for 0x{address:X}.");
}
}
}
}
else
{
address += PageSize * (targetAddress / PageSize - (address / PageSize));
ulong remainder = address % alignment;
if (remainder != 0)
{
address = (address - remainder) + alignment;
}
Logger.Debug?.Print(LogClass.ServiceNv, $"Reset and aligned address to {address:X}.");
if (address + size > AddressSpaceSize && !reachedEndOfAddresses)
{
reachedEndOfAddresses = true;
address = start;
targetAddress = _tree.Floor(address);
Logger.Debug?.Print(LogClass.ServiceNv, $"Address requirements exceeded the capacity of available address space, restarting loop @ 0x{targetAddress:X} for 0x{address:X}.");
}
}
}
else
{
break;
}
}
}
Logger.Debug?.Print(LogClass.ServiceNv, $"No suitable address range found; returning: 0x{InvalidAddress:X}.");
freeAddressStartPosition = InvalidAddress;
}
return PteUnmapped;
}
/// <summary>
/// Checks if a given memory region is mapped or reserved.
/// </summary>
/// <param name="gpuVa">GPU virtual address of the page</param>
/// <param name="size">Size of the allocation in bytes</param>
/// <param name="freeAddressStartPosition">Nearest lower address that memory can be allocated</param>
/// <returns>True if the page is mapped or reserved, false otherwise</returns>
public bool IsRegionInUse(ulong gpuVa, ulong size, out ulong freeAddressStartPosition)
{
lock (_tree)
{
ulong floorAddress = _tree.Floor(gpuVa);
freeAddressStartPosition = floorAddress;
if (floorAddress != InvalidAddress)
{
return !(gpuVa >= floorAddress && ((gpuVa + size) <= _tree.Get(floorAddress)));
}
}
return true;
}
#endregion
}
}

View file

@ -0,0 +1,41 @@
using Ryujinx.Graphics.Gpu;
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl;
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nv.Types
{
[StructLayout(LayoutKind.Sequential, Size = 0x8)]
internal struct NvFence
{
public const uint InvalidSyncPointId = uint.MaxValue;
public uint Id;
public uint Value;
public bool IsValid()
{
return Id != InvalidSyncPointId;
}
public void UpdateValue(NvHostSyncpt hostSyncpt)
{
Value = hostSyncpt.ReadSyncpointValue(Id);
}
public void Increment(GpuContext gpuContext)
{
Value = gpuContext.Synchronization.IncrementSyncpoint(Id);
}
public bool Wait(GpuContext gpuContext, TimeSpan timeout)
{
if (IsValid())
{
return gpuContext.Synchronization.WaitOnSyncpoint(Id, Value, timeout);
}
return false;
}
}
}

View file

@ -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();
}
}
}

View file

@ -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();
}
}
}

View file

@ -0,0 +1,30 @@
namespace Ryujinx.HLE.HOS.Services.Nv
{
enum NvResult : uint
{
Success = 0,
NotImplemented = 1,
NotSupported = 2,
NotInitialized = 3,
InvalidParameter = 4,
Timeout = 5,
InsufficientMemory = 6,
ReadOnlyAttribute = 7,
InvalidState = 8,
InvalidAddress = 9,
InvalidSize = 10,
InvalidValue = 11,
AlreadyAllocated = 13,
Busy = 14,
ResourceError = 15,
CountMismatch = 16,
SharedMemoryTooSmall = 0x1000,
FileOperationFailed = 0x30003,
DirectoryOperationFailed = 0x30004,
NotAvailableInProduction = 0x30006,
IoctlFailed = 0x3000F,
AccessDenied = 0x30010,
FileNotFound = 0x30013,
ModuleNotPresent = 0xA000E,
}
}

View file

@ -0,0 +1,15 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nv.Types
{
[StructLayout(LayoutKind.Sequential, Size = 0x20)]
struct NvStatus
{
public uint MemoryValue1;
public uint MemoryValue2;
public uint MemoryValue3;
public uint MemoryValue4;
public long Padding1;
public long Padding2;
}
}