Initial implementation of NvMap/NvHostCtrl

This commit is contained in:
gdkchan 2018-04-29 14:59:25 -03:00
parent 5f0dd965bf
commit d27f1f4607
16 changed files with 781 additions and 33 deletions

View file

@ -1,31 +0,0 @@
using System.Collections.Concurrent;
namespace Ryujinx.Core.OsHle.Services.Nv
{
class NvChNvMap
{
private static ConcurrentDictionary<Process, IdDictionary> NvMaps;
public void Create(ServiceCtx Context)
{
long InputPosition = Context.Request.GetBufferType0x21Position();
long OutputPosition = Context.Request.GetBufferType0x22Position();
int Size = Context.Memory.ReadInt32(InputPosition);
int Handle = AddNvMap(Context, new NvMap(Size));
Context.Memory.WriteInt32(OutputPosition, Handle);
}
private int AddNvMap(ServiceCtx Context, NvMap Map)
{
return NvMaps[Context.Process].Add(Map);
}
public NvMap GetNvMap(ServiceCtx Context, int Handle)
{
return NvMaps[Context.Process].GetData<NvMap>(Handle);
}
}
}

View file

@ -0,0 +1,10 @@
namespace Ryujinx.Core.OsHle.Services.Nv
{
static class NvHelper
{
public static void Crash()
{
}
}
}

View file

@ -0,0 +1,320 @@
using ChocolArm64.Memory;
using Ryujinx.Core.Logging;
using System.Collections.Concurrent;
using System.Threading;
namespace Ryujinx.Core.OsHle.Services.Nv
{
class NvHostCtrlIoctl
{
private const int EventsCount = 64;
private static ConcurrentDictionary<Process, NvHostEvent[]> EventArrays;
private static ConcurrentDictionary<Process, IdDictionary> NvSyncPts;
static NvHostCtrlIoctl()
{
EventArrays = new ConcurrentDictionary<Process, NvHostEvent[]>();
NvSyncPts = new ConcurrentDictionary<Process, IdDictionary>();
}
private int SyncPtRead(ServiceCtx Context)
{
return SyncPtReadMinOrMax(Context, Max: false);
}
private int SyncPtIncr(ServiceCtx Context)
{
long InputPosition = Context.Request.GetBufferType0x21Position();
int Id = Context.Memory.ReadInt32(InputPosition);
GetOrAddNvSyncPt(Context, Id).Increment();
return NvResult.Success;
}
private int SyncPtWait(ServiceCtx Context)
{
return SyncPtWait(Context, Extended: false);
}
private int SyncPtWaitEx(ServiceCtx Context)
{
return SyncPtWait(Context, Extended: true);
}
private int SyncPtReadMax(ServiceCtx Context)
{
return SyncPtReadMinOrMax(Context, Max: true);
}
private int SyncPtReadMinOrMax(ServiceCtx Context, bool Max)
{
long InputPosition = Context.Request.GetBufferType0x21Position();
long OutputPosition = Context.Request.GetBufferType0x22Position();
NvHostCtrlSyncPtRead Args = AMemoryHelper.Read<NvHostCtrlSyncPtRead>(Context.Memory, InputPosition);
if (Max)
{
Args.Value = GetOrAddNvSyncPt(Context, Args.Id).CounterMax;
}
else
{
Args.Value = GetOrAddNvSyncPt(Context, Args.Id).CounterMin;
}
AMemoryHelper.Write(Context.Memory, OutputPosition, Args);
return NvResult.Success;
}
private int SyncPtWait(ServiceCtx Context, bool Extended)
{
long InputPosition = Context.Request.GetBufferType0x21Position();
long OutputPosition = Context.Request.GetBufferType0x22Position();
NvHostCtrlSyncPtWait Args = AMemoryHelper.Read<NvHostCtrlSyncPtWait>(Context.Memory, InputPosition);
NvSyncPt SyncPt = GetOrAddNvSyncPt(Context, Args.Id);
void WriteSyncPtMin()
{
if (Extended)
{
Context.Memory.WriteInt32(OutputPosition + 0xc, SyncPt.CounterMin);
}
}
if (SyncPt.MinCompare(Args.Thresh))
{
WriteSyncPtMin();
return NvResult.Success;
}
if (Args.Timeout == 0)
{
return NvResult.TryAgain;
}
Context.Ns.Log.PrintDebug(LogClass.ServiceNv, "Waiting syncpt with timeout of " + Args.Timeout + "ms...");
bool Signaled = true;
using (ManualResetEvent WaitEvent = new ManualResetEvent(false))
{
SyncPt.AddWaiter(Args.Thresh, WaitEvent);
if (Args.Timeout != -1)
{
//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 < 0)
{
Timeout = int.MaxValue;
}
Signaled = WaitEvent.WaitOne(Timeout);
}
else
{
WaitEvent.WaitOne();
}
}
WriteSyncPtMin();
if (Signaled)
{
Context.Ns.Log.PrintDebug(LogClass.ServiceNv, "Resuming...");
}
else
{
Context.Ns.Log.PrintDebug(LogClass.ServiceNv, "Timed out, resuming...");
}
return Signaled
? NvResult.Success
: NvResult.TimedOut;
}
private int EventWait(ServiceCtx Context, bool IsEvent)
{
long InputPosition = Context.Request.GetBufferType0x21Position();
long OutputPosition = Context.Request.GetBufferType0x22Position();
int Result, Value;
NvHostCtrlSyncPtWait Args = AMemoryHelper.Read<NvHostCtrlSyncPtWait>(Context.Memory, InputPosition);
if (Args.Timeout == 0)
{
return NvResult.TryAgain;
}
if (IsEvent || Args.Timeout == -1)
{
int EventIndex = GetFreeEvent(Context, Args.Id, out NvHostEvent Event);
if (EventIndex == EventsCount)
{
return NvResult.OutOfMemory;
}
Event.Id = Args.Id;
Event.Thresh = Args.Thresh;
Event.Free = false;
if (IsEvent)
{
Value = ((Args.Id & 0xfff) << 16) | 0x10000000;
}
else
{
Value = Args.Id << 4;
}
Value |= EventIndex;
Result = NvResult.TryAgain;
}
else
{
Context.Ns.Log.PrintDebug(LogClass.ServiceNv, "Waiting syncpt with timeout of " + Args.Timeout + "ms...");
bool Signaled = true;
NvSyncPt SyncPt = GetOrAddNvSyncPt(Context, Args.Id);
using (ManualResetEvent WaitEvent = new ManualResetEvent(false))
{
SyncPt.AddWaiter(Args.Thresh, WaitEvent);
if (Args.Timeout != -1)
{
//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 < 0)
{
Timeout = int.MaxValue;
}
Signaled = WaitEvent.WaitOne(Timeout);
}
else
{
WaitEvent.WaitOne();
}
}
if (Signaled)
{
Context.Ns.Log.PrintDebug(LogClass.ServiceNv, "Resuming...");
}
else
{
Context.Ns.Log.PrintDebug(LogClass.ServiceNv, "Timed out, resuming...");
}
Value = SyncPt.CounterMin;
Result = Signaled
? NvResult.Success
: NvResult.TimedOut;
}
Context.Memory.WriteInt32(OutputPosition + 0xc, Value);
return Result;
}
private NvSyncPt GetOrAddNvSyncPt(ServiceCtx Context, int Id)
{
NvSyncPt SyncPt = GetNvSyncPt(Context, Id);
if (SyncPt == null)
{
IdDictionary SyncPts = NvSyncPts.GetOrAdd(Context.Process, (Key) => new IdDictionary());
SyncPt = new NvSyncPt();
SyncPts.Add(Id, SyncPt);
}
return SyncPt;
}
public NvSyncPt GetNvSyncPt(ServiceCtx Context, int Id)
{
if (NvSyncPts.TryGetValue(Context.Process, out IdDictionary SyncPts))
{
return SyncPts.GetData<NvSyncPt>(Id);
}
return null;
}
private int GetFreeEvent(ServiceCtx Context, int Id, out NvHostEvent Event)
{
NvHostEvent[] Events = GetEvents(Context);
int NullIndex = EventsCount;
int FreeIndex = EventsCount;
for (int Index = 0; Index < EventsCount; Index++)
{
Event = Events[Index];
if (Event != null)
{
if (Event.Free && MinCompare(Context,
Event.Id,
Event.Thresh))
{
if (Event.Id == Id)
{
return Index;
}
FreeIndex = Index;
}
}
else if (NullIndex == EventsCount)
{
NullIndex = Index;
}
}
if (NullIndex < EventsCount)
{
Events[NullIndex] = Event = new NvHostEvent();
return NullIndex;
}
Event = FreeIndex < EventsCount ? Events[FreeIndex] : null;
return FreeIndex;
}
private bool MinCompare(ServiceCtx Context, int Id, int Threshold)
{
//TODO
return false;
}
private NvHostEvent[] GetEvents(ServiceCtx Context)
{
return EventArrays.GetOrAdd(Context.Process, (Key) => new NvHostEvent[EventsCount]);
}
}
}

View file

@ -0,0 +1,8 @@
namespace Ryujinx.Core.OsHle.Services.Nv
{
struct NvHostCtrlSyncPtRead
{
public int Id;
public int Value;
}
}

View file

@ -0,0 +1,9 @@
namespace Ryujinx.Core.OsHle.Services.Nv
{
struct NvHostCtrlSyncPtWait
{
public int Id;
public int Thresh;
public int Timeout;
}
}

View file

@ -0,0 +1,15 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
namespace Ryujinx.Core.OsHle.Services.Nv
{
class NvHostEvent
{
public int Id;
public int Thresh;
public bool Free;
}
}

View file

@ -1,3 +1,5 @@
using System.Threading;
namespace Ryujinx.Core.OsHle.Services.Nv
{
class NvMap
@ -10,11 +12,28 @@ namespace Ryujinx.Core.OsHle.Services.Nv
public long CpuAddress;
public long GpuAddress;
public NvMap() { }
private long m_RefCount;
public NvMap(int Size)
public long RefCount => m_RefCount;
public NvMap()
{
m_RefCount = 1;
}
public NvMap(int Size) : this()
{
this.Size = Size;
}
public long IncrementRefCount()
{
return Interlocked.Increment(ref m_RefCount);
}
public long DecrementRefCount()
{
return Interlocked.Decrement(ref m_RefCount);
}
}
}

View file

@ -0,0 +1,12 @@
namespace Ryujinx.Core.OsHle.Services.Nv
{
struct NvMapAlloc
{
public int Handle;
public int HeapMask;
public int Flags;
public int Align;
public long Kind;
public long Address;
}
}

View file

@ -0,0 +1,8 @@
namespace Ryujinx.Core.OsHle.Services.Nv
{
struct NvMapCreate
{
public int Size;
public int Handle;
}
}

View file

@ -0,0 +1,11 @@
namespace Ryujinx.Core.OsHle.Services.Nv
{
struct NvMapFree
{
public int Handle;
public int Padding;
public long RefCount;
public int Size;
public int Flags;
}
}

View file

@ -0,0 +1,8 @@
namespace Ryujinx.Core.OsHle.Services.Nv
{
struct NvMapFromId
{
public int Id;
public int Handle;
}
}

View file

@ -0,0 +1,8 @@
namespace Ryujinx.Core.OsHle.Services.Nv
{
struct NvMapGetId
{
public int Id;
public int Handle;
}
}

View file

@ -0,0 +1,238 @@
using ChocolArm64.Memory;
using Ryujinx.Graphics.Gpu;
using System.Collections.Concurrent;
namespace Ryujinx.Core.OsHle.Services.Nv
{
class NvMapIoctl
{
private NsGpuMemoryMgr Vmm;
private static ConcurrentDictionary<Process, IdDictionary> NvMaps;
private object NvMapLock;
private const int FlagNotFreedYet = 1;
private enum NvMapParam
{
Size = 1,
Align = 2,
Base = 3,
Heap = 4,
Kind = 5,
Compr = 6
}
public NvMapIoctl()
{
NvMapLock = new object();
}
public int Create(ServiceCtx Context)
{
long InputPosition = Context.Request.GetBufferType0x21Position();
long OutputPosition = Context.Request.GetBufferType0x22Position();
NvMapCreate Args = AMemoryHelper.Read<NvMapCreate>(Context.Memory, InputPosition);
if (Args.Size == 0)
{
return NvResult.InvalidInput;
}
Args.Handle = AddNvMap(Context, new NvMap(Args.Size));
AMemoryHelper.Write(Context.Memory, OutputPosition, Args);
return NvResult.Success;
}
public int IocFromId(ServiceCtx Context)
{
long InputPosition = Context.Request.GetBufferType0x21Position();
long OutputPosition = Context.Request.GetBufferType0x22Position();
NvMapFromId Args = AMemoryHelper.Read<NvMapFromId>(Context.Memory, InputPosition);
lock (NvMapLock)
{
NvMap Map = GetNvMap(Context, Args.Id);
if (Map == null)
{
return NvResult.InvalidInput;
}
Map.IncrementRefCount();
}
AMemoryHelper.Write(Context.Memory, OutputPosition, Args);
return NvResult.Success;
}
public int Alloc(ServiceCtx Context)
{
long InputPosition = Context.Request.GetBufferType0x21Position();
long OutputPosition = Context.Request.GetBufferType0x22Position();
NvMapAlloc Args = AMemoryHelper.Read<NvMapAlloc>(Context.Memory, InputPosition);
NvMap Map = GetNvMap(Context, Args.Handle);
if (Map == null)
{
return NvResult.InvalidInput;
}
if ((Args.Align & (Args.Align - 1)) != 0)
{
return NvResult.InvalidInput;
}
if ((uint)Args.Align < 0x1000)
{
Args.Align = 0x1000;
}
Map.Align = Args.Align;
Map.Kind = (byte)Args.Kind;
Args.Address = Vmm.Reserve(Args.Address, (uint)Map.Size, (uint)Map.Align);
AMemoryHelper.Write(Context.Memory, OutputPosition, Args);
return NvResult.Success;
}
public int Free(ServiceCtx Context)
{
long InputPosition = Context.Request.GetBufferType0x21Position();
long OutputPosition = Context.Request.GetBufferType0x22Position();
NvMapFree Args = AMemoryHelper.Read<NvMapFree>(Context.Memory, InputPosition);
lock (NvMapLock)
{
NvMap Map = GetNvMap(Context, Args.Handle);
if (Map == null)
{
return NvResult.InvalidInput;
}
long RefCount = Map.DecrementRefCount();
if (RefCount <= 0)
{
DeleteNvMap(Context, Args.Handle);
Args.Flags = 0;
}
else
{
Args.Flags = FlagNotFreedYet;
}
Args.RefCount = RefCount;
Args.Size = Map.Size;
}
AMemoryHelper.Write(Context.Memory, OutputPosition, Args);
return NvResult.Success;
}
public int Param(ServiceCtx Context)
{
long InputPosition = Context.Request.GetBufferType0x21Position();
long OutputPosition = Context.Request.GetBufferType0x22Position();
Nv.NvMapParam Args = AMemoryHelper.Read<Nv.NvMapParam>(Context.Memory, InputPosition);
NvMap Map = GetNvMap(Context, Args.Handle);
if (Map == null)
{
return NvResult.InvalidInput;
}
switch ((NvMapParam)Args.Param)
{
case NvMapParam.Size: Args.Result = Map.Size; break;
case NvMapParam.Align: Args.Result = Map.Align; break;
case NvMapParam.Heap: Args.Result = 0x40000000; break;
case NvMapParam.Kind: Args.Result = Map.Kind; break;
case NvMapParam.Compr: Args.Result = 0; break;
//Note: Base is not supported and returns an error.
//Any other value also returns an error.
default: return NvResult.InvalidInput;
}
AMemoryHelper.Write(Context.Memory, OutputPosition, Args);
return NvResult.Success;
}
public int GetId(ServiceCtx Context)
{
long InputPosition = Context.Request.GetBufferType0x21Position();
long OutputPosition = Context.Request.GetBufferType0x22Position();
NvMapGetId Args = AMemoryHelper.Read<NvMapGetId>(Context.Memory, InputPosition);
NvMap Map = GetNvMap(Context, Args.Handle);
if (Map == null)
{
return NvResult.InvalidInput;
}
Args.Id = Args.Handle;
AMemoryHelper.Write(Context.Memory, OutputPosition, Args);
return NvResult.Success;
}
private int AddNvMap(ServiceCtx Context, NvMap Map)
{
IdDictionary Maps = NvMaps.GetOrAdd(Context.Process, (Key) => new IdDictionary());
return Maps.Add(Map);
}
private bool DeleteNvMap(ServiceCtx Context, int Handle)
{
if (NvMaps.TryGetValue(Context.Process, out IdDictionary Maps))
{
return Maps.Delete(Handle) != null;
}
return false;
}
public NvMap GetNvMapWithFb(ServiceCtx Context, int Handle)
{
if (NvMaps.TryGetValue(Context.Process, out IdDictionary Maps))
{
return Maps.GetData<NvMap>(Handle);
}
return null;
}
public NvMap GetNvMap(ServiceCtx Context, int Handle)
{
if (Handle != 0 && NvMaps.TryGetValue(Context.Process, out IdDictionary Maps))
{
return Maps.GetData<NvMap>(Handle);
}
return null;
}
}
}

View file

@ -0,0 +1,9 @@
namespace Ryujinx.Core.OsHle.Services.Nv
{
struct NvMapParam
{
public int Handle;
public int Param;
public int Result;
}
}

View file

@ -0,0 +1,12 @@
namespace Ryujinx.Core.OsHle.Services.Nv
{
static class NvResult
{
public const int Success = 0;
public const int TryAgain = -11;
public const int OutOfMemory = -12;
public const int InvalidInput = -22;
public const int NotSupported = -25;
public const int TimedOut = -110;
}
}

View file

@ -0,0 +1,92 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
namespace Ryujinx.Core.OsHle.Services.Nv
{
class NvSyncPt
{
private int m_CounterMin;
private int m_CounterMax;
public int CounterMin => m_CounterMin;
public int CounterMax => m_CounterMax;
public bool IsEvent { get; set; }
private ConcurrentDictionary<EventWaitHandle, int> Waiters;
public NvSyncPt()
{
Waiters = new ConcurrentDictionary<EventWaitHandle, int>();
}
public int Increment()
{
Interlocked.Increment(ref m_CounterMax);
return IncrementMin();
}
public int IncrementMin()
{
int Value = Interlocked.Increment(ref m_CounterMin);
WakeUpWaiters(Value);
return Value;
}
public int IncrementMax()
{
return Interlocked.Increment(ref m_CounterMax);
}
public void AddWaiter(int Threshold, EventWaitHandle WaitEvent)
{
if (!Waiters.TryAdd(WaitEvent, Threshold))
{
throw new InvalidOperationException();
}
}
public bool RemoveWaiter(EventWaitHandle WaitEvent)
{
return Waiters.TryRemove(WaitEvent, out _);
}
private void WakeUpWaiters(int NewValue)
{
foreach (KeyValuePair<EventWaitHandle, int> KV in Waiters)
{
if (MinCompare(NewValue, m_CounterMax, KV.Value))
{
KV.Key.Set();
Waiters.TryRemove(KV.Key, out _);
}
}
}
public bool MinCompare(int Threshold)
{
return MinCompare(m_CounterMin, m_CounterMax, Threshold);
}
private bool MinCompare(int Min, int Max, int Threshold)
{
int MinDiff = Min - Threshold;
int MaxDiff = Max - Threshold;
if (IsEvent)
{
return MinDiff >= 0;
}
else
{
return (uint)MaxDiff >= (uint)MinDiff;
}
}
}
}