More work on NvHostCtrl

This commit is contained in:
gdkchan 2018-05-01 02:15:20 -03:00
commit f992843b6d
5 changed files with 197 additions and 190 deletions

View file

@ -11,13 +11,13 @@ namespace Ryujinx.Core.OsHle.Services.Nv
private static ConcurrentDictionary<Process, NvHostEvent[]> EventArrays; private static ConcurrentDictionary<Process, NvHostEvent[]> EventArrays;
private static ConcurrentDictionary<Process, IdDictionary> NvSyncPts; private static ConcurrentDictionary<Process, NvHostSyncPt> NvSyncPts;
static NvHostCtrlIoctl() static NvHostCtrlIoctl()
{ {
EventArrays = new ConcurrentDictionary<Process, NvHostEvent[]>(); EventArrays = new ConcurrentDictionary<Process, NvHostEvent[]>();
NvSyncPts = new ConcurrentDictionary<Process, IdDictionary>(); NvSyncPts = new ConcurrentDictionary<Process, NvHostSyncPt>();
} }
private int SyncPtRead(ServiceCtx Context) private int SyncPtRead(ServiceCtx Context)
@ -31,7 +31,12 @@ namespace Ryujinx.Core.OsHle.Services.Nv
int Id = Context.Memory.ReadInt32(InputPosition); int Id = Context.Memory.ReadInt32(InputPosition);
GetOrAddNvSyncPt(Context, Id).Increment(); if ((uint)Id >= NvHostSyncPt.SyncPtsCount)
{
return NvResult.InvalidInput;
}
GetSyncPt(Context).Increment(Id);
return NvResult.Success; return NvResult.Success;
} }
@ -51,6 +56,16 @@ namespace Ryujinx.Core.OsHle.Services.Nv
return SyncPtReadMinOrMax(Context, Max: true); return SyncPtReadMinOrMax(Context, Max: true);
} }
private int EventWait(ServiceCtx Context)
{
return EventWait(Context, Async: false);
}
private int EventWaitAsync(ServiceCtx Context)
{
return EventWait(Context, Async: true);
}
private int SyncPtReadMinOrMax(ServiceCtx Context, bool Max) private int SyncPtReadMinOrMax(ServiceCtx Context, bool Max)
{ {
long InputPosition = Context.Request.GetBufferType0x21Position(); long InputPosition = Context.Request.GetBufferType0x21Position();
@ -58,13 +73,18 @@ namespace Ryujinx.Core.OsHle.Services.Nv
NvHostCtrlSyncPtRead Args = AMemoryHelper.Read<NvHostCtrlSyncPtRead>(Context.Memory, InputPosition); NvHostCtrlSyncPtRead Args = AMemoryHelper.Read<NvHostCtrlSyncPtRead>(Context.Memory, InputPosition);
if ((uint)Args.Id >= NvHostSyncPt.SyncPtsCount)
{
return NvResult.InvalidInput;
}
if (Max) if (Max)
{ {
Args.Value = GetOrAddNvSyncPt(Context, Args.Id).CounterMax; Args.Value = GetSyncPt(Context).GetMax(Args.Id);
} }
else else
{ {
Args.Value = GetOrAddNvSyncPt(Context, Args.Id).CounterMin; Args.Value = GetSyncPt(Context).GetMin(Args.Id);
} }
AMemoryHelper.Write(Context.Memory, OutputPosition, Args); AMemoryHelper.Write(Context.Memory, OutputPosition, Args);
@ -79,213 +99,171 @@ namespace Ryujinx.Core.OsHle.Services.Nv
NvHostCtrlSyncPtWait Args = AMemoryHelper.Read<NvHostCtrlSyncPtWait>(Context.Memory, InputPosition); NvHostCtrlSyncPtWait Args = AMemoryHelper.Read<NvHostCtrlSyncPtWait>(Context.Memory, InputPosition);
NvSyncPt SyncPt = GetOrAddNvSyncPt(Context, Args.Id); NvHostSyncPt SyncPt = GetSyncPt(Context);
void WriteSyncPtMin() if ((uint)Args.Id >= NvHostSyncPt.SyncPtsCount)
{ {
if (Extended) return NvResult.InvalidInput;
{
Context.Memory.WriteInt32(OutputPosition + 0xc, SyncPt.CounterMin);
}
} }
if (SyncPt.MinCompare(Args.Thresh)) int Result;
{
WriteSyncPtMin();
return NvResult.Success; if (SyncPt.MinCompare(Args.Id, Args.Thresh))
{
Result = NvResult.Success;
} }
else if (Args.Timeout == 0)
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; Result = NvResult.TryAgain;
} }
else else
{ {
Context.Ns.Log.PrintDebug(LogClass.ServiceNv, "Waiting syncpt with timeout of " + Args.Timeout + "ms..."); 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)) using (ManualResetEvent WaitEvent = new ManualResetEvent(false))
{ {
SyncPt.AddWaiter(Args.Thresh, WaitEvent); 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 < -1)
{ {
//Note: Negative (> INT_MAX) timeouts aren't valid on .NET, Timeout = int.MaxValue;
//in this case we just use the maximum timeout possible. }
int Timeout = Args.Timeout;
if (Timeout < 0) if (Timeout == -1)
{ {
Timeout = int.MaxValue; WaitEvent.WaitOne();
}
Signaled = WaitEvent.WaitOne(Timeout); Result = NvResult.Success;
}
else if (WaitEvent.WaitOne(Timeout))
{
Result = NvResult.Success;
} }
else else
{ {
WaitEvent.WaitOne(); Result = NvResult.TimedOut;
} }
}
if (Signaled)
{
Context.Ns.Log.PrintDebug(LogClass.ServiceNv, "Resuming..."); 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); if (Extended)
{
Context.Memory.WriteInt32(OutputPosition + 0xc, SyncPt.GetMin(Args.Id));
}
return Result; return Result;
} }
private NvSyncPt GetOrAddNvSyncPt(ServiceCtx Context, int Id) private int EventWait(ServiceCtx Context, bool Async)
{ {
NvSyncPt SyncPt = GetNvSyncPt(Context, Id); long InputPosition = Context.Request.GetBufferType0x21Position();
long OutputPosition = Context.Request.GetBufferType0x22Position();
if (SyncPt == null) int Result;
NvHostCtrlSyncPtWaitEx Args = AMemoryHelper.Read<NvHostCtrlSyncPtWaitEx>(Context.Memory, InputPosition);
if ((uint)Args.Id >= NvHostSyncPt.SyncPtsCount)
{ {
IdDictionary SyncPts = NvSyncPts.GetOrAdd(Context.Process, (Key) => new IdDictionary()); return NvResult.InvalidInput;
SyncPt = new NvSyncPt();
SyncPts.Add(Id, SyncPt);
} }
return SyncPt; NvHostSyncPt SyncPt = GetSyncPt(Context);
}
public NvSyncPt GetNvSyncPt(ServiceCtx Context, int Id) if (SyncPt.MinCompare(Args.Id, Args.Thresh))
{
if (NvSyncPts.TryGetValue(Context.Process, out IdDictionary SyncPts))
{ {
return SyncPts.GetData<NvSyncPt>(Id); Args.Value = SyncPt.GetMin(Args.Id);
return NvResult.Success;
} }
return null; if (Args.Timeout == 0)
{
if (!Async)
{
Args.Value = 0;
}
return NvResult.TryAgain;
}
NvHostEvent Event;
int EventIndex;
if (Async)
{
EventIndex = Args.Value;
if ((uint)EventIndex >= EventsCount)
{
return NvResult.InvalidInput;
}
Event = GetEvents(Context)[EventIndex];
}
else
{
Event = GetFreeEvent(Context, SyncPt, Args.Id, out EventIndex);
}
if (Event == null)
{
return NvResult.InvalidInput;
}
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 = NvResult.TryAgain;
AMemoryHelper.Write(Context.Memory, OutputPosition, Args);
return Result;
} }
private int GetFreeEvent(ServiceCtx Context, int Id, out NvHostEvent Event) private NvHostEvent GetFreeEvent(ServiceCtx Context, NvHostSyncPt SyncPt, int Id, out int EventIndex)
{ {
NvHostEvent[] Events = GetEvents(Context); NvHostEvent[] Events = GetEvents(Context);
EventIndex = EventsCount;
int NullIndex = EventsCount; int NullIndex = EventsCount;
int FreeIndex = EventsCount;
for (int Index = 0; Index < EventsCount; Index++) for (int Index = 0; Index < EventsCount; Index++)
{ {
Event = Events[Index]; NvHostEvent Event = Events[Index];
if (Event != null) if (Event != null)
{ {
if (Event.Free && MinCompare(Context, if (Event.State == NvHostEventState.Registered ||
Event.Id, Event.State == NvHostEventState.Free)
Event.Thresh))
{ {
EventIndex = Index;
if (Event.Id == Id) if (Event.Id == Id)
{ {
return Index; return Event;
} }
FreeIndex = Index;
} }
} }
else if (NullIndex == EventsCount) else if (NullIndex == EventsCount)
@ -296,23 +274,25 @@ namespace Ryujinx.Core.OsHle.Services.Nv
if (NullIndex < EventsCount) if (NullIndex < EventsCount)
{ {
Events[NullIndex] = Event = new NvHostEvent(); EventIndex = NullIndex;
return NullIndex; return Events[NullIndex] = new NvHostEvent();
} }
Event = FreeIndex < EventsCount ? Events[FreeIndex] : null; if (EventIndex < EventsCount)
{
return Events[EventIndex];
}
return FreeIndex; return null;
} }
private bool MinCompare(ServiceCtx Context, int Id, int Threshold) public static NvHostSyncPt GetSyncPt(ServiceCtx Context)
{ {
//TODO return NvSyncPts.GetOrAdd(Context.Process, (Key) => new NvHostSyncPt());
return false;
} }
private NvHostEvent[] GetEvents(ServiceCtx Context) private static NvHostEvent[] GetEvents(ServiceCtx Context)
{ {
return EventArrays.GetOrAdd(Context.Process, (Key) => new NvHostEvent[EventsCount]); return EventArrays.GetOrAdd(Context.Process, (Key) => new NvHostEvent[EventsCount]);
} }

View file

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

View file

@ -1,8 +1,3 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
namespace Ryujinx.Core.OsHle.Services.Nv namespace Ryujinx.Core.OsHle.Services.Nv
{ {
class NvHostEvent class NvHostEvent
@ -10,6 +5,6 @@ namespace Ryujinx.Core.OsHle.Services.Nv
public int Id; public int Id;
public int Thresh; public int Thresh;
public bool Free; public NvHostEventState State;
} }
} }

View file

@ -0,0 +1,10 @@
namespace Ryujinx.Core.OsHle.Services.Nv
{
enum NvHostEventState
{
Registered = 0,
Waiting = 1,
Busy = 2,
Free = 5
}
}

View file

@ -5,42 +5,54 @@ using System.Threading;
namespace Ryujinx.Core.OsHle.Services.Nv namespace Ryujinx.Core.OsHle.Services.Nv
{ {
class NvSyncPt class NvHostSyncPt
{ {
private int m_CounterMin; public const int SyncPtsCount = 192;
private int m_CounterMax;
public int CounterMin => m_CounterMin; public int[] CounterMin;
public int CounterMax => m_CounterMax; public int[] CounterMax;
public bool IsEvent { get; set; } private long EventMask;
private ConcurrentDictionary<EventWaitHandle, int> Waiters; private ConcurrentDictionary<EventWaitHandle, int> Waiters;
public NvSyncPt() public NvHostSyncPt()
{ {
CounterMin = new int[SyncPtsCount];
CounterMax = new int[SyncPtsCount];
Waiters = new ConcurrentDictionary<EventWaitHandle, int>(); Waiters = new ConcurrentDictionary<EventWaitHandle, int>();
} }
public int Increment() public int GetMin(int Id)
{ {
Interlocked.Increment(ref m_CounterMax); return CounterMin[Id];
return IncrementMin();
} }
public int IncrementMin() public int GetMax(int Id)
{ {
int Value = Interlocked.Increment(ref m_CounterMin); return CounterMax[Id];
}
WakeUpWaiters(Value); public int Increment(int Id)
{
Interlocked.Increment(ref CounterMax[Id]);
return IncrementMin(Id);
}
public int IncrementMin(int Id)
{
int Value = Interlocked.Increment(ref CounterMin[Id]);
WakeUpWaiters(Id, Value);
return Value; return Value;
} }
public int IncrementMax() public int IncrementMax(int Id)
{ {
return Interlocked.Increment(ref m_CounterMax); return Interlocked.Increment(ref CounterMax[Id]);
} }
public void AddWaiter(int Threshold, EventWaitHandle WaitEvent) public void AddWaiter(int Threshold, EventWaitHandle WaitEvent)
@ -56,11 +68,11 @@ namespace Ryujinx.Core.OsHle.Services.Nv
return Waiters.TryRemove(WaitEvent, out _); return Waiters.TryRemove(WaitEvent, out _);
} }
private void WakeUpWaiters(int NewValue) private void WakeUpWaiters(int Id, int NewValue)
{ {
foreach (KeyValuePair<EventWaitHandle, int> KV in Waiters) foreach (KeyValuePair<EventWaitHandle, int> KV in Waiters)
{ {
if (MinCompare(NewValue, m_CounterMax, KV.Value)) if (MinCompare(Id, NewValue, CounterMax[Id], KV.Value))
{ {
KV.Key.Set(); KV.Key.Set();
@ -69,17 +81,17 @@ namespace Ryujinx.Core.OsHle.Services.Nv
} }
} }
public bool MinCompare(int Threshold) public bool MinCompare(int Id, int Threshold)
{ {
return MinCompare(m_CounterMin, m_CounterMax, Threshold); return MinCompare(Id, CounterMin[Id], CounterMax[Id], Threshold);
} }
private bool MinCompare(int Min, int Max, int Threshold) private bool MinCompare(int Id, int Min, int Max, int Threshold)
{ {
int MinDiff = Min - Threshold; int MinDiff = Min - Threshold;
int MaxDiff = Max - Threshold; int MaxDiff = Max - Threshold;
if (IsEvent) if (((EventMask >> Id) & 1) != 0)
{ {
return MinDiff >= 0; return MinDiff >= 0;
} }