Finalize migration for ioctl standard variant
TODO: ioctl2 migration
This commit is contained in:
parent
be9d222bb9
commit
9b6bafb742
32 changed files with 824 additions and 935 deletions
|
@ -53,16 +53,24 @@ namespace Ryujinx.HLE.HOS.Services.Nv
|
||||||
// TODO: dynamically generate _channelRegistry with attribute
|
// TODO: dynamically generate _channelRegistry with attribute
|
||||||
}
|
}
|
||||||
|
|
||||||
private int Open(string path)
|
private int Open(ServiceCtx context, string path)
|
||||||
|
{
|
||||||
|
if (context.Process == _owner)
|
||||||
{
|
{
|
||||||
if (_fileDeviceRegistry.TryGetValue(path, out Type fileDeviceClass))
|
if (_fileDeviceRegistry.TryGetValue(path, out Type fileDeviceClass))
|
||||||
{
|
{
|
||||||
ConstructorInfo constructor = fileDeviceClass.GetConstructor(new Type[] { typeof(KProcess) });
|
ConstructorInfo constructor = fileDeviceClass.GetConstructor(new Type[] { typeof(ServiceCtx) });
|
||||||
|
|
||||||
NvFileDevice fileDevice = (NvFileDevice)constructor.Invoke(new object[] { _owner });
|
NvFileDevice fileDevice = (NvFileDevice)constructor.Invoke(new object[] { context });
|
||||||
|
|
||||||
return _fileDeviceIdRegistry.Add(fileDevice);
|
return _fileDeviceIdRegistry.Add(fileDevice);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Logger.PrintWarning(LogClass.ServiceNv, $"Cannot find file device \"{path}\"!");
|
Logger.PrintWarning(LogClass.ServiceNv, $"Cannot find file device \"{path}\"!");
|
||||||
|
|
||||||
|
@ -220,7 +228,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv
|
||||||
|
|
||||||
string path = MemoryHelper.ReadAsciiString(context.Memory, pathPtr);
|
string path = MemoryHelper.ReadAsciiString(context.Memory, pathPtr);
|
||||||
|
|
||||||
fd = Open(path);
|
fd = Open(context, path);
|
||||||
|
|
||||||
if (fd == -1)
|
if (fd == -1)
|
||||||
{
|
{
|
||||||
|
|
|
@ -10,9 +10,9 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices
|
||||||
{
|
{
|
||||||
protected KProcess _owner;
|
protected KProcess _owner;
|
||||||
|
|
||||||
public NvFileDevice(KProcess owner)
|
public NvFileDevice(ServiceCtx context)
|
||||||
{
|
{
|
||||||
_owner = owner;
|
_owner = context.Process;
|
||||||
}
|
}
|
||||||
|
|
||||||
public KProcess GetOwner()
|
public KProcess GetOwner()
|
||||||
|
|
|
@ -15,7 +15,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu
|
||||||
|
|
||||||
private static ConcurrentDictionary<KProcess, AddressSpaceContext> _addressSpaceContextRegistry = new ConcurrentDictionary<KProcess, AddressSpaceContext>();
|
private static ConcurrentDictionary<KProcess, AddressSpaceContext> _addressSpaceContextRegistry = new ConcurrentDictionary<KProcess, AddressSpaceContext>();
|
||||||
|
|
||||||
public NvHostAsGpuFileDevice(KProcess owner) : base(owner)
|
public NvHostAsGpuFileDevice(ServiceCtx context) : base(context)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -28,25 +28,25 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu
|
||||||
{
|
{
|
||||||
switch (command.GetNumberValue())
|
switch (command.GetNumberValue())
|
||||||
{
|
{
|
||||||
case 0x1:
|
case 0x01:
|
||||||
result = CallIoctlMethod<BindChannelArguments>(BindChannel, arguments);
|
result = CallIoctlMethod<BindChannelArguments>(BindChannel, arguments);
|
||||||
break;
|
break;
|
||||||
case 0x2:
|
case 0x02:
|
||||||
result = CallIoctlMethod<AllocSpaceArguments>(AllocSpace, arguments);
|
result = CallIoctlMethod<AllocSpaceArguments>(AllocSpace, arguments);
|
||||||
break;
|
break;
|
||||||
case 0x3:
|
case 0x03:
|
||||||
result = CallIoctlMethod<FreeSpaceArguments>(FreeSpace, arguments);
|
result = CallIoctlMethod<FreeSpaceArguments>(FreeSpace, arguments);
|
||||||
break;
|
break;
|
||||||
case 0x5:
|
case 0x05:
|
||||||
result = CallIoctlMethod<UnmapBufferArguments>(UnmapBuffer, arguments);
|
result = CallIoctlMethod<UnmapBufferArguments>(UnmapBuffer, arguments);
|
||||||
break;
|
break;
|
||||||
case 0x6:
|
case 0x06:
|
||||||
result = CallIoctlMethod<MapBufferExArguments>(MapBufferEx, arguments);
|
result = CallIoctlMethod<MapBufferExArguments>(MapBufferEx, arguments);
|
||||||
break;
|
break;
|
||||||
case 0x8:
|
case 0x08:
|
||||||
result = CallIoctlMethod<GetVaRegionsArguments>(GetVaRegions, arguments);
|
result = CallIoctlMethod<GetVaRegionsArguments>(GetVaRegions, arguments);
|
||||||
break;
|
break;
|
||||||
case 0x9:
|
case 0x09:
|
||||||
result = CallIoctlMethod<InitializeExArguments>(InitializeEx, arguments);
|
result = CallIoctlMethod<InitializeExArguments>(InitializeEx, arguments);
|
||||||
break;
|
break;
|
||||||
case 0x14:
|
case 0x14:
|
||||||
|
|
|
@ -1,40 +1,346 @@
|
||||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
using ARMeilleure.Memory;
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Graphics;
|
||||||
|
using Ryujinx.Graphics.Memory;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel
|
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel
|
||||||
{
|
{
|
||||||
class NvHostChannelFileDevice : NvFileDevice
|
class NvHostChannelFileDevice : NvFileDevice
|
||||||
{
|
{
|
||||||
public NvHostChannelFileDevice(KProcess owner) : base(owner)
|
private uint _timeout;
|
||||||
{
|
private uint _submitTimeout;
|
||||||
|
private uint _timeslice;
|
||||||
|
private NvGpu _gpu;
|
||||||
|
private MemoryManager _memory;
|
||||||
|
|
||||||
|
public NvHostChannelFileDevice(ServiceCtx context) : base(context)
|
||||||
|
{
|
||||||
|
_gpu = context.Device.Gpu;
|
||||||
|
_memory = context.Memory;
|
||||||
|
_timeout = 3000;
|
||||||
|
_submitTimeout = 0;
|
||||||
|
_timeslice = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override NvInternalResult Ioctl(NvIoctl command, Span<byte> arguments)
|
public override NvInternalResult Ioctl(NvIoctl command, Span<byte> arguments)
|
||||||
{
|
{
|
||||||
NvInternalResult result = NvInternalResult.NotImplemented;
|
NvInternalResult result = NvInternalResult.NotImplemented;
|
||||||
|
|
||||||
if (command.GetTypeValue() == NvIoctl.NvHostMagic)
|
if (command.GetTypeValue() == NvIoctl.NvHostCustomMagic)
|
||||||
{
|
{
|
||||||
switch (command.GetNumberValue())
|
switch (command.GetNumberValue())
|
||||||
{
|
{
|
||||||
|
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.GetTypeValue() == NvIoctl.NvHostMagic)
|
||||||
|
{
|
||||||
|
switch (command.GetNumberValue())
|
||||||
|
{
|
||||||
|
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.GetTypeValue() == NvIoctl.NvGpuMagic)
|
else if (command.GetTypeValue() == NvIoctl.NvGpuMagic)
|
||||||
{
|
{
|
||||||
switch (command.GetNumberValue())
|
switch (command.GetNumberValue())
|
||||||
{
|
{
|
||||||
|
case 0x14:
|
||||||
|
result = CallIoctlMethod<ulong>(SetUserData, arguments);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private NvInternalResult Submit(Span<byte> arguments)
|
||||||
|
{
|
||||||
|
int headerSize = Marshal.SizeOf<SubmitArguments>();
|
||||||
|
SubmitArguments submitHeader = MemoryMarshal.Cast<byte, SubmitArguments>(arguments)[0];
|
||||||
|
Span<CommandBuffer> commandBufferEntries = MemoryMarshal.Cast<byte, CommandBuffer>(arguments.Slice(headerSize)).Slice(0, submitHeader.CmdBufsCount);
|
||||||
|
NvGpuVmm vmm = NvHostAsGpuFileDevice.GetAddressSpaceContext(_owner).Vmm;
|
||||||
|
|
||||||
|
foreach (CommandBuffer commandBufferEntry in commandBufferEntries)
|
||||||
|
{
|
||||||
|
NvMapHandle map = NvMapFileDevice.GetMapFromHandle(_owner, commandBufferEntry.MemoryId);
|
||||||
|
|
||||||
|
int[] cmdBufData = new int[commandBufferEntry.WordsCount];
|
||||||
|
|
||||||
|
for (int offset = 0; offset < cmdBufData.Length; offset++)
|
||||||
|
{
|
||||||
|
cmdBufData[offset] = _memory.ReadInt32(map.Address + commandBufferEntry.Offset + offset * 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
_gpu.PushCommandBuffer(vmm, cmdBufData);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NvInternalResult.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
private NvInternalResult GetSyncpoint(ref GetParameterArguments arguments)
|
||||||
|
{
|
||||||
|
arguments.Value = 0;
|
||||||
|
|
||||||
|
Logger.PrintStub(LogClass.ServiceNv);
|
||||||
|
|
||||||
|
return NvInternalResult.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
private NvInternalResult GetWaitBase(ref GetParameterArguments arguments)
|
||||||
|
{
|
||||||
|
arguments.Value = 0;
|
||||||
|
|
||||||
|
Logger.PrintStub(LogClass.ServiceNv);
|
||||||
|
|
||||||
|
return NvInternalResult.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
private NvInternalResult SetSubmitTimeout(ref uint submitTimeout)
|
||||||
|
{
|
||||||
|
_submitTimeout = submitTimeout;
|
||||||
|
|
||||||
|
Logger.PrintStub(LogClass.ServiceNv);
|
||||||
|
|
||||||
|
return NvInternalResult.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
private NvInternalResult MapCommandBuffer(Span<byte> arguments)
|
||||||
|
{
|
||||||
|
int headerSize = Marshal.SizeOf<MapCommandBufferArguments>();
|
||||||
|
MapCommandBufferArguments commandBufferHeader = MemoryMarshal.Cast<byte, MapCommandBufferArguments>(arguments)[0];
|
||||||
|
Span<CommandBufferHandle> commandBufferEntries = MemoryMarshal.Cast<byte, CommandBufferHandle>(arguments.Slice(headerSize)).Slice(0, commandBufferHeader.NumEntries);
|
||||||
|
NvGpuVmm vmm = NvHostAsGpuFileDevice.GetAddressSpaceContext(_owner).Vmm;
|
||||||
|
|
||||||
|
foreach (ref CommandBufferHandle commandBufferEntry in commandBufferEntries)
|
||||||
|
{
|
||||||
|
NvMapHandle map = NvMapFileDevice.GetMapFromHandle(_owner, commandBufferEntry.MapHandle);
|
||||||
|
|
||||||
|
if (map == null)
|
||||||
|
{
|
||||||
|
Logger.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{commandBufferEntry.MapHandle:x8}!");
|
||||||
|
|
||||||
|
return NvInternalResult.InvalidInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (map)
|
||||||
|
{
|
||||||
|
if (map.DmaMapAddress == 0)
|
||||||
|
{
|
||||||
|
map.DmaMapAddress = vmm.MapLow(map.Address, map.Size);
|
||||||
|
}
|
||||||
|
|
||||||
|
commandBufferEntry.MapAddress = (int)map.DmaMapAddress;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NvInternalResult.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
private NvInternalResult UnmapCommandBuffer(Span<byte> arguments)
|
||||||
|
{
|
||||||
|
int headerSize = Marshal.SizeOf<MapCommandBufferArguments>();
|
||||||
|
MapCommandBufferArguments commandBufferHeader = MemoryMarshal.Cast<byte, MapCommandBufferArguments>(arguments)[0];
|
||||||
|
Span<CommandBufferHandle> commandBufferEntries = MemoryMarshal.Cast<byte, CommandBufferHandle>(arguments.Slice(headerSize)).Slice(0, commandBufferHeader.NumEntries);
|
||||||
|
NvGpuVmm vmm = NvHostAsGpuFileDevice.GetAddressSpaceContext(_owner).Vmm;
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
foreach (ref CommandBufferHandle commandBufferEntry in commandBufferEntries)
|
||||||
|
{
|
||||||
|
NvMapHandle map = NvMapFileDevice.GetMapFromHandle(_owner, commandBufferEntry.MapHandle);
|
||||||
|
|
||||||
|
if (map == null)
|
||||||
|
{
|
||||||
|
Logger.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{commandBufferEntry.MapHandle:x8}!");
|
||||||
|
|
||||||
|
return NvInternalResult.InvalidInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (map)
|
||||||
|
{
|
||||||
|
if (map.DmaMapAddress != 0)
|
||||||
|
{
|
||||||
|
vmm.Free(map.DmaMapAddress, map.Size);
|
||||||
|
|
||||||
|
map.DmaMapAddress = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NvInternalResult.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
private NvInternalResult SetNvMapFd(ref int nvMapFd)
|
||||||
|
{
|
||||||
|
Logger.PrintStub(LogClass.ServiceNv);
|
||||||
|
|
||||||
|
return NvInternalResult.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
private NvInternalResult SetTimeout(ref uint timeout)
|
||||||
|
{
|
||||||
|
_timeout = timeout;
|
||||||
|
|
||||||
|
Logger.PrintStub(LogClass.ServiceNv);
|
||||||
|
|
||||||
|
return NvInternalResult.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
private NvInternalResult SubmitGpfifo(Span<byte> arguments)
|
||||||
|
{
|
||||||
|
int headerSize = Marshal.SizeOf<SubmitGpfifoArguments>();
|
||||||
|
SubmitGpfifoArguments gpfifoSubmissionHeader = MemoryMarshal.Cast<byte, SubmitGpfifoArguments>(arguments)[0];
|
||||||
|
Span<long> gpfifoEntries = MemoryMarshal.Cast<byte, long>(arguments.Slice(headerSize)).Slice(0, gpfifoSubmissionHeader.NumEntries);
|
||||||
|
|
||||||
|
NvGpuVmm vmm = NvHostAsGpuFileDevice.GetAddressSpaceContext(_owner).Vmm;
|
||||||
|
|
||||||
|
foreach (long entry in gpfifoEntries)
|
||||||
|
{
|
||||||
|
_gpu.Pusher.Push(vmm, entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
gpfifoSubmissionHeader.Fence.Id = 0;
|
||||||
|
gpfifoSubmissionHeader.Fence.Value = 0;
|
||||||
|
|
||||||
|
return NvInternalResult.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
private NvInternalResult AllocObjCtx(ref AllocObjCtxArguments arguments)
|
||||||
|
{
|
||||||
|
Logger.PrintStub(LogClass.ServiceNv);
|
||||||
|
|
||||||
|
return NvInternalResult.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
private NvInternalResult ZcullBind(ref ZcullBindArguments arguments)
|
||||||
|
{
|
||||||
|
Logger.PrintStub(LogClass.ServiceNv);
|
||||||
|
|
||||||
|
return NvInternalResult.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
private NvInternalResult SetErrorNotifier(ref SetErrorNotifierArguments arguments)
|
||||||
|
{
|
||||||
|
Logger.PrintStub(LogClass.ServiceNv);
|
||||||
|
|
||||||
|
return NvInternalResult.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
private NvInternalResult SetPriority(ref NvChannelPriority priority)
|
||||||
|
{
|
||||||
|
switch (priority)
|
||||||
|
{
|
||||||
|
case NvChannelPriority.Low:
|
||||||
|
_timeslice = 1300; // Timeslice low priority in micro-seconds
|
||||||
|
break;
|
||||||
|
case NvChannelPriority.Medium:
|
||||||
|
_timeslice = 2600; // Timeslice medium priority in micro-seconds
|
||||||
|
break;
|
||||||
|
case NvChannelPriority.High:
|
||||||
|
_timeslice = 5200; // Timeslice high priority in micro-seconds
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return NvInternalResult.InvalidInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.PrintStub(LogClass.ServiceNv);
|
||||||
|
|
||||||
|
// TODO: disable and preempt channel when GPU scheduler will be implemented.
|
||||||
|
|
||||||
|
return NvInternalResult.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
private NvInternalResult AllocGpfifoEx(ref AllocGpfifoExArguments arguments)
|
||||||
|
{
|
||||||
|
Logger.PrintStub(LogClass.ServiceNv);
|
||||||
|
|
||||||
|
return NvInternalResult.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
private NvInternalResult AllocGpfifoEx2(ref AllocGpfifoExArguments arguments)
|
||||||
|
{
|
||||||
|
Logger.PrintStub(LogClass.ServiceNv);
|
||||||
|
|
||||||
|
return NvInternalResult.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
private NvInternalResult SetTimeslice(ref uint timeslice)
|
||||||
|
{
|
||||||
|
if (timeslice < 1000 || timeslice > 50000)
|
||||||
|
{
|
||||||
|
return NvInternalResult.InvalidInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
_timeslice = timeslice; // in micro-seconds
|
||||||
|
|
||||||
|
Logger.PrintStub(LogClass.ServiceNv);
|
||||||
|
|
||||||
|
// TODO: disable and preempt channel when GPU scheduler will be implemented.
|
||||||
|
|
||||||
|
return NvInternalResult.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
private NvInternalResult SetUserData(ref ulong userData)
|
||||||
|
{
|
||||||
|
Logger.PrintStub(LogClass.ServiceNv);
|
||||||
|
|
||||||
|
return NvInternalResult.Success;
|
||||||
|
}
|
||||||
|
|
||||||
public override void Close()
|
public override void Close()
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
// TODO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,371 +0,0 @@
|
||||||
using ARMeilleure.Memory;
|
|
||||||
using Ryujinx.Common.Logging;
|
|
||||||
using Ryujinx.Graphics.Memory;
|
|
||||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
|
||||||
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu;
|
|
||||||
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel
|
|
||||||
{
|
|
||||||
class NvHostChannelIoctl
|
|
||||||
{
|
|
||||||
private static ConcurrentDictionary<KProcess, NvChannel> _channels;
|
|
||||||
|
|
||||||
static NvHostChannelIoctl()
|
|
||||||
{
|
|
||||||
_channels = new ConcurrentDictionary<KProcess, NvChannel>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static NvInternalResult ProcessIoctl(ServiceCtx context, int cmd)
|
|
||||||
{
|
|
||||||
switch (cmd & 0xffff)
|
|
||||||
{
|
|
||||||
case 0x0001: return Submit (context);
|
|
||||||
case 0x0002: return GetSyncpoint (context);
|
|
||||||
case 0x0003: return GetWaitBase (context);
|
|
||||||
case 0x0007: return SetSubmitTimeout (context);
|
|
||||||
case 0x0009: return MapBuffer (context);
|
|
||||||
case 0x000a: return UnmapBuffer (context);
|
|
||||||
case 0x4714: return SetUserData (context);
|
|
||||||
case 0x4801: return SetNvMap (context);
|
|
||||||
case 0x4803: return SetTimeout (context);
|
|
||||||
case 0x4808: return SubmitGpfifo (context);
|
|
||||||
case 0x4809: return AllocObjCtx (context);
|
|
||||||
case 0x480b: return ZcullBind (context);
|
|
||||||
case 0x480c: return SetErrorNotifier (context);
|
|
||||||
case 0x480d: return SetPriority (context);
|
|
||||||
case 0x481a: return AllocGpfifoEx2 (context);
|
|
||||||
case 0x481b: return KickoffPbWithAttr(context);
|
|
||||||
case 0x481d: return SetTimeslice (context);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new NotImplementedException(cmd.ToString("x8"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static NvInternalResult Submit(ServiceCtx context)
|
|
||||||
{
|
|
||||||
long inputPosition = context.Request.GetBufferType0x21().Position;
|
|
||||||
long outputPosition = context.Request.GetBufferType0x22().Position;
|
|
||||||
|
|
||||||
NvHostChannelSubmit args = MemoryHelper.Read<NvHostChannelSubmit>(context.Memory, inputPosition);
|
|
||||||
|
|
||||||
NvGpuVmm vmm = NvHostAsGpuFileDevice.GetAddressSpaceContext(context.Process).Vmm;
|
|
||||||
|
|
||||||
for (int index = 0; index < args.CmdBufsCount; index++)
|
|
||||||
{
|
|
||||||
long cmdBufOffset = inputPosition + 0x10 + index * 0xc;
|
|
||||||
|
|
||||||
NvHostChannelCmdBuf cmdBuf = MemoryHelper.Read<NvHostChannelCmdBuf>(context.Memory, cmdBufOffset);
|
|
||||||
|
|
||||||
NvMapHandle map = NvMapFileDevice.GetMapFromHandle(context.Process, cmdBuf.MemoryId);
|
|
||||||
|
|
||||||
int[] cmdBufData = new int[cmdBuf.WordsCount];
|
|
||||||
|
|
||||||
for (int offset = 0; offset < cmdBufData.Length; offset++)
|
|
||||||
{
|
|
||||||
cmdBufData[offset] = context.Memory.ReadInt32(map.Address + cmdBuf.Offset + offset * 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
context.Device.Gpu.PushCommandBuffer(vmm, cmdBufData);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Relocation, waitchecks, etc.
|
|
||||||
|
|
||||||
return NvInternalResult.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static NvInternalResult GetSyncpoint(ServiceCtx context)
|
|
||||||
{
|
|
||||||
// TODO
|
|
||||||
long inputPosition = context.Request.GetBufferType0x21().Position;
|
|
||||||
long outputPosition = context.Request.GetBufferType0x22().Position;
|
|
||||||
|
|
||||||
NvHostChannelGetParamArg args = MemoryHelper.Read<NvHostChannelGetParamArg>(context.Memory, inputPosition);
|
|
||||||
|
|
||||||
args.Value = 0;
|
|
||||||
|
|
||||||
MemoryHelper.Write(context.Memory, outputPosition, args);
|
|
||||||
|
|
||||||
return NvInternalResult.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static NvInternalResult GetWaitBase(ServiceCtx context)
|
|
||||||
{
|
|
||||||
long inputPosition = context.Request.GetBufferType0x21().Position;
|
|
||||||
long outputPosition = context.Request.GetBufferType0x22().Position;
|
|
||||||
|
|
||||||
NvHostChannelGetParamArg args = MemoryHelper.Read<NvHostChannelGetParamArg>(context.Memory, inputPosition);
|
|
||||||
|
|
||||||
args.Value = 0;
|
|
||||||
|
|
||||||
MemoryHelper.Write(context.Memory, outputPosition, args);
|
|
||||||
|
|
||||||
return NvInternalResult.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static NvInternalResult SetSubmitTimeout(ServiceCtx context)
|
|
||||||
{
|
|
||||||
long inputPosition = context.Request.GetBufferType0x21().Position;
|
|
||||||
|
|
||||||
GetChannel(context).SubmitTimeout = context.Memory.ReadInt32(inputPosition);
|
|
||||||
|
|
||||||
// TODO: Handle the timeout in the submit method.
|
|
||||||
|
|
||||||
Logger.PrintStub(LogClass.ServiceNv);
|
|
||||||
|
|
||||||
return NvInternalResult.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static NvInternalResult MapBuffer(ServiceCtx context)
|
|
||||||
{
|
|
||||||
long inputPosition = context.Request.GetBufferType0x21().Position;
|
|
||||||
long outputPosition = context.Request.GetBufferType0x22().Position;
|
|
||||||
|
|
||||||
NvHostChannelMapBuffer args = MemoryHelper.Read<NvHostChannelMapBuffer>(context.Memory, inputPosition);
|
|
||||||
|
|
||||||
NvGpuVmm vmm = NvHostAsGpuFileDevice.GetAddressSpaceContext(context.Process).Vmm;
|
|
||||||
|
|
||||||
for (int index = 0; index < args.NumEntries; index++)
|
|
||||||
{
|
|
||||||
int handle = context.Memory.ReadInt32(inputPosition + 0xc + index * 8);
|
|
||||||
|
|
||||||
NvMapHandle map = NvMapFileDevice.GetMapFromHandle(context.Process, handle);
|
|
||||||
|
|
||||||
if (map == null)
|
|
||||||
{
|
|
||||||
Logger.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{handle:x8}!");
|
|
||||||
|
|
||||||
return NvInternalResult.InvalidInput;
|
|
||||||
}
|
|
||||||
|
|
||||||
lock (map)
|
|
||||||
{
|
|
||||||
if (map.DmaMapAddress == 0)
|
|
||||||
{
|
|
||||||
map.DmaMapAddress = vmm.MapLow(map.Address, map.Size);
|
|
||||||
}
|
|
||||||
|
|
||||||
context.Memory.WriteInt32(outputPosition + 0xc + 4 + index * 8, (int)map.DmaMapAddress);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return NvInternalResult.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static NvInternalResult UnmapBuffer(ServiceCtx context)
|
|
||||||
{
|
|
||||||
long inputPosition = context.Request.GetBufferType0x21().Position;
|
|
||||||
|
|
||||||
NvHostChannelMapBuffer args = MemoryHelper.Read<NvHostChannelMapBuffer>(context.Memory, inputPosition);
|
|
||||||
|
|
||||||
NvGpuVmm vmm = NvHostAsGpuFileDevice.GetAddressSpaceContext(context.Process).Vmm;
|
|
||||||
|
|
||||||
for (int index = 0; index < args.NumEntries; index++)
|
|
||||||
{
|
|
||||||
int handle = context.Memory.ReadInt32(inputPosition + 0xc + index * 8);
|
|
||||||
|
|
||||||
NvMapHandle map = NvMapFileDevice.GetMapFromHandle(context.Process, handle);
|
|
||||||
|
|
||||||
if (map == null)
|
|
||||||
{
|
|
||||||
Logger.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{handle:x8}!");
|
|
||||||
|
|
||||||
return NvInternalResult.InvalidInput;
|
|
||||||
}
|
|
||||||
|
|
||||||
lock (map)
|
|
||||||
{
|
|
||||||
if (map.DmaMapAddress != 0)
|
|
||||||
{
|
|
||||||
vmm.Free(map.DmaMapAddress, map.Size);
|
|
||||||
|
|
||||||
map.DmaMapAddress = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return NvInternalResult.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static NvInternalResult SetUserData(ServiceCtx context)
|
|
||||||
{
|
|
||||||
long inputPosition = context.Request.GetBufferType0x21().Position;
|
|
||||||
long outputPosition = context.Request.GetBufferType0x22().Position;
|
|
||||||
|
|
||||||
Logger.PrintStub(LogClass.ServiceNv);
|
|
||||||
|
|
||||||
return NvInternalResult.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static NvInternalResult SetNvMap(ServiceCtx context)
|
|
||||||
{
|
|
||||||
long inputPosition = context.Request.GetBufferType0x21().Position;
|
|
||||||
long outputPosition = context.Request.GetBufferType0x22().Position;
|
|
||||||
|
|
||||||
Logger.PrintStub(LogClass.ServiceNv);
|
|
||||||
|
|
||||||
return NvInternalResult.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static NvInternalResult SetTimeout(ServiceCtx context)
|
|
||||||
{
|
|
||||||
long inputPosition = context.Request.GetBufferType0x21().Position;
|
|
||||||
|
|
||||||
GetChannel(context).Timeout = context.Memory.ReadInt32(inputPosition);
|
|
||||||
|
|
||||||
Logger.PrintStub(LogClass.ServiceNv);
|
|
||||||
|
|
||||||
return NvInternalResult.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static NvInternalResult SubmitGpfifo(ServiceCtx context)
|
|
||||||
{
|
|
||||||
long inputPosition = context.Request.GetBufferType0x21().Position;
|
|
||||||
long outputPosition = context.Request.GetBufferType0x22().Position;
|
|
||||||
|
|
||||||
NvHostChannelSubmitGpfifo args = MemoryHelper.Read<NvHostChannelSubmitGpfifo>(context.Memory, inputPosition);
|
|
||||||
|
|
||||||
NvGpuVmm vmm = NvHostAsGpuFileDevice.GetAddressSpaceContext(context.Process).Vmm;
|
|
||||||
|
|
||||||
for (int index = 0; index < args.NumEntries; index++)
|
|
||||||
{
|
|
||||||
long gpfifo = context.Memory.ReadInt64(inputPosition + 0x18 + index * 8);
|
|
||||||
|
|
||||||
PushGpfifo(context, vmm, gpfifo);
|
|
||||||
}
|
|
||||||
|
|
||||||
args.SyncptId = 0;
|
|
||||||
args.SyncptValue = 0;
|
|
||||||
|
|
||||||
MemoryHelper.Write(context.Memory, outputPosition, args);
|
|
||||||
|
|
||||||
return NvInternalResult.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static NvInternalResult AllocObjCtx(ServiceCtx context)
|
|
||||||
{
|
|
||||||
long inputPosition = context.Request.GetBufferType0x21().Position;
|
|
||||||
long outputPosition = context.Request.GetBufferType0x22().Position;
|
|
||||||
|
|
||||||
Logger.PrintStub(LogClass.ServiceNv);
|
|
||||||
|
|
||||||
return NvInternalResult.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static NvInternalResult ZcullBind(ServiceCtx context)
|
|
||||||
{
|
|
||||||
long inputPosition = context.Request.GetBufferType0x21().Position;
|
|
||||||
long outputPosition = context.Request.GetBufferType0x22().Position;
|
|
||||||
|
|
||||||
Logger.PrintStub(LogClass.ServiceNv);
|
|
||||||
|
|
||||||
return NvInternalResult.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static NvInternalResult SetErrorNotifier(ServiceCtx context)
|
|
||||||
{
|
|
||||||
long inputPosition = context.Request.GetBufferType0x21().Position;
|
|
||||||
long outputPosition = context.Request.GetBufferType0x22().Position;
|
|
||||||
|
|
||||||
Logger.PrintStub(LogClass.ServiceNv);
|
|
||||||
|
|
||||||
return NvInternalResult.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static NvInternalResult SetPriority(ServiceCtx context)
|
|
||||||
{
|
|
||||||
long inputPosition = context.Request.GetBufferType0x21().Position;
|
|
||||||
|
|
||||||
switch ((NvChannelPriority)context.Memory.ReadInt32(inputPosition))
|
|
||||||
{
|
|
||||||
case NvChannelPriority.Low:
|
|
||||||
GetChannel(context).Timeslice = 1300; // Timeslice low priority in micro-seconds
|
|
||||||
break;
|
|
||||||
case NvChannelPriority.Medium:
|
|
||||||
GetChannel(context).Timeslice = 2600; // Timeslice medium priority in micro-seconds
|
|
||||||
break;
|
|
||||||
case NvChannelPriority.High:
|
|
||||||
GetChannel(context).Timeslice = 5200; // Timeslice high priority in micro-seconds
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return NvInternalResult.InvalidInput;
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.PrintStub(LogClass.ServiceNv);
|
|
||||||
|
|
||||||
// TODO: disable and preempt channel when GPU scheduler will be implemented.
|
|
||||||
|
|
||||||
return NvInternalResult.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static NvInternalResult AllocGpfifoEx2(ServiceCtx context)
|
|
||||||
{
|
|
||||||
long inputPosition = context.Request.GetBufferType0x21().Position;
|
|
||||||
long outputPosition = context.Request.GetBufferType0x22().Position;
|
|
||||||
|
|
||||||
Logger.PrintStub(LogClass.ServiceNv);
|
|
||||||
|
|
||||||
return NvInternalResult.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static NvInternalResult KickoffPbWithAttr(ServiceCtx context)
|
|
||||||
{
|
|
||||||
long inputPosition = context.Request.GetBufferType0x21().Position;
|
|
||||||
long outputPosition = context.Request.GetBufferType0x22().Position;
|
|
||||||
|
|
||||||
NvHostChannelSubmitGpfifo args = MemoryHelper.Read<NvHostChannelSubmitGpfifo>(context.Memory, inputPosition);
|
|
||||||
|
|
||||||
NvGpuVmm vmm = NvHostAsGpuFileDevice.GetAddressSpaceContext(context.Process).Vmm;
|
|
||||||
|
|
||||||
for (int index = 0; index < args.NumEntries; index++)
|
|
||||||
{
|
|
||||||
long gpfifo = context.Memory.ReadInt64(args.Address + index * 8);
|
|
||||||
|
|
||||||
PushGpfifo(context, vmm, gpfifo);
|
|
||||||
}
|
|
||||||
|
|
||||||
args.SyncptId = 0;
|
|
||||||
args.SyncptValue = 0;
|
|
||||||
|
|
||||||
MemoryHelper.Write(context.Memory, outputPosition, args);
|
|
||||||
|
|
||||||
return NvInternalResult.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static NvInternalResult SetTimeslice(ServiceCtx context)
|
|
||||||
{
|
|
||||||
long inputPosition = context.Request.GetBufferType0x21().Position;
|
|
||||||
int timeslice = context.Memory.ReadInt32(inputPosition);
|
|
||||||
|
|
||||||
if (timeslice < 1000 || timeslice > 50000)
|
|
||||||
{
|
|
||||||
return NvInternalResult.InvalidInput;
|
|
||||||
}
|
|
||||||
|
|
||||||
GetChannel(context).Timeslice = timeslice; // in micro-seconds
|
|
||||||
|
|
||||||
Logger.PrintStub(LogClass.ServiceNv);
|
|
||||||
|
|
||||||
// TODO: disable and preempt channel when GPU scheduler will be implemented.
|
|
||||||
|
|
||||||
return NvInternalResult.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void PushGpfifo(ServiceCtx context, NvGpuVmm vmm, long gpfifo)
|
|
||||||
{
|
|
||||||
context.Device.Gpu.Pusher.Push(vmm, gpfifo);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static NvChannel GetChannel(ServiceCtx context)
|
|
||||||
{
|
|
||||||
return _channels.GetOrAdd(context.Process, (key) => new NvChannel());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void UnloadProcess(KProcess process)
|
|
||||||
{
|
|
||||||
_channels.TryRemove(process, out _);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)]
|
||||||
|
struct MapCommandBufferArguments
|
||||||
|
{
|
||||||
|
public int NumEntries;
|
||||||
|
public int DataAddress; // Ignored by the driver.
|
||||||
|
public bool AttachHostChDas;
|
||||||
|
public byte Padding1;
|
||||||
|
public short Padding2;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel
|
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel
|
||||||
{
|
{
|
||||||
enum NvChannelPriority
|
enum NvChannelPriority : uint
|
||||||
{
|
{
|
||||||
Low = 50,
|
Low = 50,
|
||||||
Medium = 100,
|
Medium = 100,
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel
|
|
||||||
{
|
|
||||||
[StructLayout(LayoutKind.Sequential, Size = 8, Pack = 4)]
|
|
||||||
struct NvHostChannelCmdBuf
|
|
||||||
{
|
|
||||||
public int MemoryId;
|
|
||||||
public int Offset;
|
|
||||||
public int WordsCount;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel
|
|
||||||
{
|
|
||||||
[StructLayout(LayoutKind.Sequential, Size = 8, Pack = 4)]
|
|
||||||
struct NvHostChannelGetParamArg
|
|
||||||
{
|
|
||||||
public int Param;
|
|
||||||
public int Value;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel
|
|
||||||
{
|
|
||||||
[StructLayout(LayoutKind.Sequential, Size = 0xc, Pack = 4)]
|
|
||||||
struct NvHostChannelMapBuffer
|
|
||||||
{
|
|
||||||
public int NumEntries;
|
|
||||||
public int DataAddress; // Ignored by the driver.
|
|
||||||
public bool AttachHostChDas;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel
|
|
||||||
{
|
|
||||||
[StructLayout(LayoutKind.Sequential, Size = 8, Pack = 4)]
|
|
||||||
struct NvHostChannelSubmit
|
|
||||||
{
|
|
||||||
public int CmdBufsCount;
|
|
||||||
public int RelocsCount;
|
|
||||||
public int SyncptIncrsCount;
|
|
||||||
public int WaitchecksCount;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel
|
|
||||||
{
|
|
||||||
struct NvHostChannelSubmitGpfifo
|
|
||||||
{
|
|
||||||
public long Address;
|
|
||||||
public int NumEntries;
|
|
||||||
public int Flags;
|
|
||||||
public int SyncptId;
|
|
||||||
public int SyncptValue;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,21 @@
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types
|
||||||
|
{
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
struct CommandBuffer
|
||||||
|
{
|
||||||
|
public int MemoryId;
|
||||||
|
public int Offset;
|
||||||
|
public int WordsCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
struct SubmitArguments
|
||||||
|
{
|
||||||
|
public int CmdBufsCount;
|
||||||
|
public int RelocsCount;
|
||||||
|
public int SyncptIncrsCount;
|
||||||
|
public int WaitchecksCount;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
using Ryujinx.HLE.HOS.Services.Nv.Types;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types
|
||||||
|
{
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
struct SubmitGpfifoArguments
|
||||||
|
{
|
||||||
|
public long Address;
|
||||||
|
public int NumEntries;
|
||||||
|
public int Flags;
|
||||||
|
public NvFence Fence;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,32 +1,60 @@
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
|
||||||
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl.Types;
|
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl.Types;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Nv.Types;
|
||||||
using Ryujinx.HLE.HOS.Services.Settings;
|
using Ryujinx.HLE.HOS.Services.Settings;
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl
|
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl
|
||||||
{
|
{
|
||||||
internal class NvHostCtrlFileDevice : NvFileDevice
|
internal class NvHostCtrlFileDevice : NvFileDevice
|
||||||
{
|
{
|
||||||
private bool _isProductionMode;
|
private const int EventsCount = 64;
|
||||||
|
|
||||||
public NvHostCtrlFileDevice(KProcess owner) : base(owner)
|
private bool _isProductionMode;
|
||||||
|
private NvHostSyncpt _syncpt;
|
||||||
|
private NvHostEvent[] _events;
|
||||||
|
|
||||||
|
public NvHostCtrlFileDevice(ServiceCtx context) : base(context)
|
||||||
{
|
{
|
||||||
if (NxSettings.Settings.TryGetValue("nv!rmos_set_production_mode", out object productionModeSetting))
|
if (NxSettings.Settings.TryGetValue("nv!rmos_set_production_mode", out object productionModeSetting))
|
||||||
{
|
{
|
||||||
_isProductionMode = ((string)productionModeSetting) != "0"; // Default value is ""
|
_isProductionMode = ((string)productionModeSetting) != "0"; // Default value is ""
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_isProductionMode = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_syncpt = new NvHostSyncpt();
|
||||||
|
_events = new NvHostEvent[EventsCount];
|
||||||
}
|
}
|
||||||
|
|
||||||
public override NvInternalResult Ioctl(NvIoctl command, Span<byte> arguments)
|
public override NvInternalResult Ioctl(NvIoctl command, Span<byte> arguments)
|
||||||
{
|
{
|
||||||
NvInternalResult result = NvInternalResult.NotImplemented;
|
NvInternalResult result = NvInternalResult.NotImplemented;
|
||||||
|
|
||||||
if (command.GetTypeValue() == NvIoctl.NvHostMagic)
|
if (command.GetTypeValue() == NvIoctl.NvHostCustomMagic)
|
||||||
{
|
{
|
||||||
switch (command.GetNumberValue())
|
switch (command.GetNumberValue())
|
||||||
{
|
{
|
||||||
|
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:
|
case 0x1b:
|
||||||
// As Marshal cannot handle unaligned arrays, we do everything by hand here.
|
// As Marshal cannot handle unaligned arrays, we do everything by hand here.
|
||||||
GetConfigurationArguments configArgument = GetConfigurationArguments.FromSpan(arguments);
|
GetConfigurationArguments configArgument = GetConfigurationArguments.FromSpan(arguments);
|
||||||
|
@ -37,6 +65,15 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl
|
||||||
configArgument.CopyTo(arguments);
|
configArgument.CopyTo(arguments);
|
||||||
}
|
}
|
||||||
break;
|
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;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -45,6 +82,38 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private NvInternalResult SyncptRead(ref NvFence arguments)
|
||||||
|
{
|
||||||
|
return SyncptReadMinOrMax(ref arguments, max: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private NvInternalResult SyncptIncr(ref uint id)
|
||||||
|
{
|
||||||
|
if (id >= NvHostSyncpt.SyncptsCount)
|
||||||
|
{
|
||||||
|
return NvInternalResult.InvalidInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
_syncpt.Increment((int)id);
|
||||||
|
|
||||||
|
return NvInternalResult.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
private NvInternalResult SyncptWait(ref SyncptWaitArguments arguments)
|
||||||
|
{
|
||||||
|
return SyncptWait(ref arguments, out _);
|
||||||
|
}
|
||||||
|
|
||||||
|
private NvInternalResult SyncptWaitEx(ref SyncptWaitExArguments arguments)
|
||||||
|
{
|
||||||
|
return SyncptWait(ref arguments.Input, out arguments.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private NvInternalResult SyncptReadMax(ref NvFence arguments)
|
||||||
|
{
|
||||||
|
return SyncptReadMinOrMax(ref arguments, max: true);
|
||||||
|
}
|
||||||
|
|
||||||
private NvInternalResult GetConfig(GetConfigurationArguments arguments)
|
private NvInternalResult GetConfig(GetConfigurationArguments arguments)
|
||||||
{
|
{
|
||||||
if (!_isProductionMode && NxSettings.Settings.TryGetValue($"{arguments.Domain}!{arguments.Parameter}".ToLower(), out object nvSetting))
|
if (!_isProductionMode && NxSettings.Settings.TryGetValue($"{arguments.Domain}!{arguments.Parameter}".ToLower(), out object nvSetting))
|
||||||
|
@ -87,6 +156,222 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl
|
||||||
return NvInternalResult.InvalidInput;
|
return NvInternalResult.InvalidInput;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private NvInternalResult EventWait(ref EventWaitArguments arguments)
|
||||||
|
{
|
||||||
|
return EventWait(ref arguments, async: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private NvInternalResult EventWaitAsync(ref EventWaitArguments arguments)
|
||||||
|
{
|
||||||
|
return EventWait(ref arguments, async: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private NvInternalResult EventRegister(ref uint userEventId)
|
||||||
|
{
|
||||||
|
Logger.PrintStub(LogClass.ServiceNv);
|
||||||
|
|
||||||
|
return NvInternalResult.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
private NvInternalResult SyncptReadMinOrMax(ref NvFence arguments, bool max)
|
||||||
|
{
|
||||||
|
if (arguments.Id >= NvHostSyncpt.SyncptsCount)
|
||||||
|
{
|
||||||
|
return NvInternalResult.InvalidInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (max)
|
||||||
|
{
|
||||||
|
arguments.Value = (uint)_syncpt.GetMax((int)arguments.Id);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
arguments.Value = (uint)_syncpt.GetMin((int)arguments.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NvInternalResult.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
private NvInternalResult SyncptWait(ref SyncptWaitArguments arguments, out int value)
|
||||||
|
{
|
||||||
|
if (arguments.Id >= NvHostSyncpt.SyncptsCount)
|
||||||
|
{
|
||||||
|
value = 0;
|
||||||
|
|
||||||
|
return NvInternalResult.InvalidInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
NvInternalResult result;
|
||||||
|
|
||||||
|
if (_syncpt.MinCompare((int)arguments.Id, arguments.Thresh))
|
||||||
|
{
|
||||||
|
result = NvInternalResult.Success;
|
||||||
|
}
|
||||||
|
else if (arguments.Timeout == 0)
|
||||||
|
{
|
||||||
|
result = NvInternalResult.TryAgain;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.PrintDebug(LogClass.ServiceNv, $"Waiting syncpt with timeout of {arguments.Timeout}ms...");
|
||||||
|
|
||||||
|
using (ManualResetEvent waitEvent = new ManualResetEvent(false))
|
||||||
|
{
|
||||||
|
_syncpt.AddWaiter(arguments.Thresh, waitEvent);
|
||||||
|
|
||||||
|
// Note: Negative (> INT_MAX) timeouts aren't valid on .NET,
|
||||||
|
// in this case we just use the maximum timeout possible.
|
||||||
|
int timeout = arguments.Timeout;
|
||||||
|
|
||||||
|
if (timeout < -1)
|
||||||
|
{
|
||||||
|
timeout = int.MaxValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeout == -1)
|
||||||
|
{
|
||||||
|
waitEvent.WaitOne();
|
||||||
|
|
||||||
|
result = NvInternalResult.Success;
|
||||||
|
}
|
||||||
|
else if (waitEvent.WaitOne(timeout))
|
||||||
|
{
|
||||||
|
result = NvInternalResult.Success;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = NvInternalResult.TimedOut;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.PrintDebug(LogClass.ServiceNv, "Resuming...");
|
||||||
|
}
|
||||||
|
|
||||||
|
value = _syncpt.GetMin((int)arguments.Id);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private NvInternalResult EventWait(ref EventWaitArguments arguments, bool async)
|
||||||
|
{
|
||||||
|
if (arguments.Id >= NvHostSyncpt.SyncptsCount)
|
||||||
|
{
|
||||||
|
return NvInternalResult.InvalidInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_syncpt.MinCompare(arguments.Id, arguments.Thresh))
|
||||||
|
{
|
||||||
|
arguments.Value = _syncpt.GetMin(arguments.Id);
|
||||||
|
|
||||||
|
return NvInternalResult.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!async)
|
||||||
|
{
|
||||||
|
arguments.Value = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arguments.Timeout == 0)
|
||||||
|
{
|
||||||
|
return NvInternalResult.TryAgain;
|
||||||
|
}
|
||||||
|
|
||||||
|
NvHostEvent Event;
|
||||||
|
|
||||||
|
NvInternalResult result;
|
||||||
|
|
||||||
|
int eventIndex;
|
||||||
|
|
||||||
|
if (async)
|
||||||
|
{
|
||||||
|
eventIndex = arguments.Value;
|
||||||
|
|
||||||
|
if ((uint)eventIndex >= EventsCount)
|
||||||
|
{
|
||||||
|
return NvInternalResult.InvalidInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
Event = _events[eventIndex];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Event = GetFreeEvent(arguments.Id, out eventIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Event != null &&
|
||||||
|
(Event.State == NvHostEventState.Registered ||
|
||||||
|
Event.State == NvHostEventState.Free))
|
||||||
|
{
|
||||||
|
Event.Id = arguments.Id;
|
||||||
|
Event.Thresh = arguments.Thresh;
|
||||||
|
|
||||||
|
Event.State = NvHostEventState.Waiting;
|
||||||
|
|
||||||
|
if (!async)
|
||||||
|
{
|
||||||
|
arguments.Value = ((arguments.Id & 0xfff) << 16) | 0x10000000;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
arguments.Value = arguments.Id << 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
arguments.Value |= eventIndex;
|
||||||
|
|
||||||
|
result = NvInternalResult.TryAgain;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = NvInternalResult.InvalidInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private NvHostEvent GetFreeEvent(int id, out int eventIndex)
|
||||||
|
{
|
||||||
|
eventIndex = EventsCount;
|
||||||
|
|
||||||
|
int nullIndex = EventsCount;
|
||||||
|
|
||||||
|
for (int index = 0; index < EventsCount; index++)
|
||||||
|
{
|
||||||
|
NvHostEvent Event = _events[index];
|
||||||
|
|
||||||
|
if (Event != null)
|
||||||
|
{
|
||||||
|
if (Event.State == NvHostEventState.Registered ||
|
||||||
|
Event.State == NvHostEventState.Free)
|
||||||
|
{
|
||||||
|
eventIndex = index;
|
||||||
|
|
||||||
|
if (Event.Id == id)
|
||||||
|
{
|
||||||
|
return Event;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (nullIndex == EventsCount)
|
||||||
|
{
|
||||||
|
nullIndex = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nullIndex < EventsCount)
|
||||||
|
{
|
||||||
|
eventIndex = nullIndex;
|
||||||
|
|
||||||
|
return _events[nullIndex] = new NvHostEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eventIndex < EventsCount)
|
||||||
|
{
|
||||||
|
return _events[eventIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public override void Close()
|
public override void Close()
|
||||||
{
|
{
|
||||||
// TODO
|
// TODO
|
||||||
|
|
|
@ -1,404 +0,0 @@
|
||||||
using ARMeilleure.Memory;
|
|
||||||
using Ryujinx.Common.Logging;
|
|
||||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
|
||||||
using Ryujinx.HLE.HOS.Services.Settings;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl
|
|
||||||
{
|
|
||||||
class NvHostCtrlIoctl
|
|
||||||
{
|
|
||||||
private static ConcurrentDictionary<KProcess, NvHostCtrlUserCtx> _userCtxs;
|
|
||||||
|
|
||||||
private static bool _isProductionMode = true;
|
|
||||||
|
|
||||||
static NvHostCtrlIoctl()
|
|
||||||
{
|
|
||||||
_userCtxs = new ConcurrentDictionary<KProcess, NvHostCtrlUserCtx>();
|
|
||||||
|
|
||||||
if (NxSettings.Settings.TryGetValue("nv!rmos_set_production_mode", out object productionModeSetting))
|
|
||||||
{
|
|
||||||
_isProductionMode = ((string)productionModeSetting) != "0"; // Default value is ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static NvInternalResult ProcessIoctl(ServiceCtx context, int cmd)
|
|
||||||
{
|
|
||||||
switch (cmd & 0xffff)
|
|
||||||
{
|
|
||||||
case 0x0014: return SyncptRead (context);
|
|
||||||
case 0x0015: return SyncptIncr (context);
|
|
||||||
case 0x0016: return SyncptWait (context);
|
|
||||||
case 0x0019: return SyncptWaitEx (context);
|
|
||||||
case 0x001a: return SyncptReadMax (context);
|
|
||||||
case 0x001b: return GetConfig (context);
|
|
||||||
case 0x001d: return EventWait (context);
|
|
||||||
case 0x001e: return EventWaitAsync(context);
|
|
||||||
case 0x001f: return EventRegister (context);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new NotImplementedException(cmd.ToString("x8"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static NvInternalResult SyncptRead(ServiceCtx context)
|
|
||||||
{
|
|
||||||
return SyncptReadMinOrMax(context, max: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static NvInternalResult SyncptIncr(ServiceCtx context)
|
|
||||||
{
|
|
||||||
long inputPosition = context.Request.GetBufferType0x21().Position;
|
|
||||||
|
|
||||||
int id = context.Memory.ReadInt32(inputPosition);
|
|
||||||
|
|
||||||
if ((uint)id >= NvHostSyncpt.SyncptsCount)
|
|
||||||
{
|
|
||||||
return NvInternalResult.InvalidInput;
|
|
||||||
}
|
|
||||||
|
|
||||||
GetUserCtx(context).Syncpt.Increment(id);
|
|
||||||
|
|
||||||
return NvInternalResult.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static NvInternalResult SyncptWait(ServiceCtx context)
|
|
||||||
{
|
|
||||||
return SyncptWait(context, extended: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static NvInternalResult SyncptWaitEx(ServiceCtx context)
|
|
||||||
{
|
|
||||||
return SyncptWait(context, extended: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static NvInternalResult SyncptReadMax(ServiceCtx context)
|
|
||||||
{
|
|
||||||
return SyncptReadMinOrMax(context, max: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static NvInternalResult GetConfig(ServiceCtx context)
|
|
||||||
{
|
|
||||||
if (!_isProductionMode)
|
|
||||||
{
|
|
||||||
long inputPosition = context.Request.GetBufferType0x21().Position;
|
|
||||||
long outputPosition = context.Request.GetBufferType0x22().Position;
|
|
||||||
|
|
||||||
string domain = MemoryHelper.ReadAsciiString(context.Memory, inputPosition + 0, 0x41);
|
|
||||||
string name = MemoryHelper.ReadAsciiString(context.Memory, inputPosition + 0x41, 0x41);
|
|
||||||
|
|
||||||
if (NxSettings.Settings.TryGetValue($"{domain}!{name}", out object nvSetting))
|
|
||||||
{
|
|
||||||
byte[] settingBuffer = new byte[0x101];
|
|
||||||
|
|
||||||
if (nvSetting is string stringValue)
|
|
||||||
{
|
|
||||||
if (stringValue.Length > 0x100)
|
|
||||||
{
|
|
||||||
Logger.PrintError(LogClass.ServiceNv, $"{domain}!{name} String value size is too big!");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
settingBuffer = Encoding.ASCII.GetBytes(stringValue + "\0");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nvSetting is int intValue)
|
|
||||||
{
|
|
||||||
settingBuffer = BitConverter.GetBytes(intValue);
|
|
||||||
}
|
|
||||||
else if (nvSetting is bool boolValue)
|
|
||||||
{
|
|
||||||
settingBuffer[0] = boolValue ? (byte)1 : (byte)0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new NotImplementedException(nvSetting.GetType().Name);
|
|
||||||
}
|
|
||||||
|
|
||||||
context.Memory.WriteBytes(outputPosition + 0x82, settingBuffer);
|
|
||||||
|
|
||||||
Logger.PrintDebug(LogClass.ServiceNv, $"Got setting {domain}!{name}");
|
|
||||||
}
|
|
||||||
|
|
||||||
return 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 static NvInternalResult EventWait(ServiceCtx context)
|
|
||||||
{
|
|
||||||
return EventWait(context, async: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static NvInternalResult EventWaitAsync(ServiceCtx context)
|
|
||||||
{
|
|
||||||
return EventWait(context, async: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static NvInternalResult EventRegister(ServiceCtx context)
|
|
||||||
{
|
|
||||||
long inputPosition = context.Request.GetBufferType0x21().Position;
|
|
||||||
long outputPosition = context.Request.GetBufferType0x22().Position;
|
|
||||||
|
|
||||||
int eventId = context.Memory.ReadInt32(inputPosition);
|
|
||||||
|
|
||||||
Logger.PrintStub(LogClass.ServiceNv);
|
|
||||||
|
|
||||||
return NvInternalResult.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static NvInternalResult SyncptReadMinOrMax(ServiceCtx context, bool max)
|
|
||||||
{
|
|
||||||
long inputPosition = context.Request.GetBufferType0x21().Position;
|
|
||||||
long outputPosition = context.Request.GetBufferType0x22().Position;
|
|
||||||
|
|
||||||
NvHostCtrlSyncptRead args = MemoryHelper.Read<NvHostCtrlSyncptRead>(context.Memory, inputPosition);
|
|
||||||
|
|
||||||
if ((uint)args.Id >= NvHostSyncpt.SyncptsCount)
|
|
||||||
{
|
|
||||||
return NvInternalResult.InvalidInput;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (max)
|
|
||||||
{
|
|
||||||
args.Value = GetUserCtx(context).Syncpt.GetMax(args.Id);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
args.Value = GetUserCtx(context).Syncpt.GetMin(args.Id);
|
|
||||||
}
|
|
||||||
|
|
||||||
MemoryHelper.Write(context.Memory, outputPosition, args);
|
|
||||||
|
|
||||||
return NvInternalResult.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static NvInternalResult SyncptWait(ServiceCtx context, bool extended)
|
|
||||||
{
|
|
||||||
long inputPosition = context.Request.GetBufferType0x21().Position;
|
|
||||||
long outputPosition = context.Request.GetBufferType0x22().Position;
|
|
||||||
|
|
||||||
NvHostCtrlSyncptWait args = MemoryHelper.Read<NvHostCtrlSyncptWait>(context.Memory, inputPosition);
|
|
||||||
|
|
||||||
NvHostSyncpt syncpt = GetUserCtx(context).Syncpt;
|
|
||||||
|
|
||||||
if ((uint)args.Id >= NvHostSyncpt.SyncptsCount)
|
|
||||||
{
|
|
||||||
return NvInternalResult.InvalidInput;
|
|
||||||
}
|
|
||||||
|
|
||||||
NvInternalResult result;
|
|
||||||
|
|
||||||
if (syncpt.MinCompare(args.Id, args.Thresh))
|
|
||||||
{
|
|
||||||
result = NvInternalResult.Success;
|
|
||||||
}
|
|
||||||
else if (args.Timeout == 0)
|
|
||||||
{
|
|
||||||
result = NvInternalResult.TryAgain;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger.PrintDebug(LogClass.ServiceNv, "Waiting syncpt with timeout of " + args.Timeout + "ms...");
|
|
||||||
|
|
||||||
using (ManualResetEvent waitEvent = new ManualResetEvent(false))
|
|
||||||
{
|
|
||||||
syncpt.AddWaiter(args.Thresh, waitEvent);
|
|
||||||
|
|
||||||
// Note: Negative (> INT_MAX) timeouts aren't valid on .NET,
|
|
||||||
// in this case we just use the maximum timeout possible.
|
|
||||||
int timeout = args.Timeout;
|
|
||||||
|
|
||||||
if (timeout < -1)
|
|
||||||
{
|
|
||||||
timeout = int.MaxValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timeout == -1)
|
|
||||||
{
|
|
||||||
waitEvent.WaitOne();
|
|
||||||
|
|
||||||
result = NvInternalResult.Success;
|
|
||||||
}
|
|
||||||
else if (waitEvent.WaitOne(timeout))
|
|
||||||
{
|
|
||||||
result = NvInternalResult.Success;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result = NvInternalResult.TimedOut;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.PrintDebug(LogClass.ServiceNv, "Resuming...");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (extended)
|
|
||||||
{
|
|
||||||
context.Memory.WriteInt32(outputPosition + 0xc, syncpt.GetMin(args.Id));
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static NvInternalResult EventWait(ServiceCtx context, bool async)
|
|
||||||
{
|
|
||||||
long inputPosition = context.Request.GetBufferType0x21().Position;
|
|
||||||
long outputPosition = context.Request.GetBufferType0x22().Position;
|
|
||||||
|
|
||||||
NvHostCtrlSyncptWaitEx args = MemoryHelper.Read<NvHostCtrlSyncptWaitEx>(context.Memory, inputPosition);
|
|
||||||
|
|
||||||
if ((uint)args.Id >= NvHostSyncpt.SyncptsCount)
|
|
||||||
{
|
|
||||||
return NvInternalResult.InvalidInput;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WriteArgs()
|
|
||||||
{
|
|
||||||
MemoryHelper.Write(context.Memory, outputPosition, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
NvHostSyncpt syncpt = GetUserCtx(context).Syncpt;
|
|
||||||
|
|
||||||
if (syncpt.MinCompare(args.Id, args.Thresh))
|
|
||||||
{
|
|
||||||
args.Value = syncpt.GetMin(args.Id);
|
|
||||||
|
|
||||||
WriteArgs();
|
|
||||||
|
|
||||||
return NvInternalResult.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!async)
|
|
||||||
{
|
|
||||||
args.Value = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args.Timeout == 0)
|
|
||||||
{
|
|
||||||
WriteArgs();
|
|
||||||
|
|
||||||
return NvInternalResult.TryAgain;
|
|
||||||
}
|
|
||||||
|
|
||||||
NvHostEvent Event;
|
|
||||||
|
|
||||||
NvInternalResult result;
|
|
||||||
|
|
||||||
int eventIndex;
|
|
||||||
|
|
||||||
if (async)
|
|
||||||
{
|
|
||||||
eventIndex = args.Value;
|
|
||||||
|
|
||||||
if ((uint)eventIndex >= NvHostCtrlUserCtx.EventsCount)
|
|
||||||
{
|
|
||||||
return NvInternalResult.InvalidInput;
|
|
||||||
}
|
|
||||||
|
|
||||||
Event = GetUserCtx(context).Events[eventIndex];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Event = GetFreeEvent(context, syncpt, args.Id, out eventIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Event != null &&
|
|
||||||
(Event.State == NvHostEventState.Registered ||
|
|
||||||
Event.State == NvHostEventState.Free))
|
|
||||||
{
|
|
||||||
Event.Id = args.Id;
|
|
||||||
Event.Thresh = args.Thresh;
|
|
||||||
|
|
||||||
Event.State = NvHostEventState.Waiting;
|
|
||||||
|
|
||||||
if (!async)
|
|
||||||
{
|
|
||||||
args.Value = ((args.Id & 0xfff) << 16) | 0x10000000;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
args.Value = args.Id << 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
args.Value |= eventIndex;
|
|
||||||
|
|
||||||
result = NvInternalResult.TryAgain;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result = NvInternalResult.InvalidInput;
|
|
||||||
}
|
|
||||||
|
|
||||||
WriteArgs();
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static NvHostEvent GetFreeEvent(
|
|
||||||
ServiceCtx context,
|
|
||||||
NvHostSyncpt syncpt,
|
|
||||||
int id,
|
|
||||||
out int eventIndex)
|
|
||||||
{
|
|
||||||
NvHostEvent[] events = GetUserCtx(context).Events;
|
|
||||||
|
|
||||||
eventIndex = NvHostCtrlUserCtx.EventsCount;
|
|
||||||
|
|
||||||
int nullIndex = NvHostCtrlUserCtx.EventsCount;
|
|
||||||
|
|
||||||
for (int index = 0; index < NvHostCtrlUserCtx.EventsCount; index++)
|
|
||||||
{
|
|
||||||
NvHostEvent Event = events[index];
|
|
||||||
|
|
||||||
if (Event != null)
|
|
||||||
{
|
|
||||||
if (Event.State == NvHostEventState.Registered ||
|
|
||||||
Event.State == NvHostEventState.Free)
|
|
||||||
{
|
|
||||||
eventIndex = index;
|
|
||||||
|
|
||||||
if (Event.Id == id)
|
|
||||||
{
|
|
||||||
return Event;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (nullIndex == NvHostCtrlUserCtx.EventsCount)
|
|
||||||
{
|
|
||||||
nullIndex = index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nullIndex < NvHostCtrlUserCtx.EventsCount)
|
|
||||||
{
|
|
||||||
eventIndex = nullIndex;
|
|
||||||
|
|
||||||
return events[nullIndex] = new NvHostEvent();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (eventIndex < NvHostCtrlUserCtx.EventsCount)
|
|
||||||
{
|
|
||||||
return events[eventIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static NvHostCtrlUserCtx GetUserCtx(ServiceCtx context)
|
|
||||||
{
|
|
||||||
return _userCtxs.GetOrAdd(context.Process, (key) => new NvHostCtrlUserCtx());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void UnloadProcess(KProcess process)
|
|
||||||
{
|
|
||||||
_userCtxs.TryRemove(process, out _);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl.Types
|
||||||
|
{
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
struct EventWaitArguments
|
||||||
|
{
|
||||||
|
public int Id;
|
||||||
|
public int Thresh;
|
||||||
|
public int Timeout;
|
||||||
|
public int Value;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +0,0 @@
|
||||||
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl
|
|
||||||
{
|
|
||||||
struct NvHostCtrlSyncptRead
|
|
||||||
{
|
|
||||||
public int Id;
|
|
||||||
public int Value;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl
|
|
||||||
{
|
|
||||||
struct NvHostCtrlSyncptWait
|
|
||||||
{
|
|
||||||
public int Id;
|
|
||||||
public int Thresh;
|
|
||||||
public int Timeout;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl
|
|
||||||
{
|
|
||||||
struct NvHostCtrlSyncptWaitEx
|
|
||||||
{
|
|
||||||
public int Id;
|
|
||||||
public int Thresh;
|
|
||||||
public int Timeout;
|
|
||||||
public int Value;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl
|
|
||||||
{
|
|
||||||
class NvHostCtrlUserCtx
|
|
||||||
{
|
|
||||||
public const int LocksCount = 16;
|
|
||||||
public const int EventsCount = 64;
|
|
||||||
|
|
||||||
public NvHostSyncpt Syncpt { get; private set; }
|
|
||||||
|
|
||||||
public NvHostEvent[] Events { get; private set; }
|
|
||||||
|
|
||||||
public NvHostCtrlUserCtx()
|
|
||||||
{
|
|
||||||
Syncpt = new NvHostSyncpt();
|
|
||||||
|
|
||||||
Events = new NvHostEvent[EventsCount];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl.Types
|
||||||
|
{
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
struct SyncptWaitArguments
|
||||||
|
{
|
||||||
|
public uint Id;
|
||||||
|
public int Thresh;
|
||||||
|
public int Timeout;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl.Types
|
||||||
|
{
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
struct SyncptWaitExArguments
|
||||||
|
{
|
||||||
|
public SyncptWaitArguments Input;
|
||||||
|
public int Value;
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,8 +11,9 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu
|
||||||
private static Stopwatch _pTimer = new Stopwatch();
|
private static Stopwatch _pTimer = new Stopwatch();
|
||||||
private static double _ticksToNs = (1.0 / Stopwatch.Frequency) * 1_000_000_000;
|
private static double _ticksToNs = (1.0 / Stopwatch.Frequency) * 1_000_000_000;
|
||||||
|
|
||||||
public NvHostCtrlGpuFileDevice(KProcess owner) : base(owner)
|
public NvHostCtrlGpuFileDevice(ServiceCtx context) : base(context)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static NvHostCtrlGpuFileDevice()
|
static NvHostCtrlGpuFileDevice()
|
||||||
|
@ -28,19 +29,19 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu
|
||||||
{
|
{
|
||||||
switch (command.GetNumberValue())
|
switch (command.GetNumberValue())
|
||||||
{
|
{
|
||||||
case 0x1:
|
case 0x01:
|
||||||
result = CallIoctlMethod<ZcullGetCtxSizeArguments>(ZcullGetCtxSize, arguments);
|
result = CallIoctlMethod<ZcullGetCtxSizeArguments>(ZcullGetCtxSize, arguments);
|
||||||
break;
|
break;
|
||||||
case 0x2:
|
case 0x02:
|
||||||
result = CallIoctlMethod<ZcullGetInfoArguments>(ZcullGetInfo, arguments);
|
result = CallIoctlMethod<ZcullGetInfoArguments>(ZcullGetInfo, arguments);
|
||||||
break;
|
break;
|
||||||
case 0x3:
|
case 0x03:
|
||||||
result = CallIoctlMethod<ZbcSetTableArguments>(ZbcSetTable, arguments);
|
result = CallIoctlMethod<ZbcSetTableArguments>(ZbcSetTable, arguments);
|
||||||
break;
|
break;
|
||||||
case 0x5:
|
case 0x05:
|
||||||
result = CallIoctlMethod<GetCharacteristicsArguments>(GetCharacteristics, arguments);
|
result = CallIoctlMethod<GetCharacteristicsArguments>(GetCharacteristics, arguments);
|
||||||
break;
|
break;
|
||||||
case 0x6:
|
case 0x06:
|
||||||
result = CallIoctlMethod<GetTpcMasksArguments>(GetTpcMasks, arguments);
|
result = CallIoctlMethod<GetTpcMasksArguments>(GetTpcMasks, arguments);
|
||||||
break;
|
break;
|
||||||
case 0x14:
|
case 0x14:
|
||||||
|
|
|
@ -13,9 +13,9 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
|
||||||
|
|
||||||
private static ConcurrentDictionary<KProcess, IdDictionary> _maps = new ConcurrentDictionary<KProcess, IdDictionary>();
|
private static ConcurrentDictionary<KProcess, IdDictionary> _maps = new ConcurrentDictionary<KProcess, IdDictionary>();
|
||||||
|
|
||||||
public NvMapFileDevice(KProcess owner) : base(owner)
|
public NvMapFileDevice(ServiceCtx context) : base(context)
|
||||||
{
|
{
|
||||||
IdDictionary dict = _maps.GetOrAdd(owner, (key) => new IdDictionary());
|
IdDictionary dict = _maps.GetOrAdd(_owner, (key) => new IdDictionary());
|
||||||
|
|
||||||
dict.Add(0, new NvMapHandle());
|
dict.Add(0, new NvMapHandle());
|
||||||
}
|
}
|
||||||
|
@ -24,36 +24,36 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
|
||||||
{
|
{
|
||||||
NvInternalResult result = NvInternalResult.NotImplemented;
|
NvInternalResult result = NvInternalResult.NotImplemented;
|
||||||
|
|
||||||
if (command.GetTypeValue() == NvIoctl.NvMapMagic)
|
if (command.GetTypeValue() == NvIoctl.NvMapCustomMagic)
|
||||||
{
|
{
|
||||||
switch (command.GetNumberValue())
|
switch (command.GetNumberValue())
|
||||||
{
|
{
|
||||||
case 0x1:
|
case 0x01:
|
||||||
result = CallIoctlMethod<NvMapCreate>(Create, arguments);
|
result = CallIoctlMethod<NvMapCreate>(Create, arguments);
|
||||||
break;
|
break;
|
||||||
case 0x3:
|
case 0x03:
|
||||||
result = CallIoctlMethod<NvMapFromId>(FromId, arguments);
|
result = CallIoctlMethod<NvMapFromId>(FromId, arguments);
|
||||||
break;
|
break;
|
||||||
case 0x4:
|
case 0x04:
|
||||||
result = CallIoctlMethod<NvMapAlloc>(Alloc, arguments);
|
result = CallIoctlMethod<NvMapAlloc>(Alloc, arguments);
|
||||||
break;
|
break;
|
||||||
case 0x5:
|
case 0x05:
|
||||||
result = CallIoctlMethod<NvMapFree>(Free, arguments);
|
result = CallIoctlMethod<NvMapFree>(Free, arguments);
|
||||||
break;
|
break;
|
||||||
case 0x9:
|
case 0x09:
|
||||||
result = CallIoctlMethod<NvMapParam>(Param, arguments);
|
result = CallIoctlMethod<NvMapParam>(Param, arguments);
|
||||||
break;
|
break;
|
||||||
case 0xe:
|
case 0x0e:
|
||||||
result = CallIoctlMethod<NvMapGetId>(GetId, arguments);
|
result = CallIoctlMethod<NvMapGetId>(GetId, arguments);
|
||||||
break;
|
break;
|
||||||
case 0x2:
|
case 0x02:
|
||||||
case 0x6:
|
case 0x06:
|
||||||
case 0x7:
|
case 0x07:
|
||||||
case 0x8:
|
case 0x08:
|
||||||
case 0xa:
|
case 0x0a:
|
||||||
case 0xc:
|
case 0x0c:
|
||||||
case 0xd:
|
case 0x0d:
|
||||||
case 0xf:
|
case 0x0f:
|
||||||
case 0x10:
|
case 0x10:
|
||||||
case 0x11:
|
case 0x11:
|
||||||
result = NvInternalResult.NotSupported;
|
result = NvInternalResult.NotSupported;
|
||||||
|
|
|
@ -6,10 +6,11 @@ namespace Ryujinx.HLE.HOS.Services.Nv
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
struct NvIoctl
|
struct NvIoctl
|
||||||
{
|
{
|
||||||
public const int NvHostMagic = 0x00;
|
public const int NvHostCustomMagic = 0x00;
|
||||||
public const int NvMapMagic = 0x01;
|
public const int NvMapCustomMagic = 0x01;
|
||||||
public const int NvGpuAsMagic = 0x41;
|
public const int NvGpuAsMagic = 0x41;
|
||||||
public const int NvGpuMagic = 0x47;
|
public const int NvGpuMagic = 0x47;
|
||||||
|
public const int NvHostMagic = 0x48;
|
||||||
|
|
||||||
private const int NumberBits = 8;
|
private const int NumberBits = 8;
|
||||||
private const int TypeBits = 8;
|
private const int TypeBits = 8;
|
||||||
|
|
11
Ryujinx.HLE/HOS/Services/Nv/Types/NvFence.cs
Normal file
11
Ryujinx.HLE/HOS/Services/Nv/Types/NvFence.cs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.HOS.Services.Nv.Types
|
||||||
|
{
|
||||||
|
[StructLayout(LayoutKind.Sequential, Size = 0x8)]
|
||||||
|
internal struct NvFence
|
||||||
|
{
|
||||||
|
public uint Id;
|
||||||
|
public uint Value;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue