Rewrite nvservices (#800)

* Start rewriting nvservices internals

TODO:

- nvgpu device interface
- nvhost generic device interface

* Some clean up and fixes

- Make sure to remove the fd of a closed channel.
- NvFileDevice now doesn't implement Disposable as it was never used.
- Rename NvHostCtrlGetConfigurationArgument to GetConfigurationArguments
to follow calling convention.
- Make sure to check every ioctls magic.

* Finalize migration for ioctl standard variant

TODO: ioctl2 migration

* Implement SubmitGpfifoEx and fix nvdec

* Implement Ioctl3

* Implement some ioctl3 required by recent games

* Remove unused code and outdated comments

* Return valid event handles with QueryEvent

Also add an exception for unimplemented event ids.

This commit doesn't implement accurately the events, this only define
different events for different event ids.

* Rename all occurance of FileDevice to DeviceFile

* Restub SetClientPid to not cause regressions

* Address comments

* Remove GlobalStateTable

* Address comments

* Align variables in ioctl3

* Some missing alignments

* GetVaRegionsArguments realign

* Make Owner public in NvDeviceFile

* Address LDj3SNuD's comments
This commit is contained in:
Thomas Guillemard 2019-11-02 23:47:56 +01:00 committed by jduncanator
parent c414a356a0
commit be9b9c7cdf
75 changed files with 2798 additions and 2005 deletions

View file

@ -0,0 +1,318 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.Memory;
using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types;
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap;
using System;
using System.Collections.Concurrent;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu
{
class NvHostAsGpuDeviceFile : NvDeviceFile
{
private static ConcurrentDictionary<KProcess, AddressSpaceContext> _addressSpaceContextRegistry = new ConcurrentDictionary<KProcess, AddressSpaceContext>();
public NvHostAsGpuDeviceFile(ServiceCtx context) : base(context) { }
public override NvInternalResult Ioctl(NvIoctl command, Span<byte> arguments)
{
NvInternalResult result = NvInternalResult.NotImplemented;
if (command.Type == NvIoctl.NvGpuAsMagic)
{
switch (command.Number)
{
case 0x01:
result = CallIoctlMethod<BindChannelArguments>(BindChannel, arguments);
break;
case 0x02:
result = CallIoctlMethod<AllocSpaceArguments>(AllocSpace, arguments);
break;
case 0x03:
result = CallIoctlMethod<FreeSpaceArguments>(FreeSpace, arguments);
break;
case 0x05:
result = CallIoctlMethod<UnmapBufferArguments>(UnmapBuffer, arguments);
break;
case 0x06:
result = CallIoctlMethod<MapBufferExArguments>(MapBufferEx, arguments);
break;
case 0x08:
result = CallIoctlMethod<GetVaRegionsArguments>(GetVaRegions, arguments);
break;
case 0x09:
result = CallIoctlMethod<InitializeExArguments>(InitializeEx, arguments);
break;
case 0x14:
result = CallIoctlMethod<RemapArguments>(Remap, arguments);
break;
}
}
return result;
}
public override NvInternalResult Ioctl3(NvIoctl command, Span<byte> arguments, Span<byte> inlineOutBuffer)
{
NvInternalResult result = NvInternalResult.NotImplemented;
if (command.Type == NvIoctl.NvGpuAsMagic)
{
switch (command.Number)
{
case 0x08:
// This is the same as the one in ioctl as inlineOutBuffer is empty.
result = CallIoctlMethod<GetVaRegionsArguments>(GetVaRegions, arguments);
break;
}
}
return result;
}
private NvInternalResult BindChannel(ref BindChannelArguments arguments)
{
Logger.PrintStub(LogClass.ServiceNv);
return NvInternalResult.Success;
}
private NvInternalResult AllocSpace(ref AllocSpaceArguments arguments)
{
AddressSpaceContext addressSpaceContext = GetAddressSpaceContext(Owner);
ulong size = (ulong)arguments.Pages * (ulong)arguments.PageSize;
NvInternalResult result = NvInternalResult.Success;
lock (addressSpaceContext)
{
// Note: When the fixed offset flag is not set,
// the Offset field holds the alignment size instead.
if ((arguments.Flags & AddressSpaceFlags.FixedOffset) != 0)
{
arguments.Offset = addressSpaceContext.Vmm.ReserveFixed(arguments.Offset, (long)size);
}
else
{
arguments.Offset = addressSpaceContext.Vmm.Reserve((long)size, arguments.Offset);
}
if (arguments.Offset < 0)
{
arguments.Offset = 0;
Logger.PrintWarning(LogClass.ServiceNv, $"Failed to allocate size {size:x16}!");
result = NvInternalResult.OutOfMemory;
}
else
{
addressSpaceContext.AddReservation(arguments.Offset, (long)size);
}
}
return result;
}
private NvInternalResult FreeSpace(ref FreeSpaceArguments arguments)
{
AddressSpaceContext addressSpaceContext = GetAddressSpaceContext(Owner);
NvInternalResult result = NvInternalResult.Success;
lock (addressSpaceContext)
{
ulong size = (ulong)arguments.Pages * (ulong)arguments.PageSize;
if (addressSpaceContext.RemoveReservation(arguments.Offset))
{
addressSpaceContext.Vmm.Free(arguments.Offset, (long)size);
}
else
{
Logger.PrintWarning(LogClass.ServiceNv,
$"Failed to free offset 0x{arguments.Offset:x16} size 0x{size:x16}!");
result = NvInternalResult.InvalidInput;
}
}
return result;
}
private NvInternalResult UnmapBuffer(ref UnmapBufferArguments arguments)
{
AddressSpaceContext addressSpaceContext = GetAddressSpaceContext(Owner);
lock (addressSpaceContext)
{
if (addressSpaceContext.RemoveMap(arguments.Offset, out long size))
{
if (size != 0)
{
addressSpaceContext.Vmm.Free(arguments.Offset, size);
}
}
else
{
Logger.PrintWarning(LogClass.ServiceNv, $"Invalid buffer offset {arguments.Offset:x16}!");
}
}
return NvInternalResult.Success;
}
private NvInternalResult MapBufferEx(ref MapBufferExArguments arguments)
{
const string mapErrorMsg = "Failed to map fixed buffer with offset 0x{0:x16} and size 0x{1:x16}!";
AddressSpaceContext addressSpaceContext = GetAddressSpaceContext(Owner);
NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, arguments.NvMapHandle, true);
if (map == null)
{
Logger.PrintWarning(LogClass.ServiceNv, $"Invalid NvMap handle 0x{arguments.NvMapHandle:x8}!");
return NvInternalResult.InvalidInput;
}
long physicalAddress;
if ((arguments.Flags & AddressSpaceFlags.RemapSubRange) != 0)
{
lock (addressSpaceContext)
{
if (addressSpaceContext.TryGetMapPhysicalAddress(arguments.Offset, out physicalAddress))
{
long virtualAddress = arguments.Offset + arguments.BufferOffset;
physicalAddress += arguments.BufferOffset;
if (addressSpaceContext.Vmm.Map(physicalAddress, virtualAddress, arguments.MappingSize) < 0)
{
string message = string.Format(mapErrorMsg, virtualAddress, arguments.MappingSize);
Logger.PrintWarning(LogClass.ServiceNv, message);
return NvInternalResult.InvalidInput;
}
return NvInternalResult.Success;
}
else
{
Logger.PrintWarning(LogClass.ServiceNv, $"Address 0x{arguments.Offset:x16} not mapped!");
return NvInternalResult.InvalidInput;
}
}
}
physicalAddress = map.Address + arguments.BufferOffset;
long size = arguments.MappingSize;
if (size == 0)
{
size = (uint)map.Size;
}
NvInternalResult result = NvInternalResult.Success;
lock (addressSpaceContext)
{
// Note: When the fixed offset flag is not set,
// the Offset field holds the alignment size instead.
bool virtualAddressAllocated = (arguments.Flags & AddressSpaceFlags.FixedOffset) == 0;
if (!virtualAddressAllocated)
{
if (addressSpaceContext.ValidateFixedBuffer(arguments.Offset, size))
{
arguments.Offset = addressSpaceContext.Vmm.Map(physicalAddress, arguments.Offset, size);
}
else
{
string message = string.Format(mapErrorMsg, arguments.Offset, size);
Logger.PrintWarning(LogClass.ServiceNv, message);
result = NvInternalResult.InvalidInput;
}
}
else
{
arguments.Offset = addressSpaceContext.Vmm.Map(physicalAddress, size);
}
if (arguments.Offset < 0)
{
arguments.Offset = 0;
Logger.PrintWarning(LogClass.ServiceNv, $"Failed to map size 0x{size:x16}!");
result = NvInternalResult.InvalidInput;
}
else
{
addressSpaceContext.AddMap(arguments.Offset, size, physicalAddress, virtualAddressAllocated);
}
}
return result;
}
private NvInternalResult GetVaRegions(ref GetVaRegionsArguments arguments)
{
Logger.PrintStub(LogClass.ServiceNv);
return NvInternalResult.Success;
}
private NvInternalResult InitializeEx(ref InitializeExArguments arguments)
{
Logger.PrintStub(LogClass.ServiceNv);
return NvInternalResult.Success;
}
private NvInternalResult Remap(Span<RemapArguments> arguments)
{
for (int index = 0; index < arguments.Length; index++)
{
NvGpuVmm vmm = GetAddressSpaceContext(Owner).Vmm;
NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, arguments[index].NvMapHandle, true);
if (map == null)
{
Logger.PrintWarning(LogClass.ServiceNv, $"Invalid NvMap handle 0x{arguments[index].NvMapHandle:x8}!");
return NvInternalResult.InvalidInput;
}
long result = vmm.Map(map.Address, (long)arguments[index].Offset << 16,
(long)arguments[index].Pages << 16);
if (result < 0)
{
Logger.PrintWarning(LogClass.ServiceNv,
$"Page 0x{arguments[index].Offset:x16} size 0x{arguments[index].Pages:x16} not allocated!");
return NvInternalResult.InvalidInput;
}
}
return NvInternalResult.Success;
}
public override void Close() { }
public static AddressSpaceContext GetAddressSpaceContext(KProcess process)
{
return _addressSpaceContextRegistry.GetOrAdd(process, (key) => new AddressSpaceContext(process));
}
}
}

View file

@ -0,0 +1,204 @@
using ARMeilleure.Memory;
using Ryujinx.Graphics.Memory;
using Ryujinx.HLE.HOS.Kernel.Process;
using System;
using System.Collections.Generic;
using System.Text;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types
{
class AddressSpaceContext
{
public NvGpuVmm Vmm { get; private set; }
private class Range
{
public ulong Start { get; private set; }
public ulong End { get; private set; }
public Range(long position, long size)
{
Start = (ulong)position;
End = (ulong)size + Start;
}
}
private class MappedMemory : Range
{
public long PhysicalAddress { get; private set; }
public bool VaAllocated { get; private set; }
public MappedMemory(
long position,
long size,
long physicalAddress,
bool vaAllocated) : base(position, size)
{
PhysicalAddress = physicalAddress;
VaAllocated = vaAllocated;
}
}
private SortedList<long, Range> _maps;
private SortedList<long, Range> _reservations;
public AddressSpaceContext(KProcess process)
{
Vmm = new NvGpuVmm(process.CpuMemory);
_maps = new SortedList<long, Range>();
_reservations = new SortedList<long, Range>();
}
public bool ValidateFixedBuffer(long position, long size)
{
long mapEnd = position + size;
// Check if size is valid (0 is also not allowed).
if ((ulong)mapEnd <= (ulong)position)
{
return false;
}
// Check if address is page aligned.
if ((position & NvGpuVmm.PageMask) != 0)
{
return false;
}
// Check if region is reserved.
if (BinarySearch(_reservations, position) == null)
{
return false;
}
// Check for overlap with already mapped buffers.
Range map = BinarySearchLt(_maps, mapEnd);
if (map != null && map.End > (ulong)position)
{
return false;
}
return true;
}
public void AddMap(
long position,
long size,
long physicalAddress,
bool vaAllocated)
{
_maps.Add(position, new MappedMemory(position, size, physicalAddress, vaAllocated));
}
public bool RemoveMap(long position, out long size)
{
size = 0;
if (_maps.Remove(position, out Range value))
{
MappedMemory map = (MappedMemory)value;
if (map.VaAllocated)
{
size = (long)(map.End - map.Start);
}
return true;
}
return false;
}
public bool TryGetMapPhysicalAddress(long position, out long physicalAddress)
{
Range map = BinarySearch(_maps, position);
if (map != null)
{
physicalAddress = ((MappedMemory)map).PhysicalAddress;
return true;
}
physicalAddress = 0;
return false;
}
public void AddReservation(long position, long size)
{
_reservations.Add(position, new Range(position, size));
}
public bool RemoveReservation(long position)
{
return _reservations.Remove(position);
}
private Range BinarySearch(SortedList<long, Range> lst, long position)
{
int left = 0;
int right = lst.Count - 1;
while (left <= right)
{
int size = right - left;
int middle = left + (size >> 1);
Range rg = lst.Values[middle];
if ((ulong)position >= rg.Start && (ulong)position < rg.End)
{
return rg;
}
if ((ulong)position < rg.Start)
{
right = middle - 1;
}
else
{
left = middle + 1;
}
}
return null;
}
private Range BinarySearchLt(SortedList<long, Range> lst, long position)
{
Range ltRg = null;
int left = 0;
int right = lst.Count - 1;
while (left <= right)
{
int size = right - left;
int middle = left + (size >> 1);
Range rg = lst.Values[middle];
if ((ulong)position < rg.Start)
{
right = middle - 1;
}
else
{
left = middle + 1;
if ((ulong)position > rg.Start)
{
ltRg = rg;
}
}
}
return ltRg;
}
}
}

View file

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

View file

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

View file

@ -0,0 +1,10 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types
{
[StructLayout(LayoutKind.Sequential)]
struct BindChannelArguments
{
public int Fd;
}
}

View file

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

View file

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

View file

@ -0,0 +1,16 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types
{
[StructLayout(LayoutKind.Sequential)]
struct InitializeExArguments
{
public uint Flags;
public int AsFd;
public uint BigPageSize;
public uint Reserved;
public ulong Unknown0;
public ulong Unknown1;
public ulong Unknown2;
}
}

View file

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

View file

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

View file

@ -0,0 +1,7 @@
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types
{
struct UnmapBufferArguments
{
public long Offset;
}
}