Move solution and projects to src
This commit is contained in:
parent
cd124bda58
commit
cee7121058
3466 changed files with 55 additions and 55 deletions
32
src/Ryujinx.HLE/HOS/Services/Nv/Host1xContext.cs
Normal file
32
src/Ryujinx.HLE/HOS/Services/Nv/Host1xContext.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
8
src/Ryujinx.HLE/HOS/Services/Nv/INvDrvDebugFSServices.cs
Normal file
8
src/Ryujinx.HLE/HOS/Services/Nv/INvDrvDebugFSServices.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.Nv
|
||||
{
|
||||
[Service("nvdrvdbg")]
|
||||
class INvDrvDebugFSServices : IpcService
|
||||
{
|
||||
public INvDrvDebugFSServices(ServiceCtx context) { }
|
||||
}
|
||||
}
|
598
src/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs
Normal file
598
src/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
8
src/Ryujinx.HLE/HOS/Services/Nv/INvGemControl.cs
Normal file
8
src/Ryujinx.HLE/HOS/Services/Nv/INvGemControl.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.Nv
|
||||
{
|
||||
[Service("nvgem:c")]
|
||||
class INvGemControl : IpcService
|
||||
{
|
||||
public INvGemControl(ServiceCtx context) { }
|
||||
}
|
||||
}
|
8
src/Ryujinx.HLE/HOS/Services/Nv/INvGemCoreDump.cs
Normal file
8
src/Ryujinx.HLE/HOS/Services/Nv/INvGemCoreDump.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.Nv
|
||||
{
|
||||
[Service("nvgem:cd")]
|
||||
class INvGemCoreDump : IpcService
|
||||
{
|
||||
public INvGemCoreDump(ServiceCtx context) { }
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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() { }
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types
|
||||
{
|
||||
[Flags]
|
||||
enum AddressSpaceFlags : uint
|
||||
{
|
||||
FixedOffset = 1,
|
||||
RemapSubRange = 0x100,
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel
|
||||
{
|
||||
enum NvChannelPriority : uint
|
||||
{
|
||||
Low = 50,
|
||||
Medium = 100,
|
||||
High = 150
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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() { }
|
||||
}
|
||||
}
|
|
@ -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() { }
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
45
src/Ryujinx.HLE/HOS/Services/Nv/NvIoctl.cs
Normal file
45
src/Ryujinx.HLE/HOS/Services/Nv/NvIoctl.cs
Normal 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);
|
||||
}
|
||||
}
|
310
src/Ryujinx.HLE/HOS/Services/Nv/NvMemoryAllocator.cs
Normal file
310
src/Ryujinx.HLE/HOS/Services/Nv/NvMemoryAllocator.cs
Normal 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
|
||||
}
|
||||
}
|
41
src/Ryujinx.HLE/HOS/Services/Nv/Types/NvFence.cs
Normal file
41
src/Ryujinx.HLE/HOS/Services/Nv/Types/NvFence.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
30
src/Ryujinx.HLE/HOS/Services/Nv/Types/NvResult.cs
Normal file
30
src/Ryujinx.HLE/HOS/Services/Nv/Types/NvResult.cs
Normal 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,
|
||||
}
|
||||
}
|
15
src/Ryujinx.HLE/HOS/Services/Nv/Types/NvStatus.cs
Normal file
15
src/Ryujinx.HLE/HOS/Services/Nv/Types/NvStatus.cs
Normal 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;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue