2019-11-02 22:47:56 +00:00
using Ryujinx.Common.Logging ;
2020-04-19 01:25:57 +00:00
using Ryujinx.Graphics.Gpu.Synchronization ;
2019-11-02 22:47:56 +00:00
using Ryujinx.HLE.HOS.Kernel.Common ;
using Ryujinx.HLE.HOS.Kernel.Threading ;
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl.Types ;
using Ryujinx.HLE.HOS.Services.Nv.Types ;
using Ryujinx.HLE.HOS.Services.Settings ;
using System ;
using System.Text ;
using System.Threading ;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl
{
internal class NvHostCtrlDeviceFile : NvDeviceFile
{
2020-04-19 01:25:57 +00:00
public const int EventsCount = 64 ;
2019-11-02 22:47:56 +00:00
private bool _isProductionMode ;
2020-04-19 01:25:57 +00:00
private Switch _device ;
2019-11-02 22:47:56 +00:00
private NvHostEvent [ ] _events ;
public NvHostCtrlDeviceFile ( ServiceCtx context ) : base ( context )
{
if ( NxSettings . Settings . TryGetValue ( "nv!rmos_set_production_mode" , out object productionModeSetting ) )
{
_isProductionMode = ( ( string ) productionModeSetting ) ! = "0" ; // Default value is ""
}
else
{
_isProductionMode = true ;
}
2020-04-19 01:25:57 +00:00
_device = context . Device ;
_events = new NvHostEvent [ EventsCount ] ;
2019-11-02 22:47:56 +00:00
}
public override NvInternalResult Ioctl ( NvIoctl command , Span < byte > arguments )
{
NvInternalResult result = NvInternalResult . NotImplemented ;
if ( command . Type = = NvIoctl . NvHostCustomMagic )
{
switch ( command . Number )
{
case 0x14 :
result = CallIoctlMethod < NvFence > ( SyncptRead , arguments ) ;
break ;
case 0x15 :
result = CallIoctlMethod < uint > ( SyncptIncr , arguments ) ;
break ;
case 0x16 :
result = CallIoctlMethod < SyncptWaitArguments > ( SyncptWait , arguments ) ;
break ;
case 0x19 :
result = CallIoctlMethod < SyncptWaitExArguments > ( SyncptWaitEx , arguments ) ;
break ;
case 0x1a :
result = CallIoctlMethod < NvFence > ( SyncptReadMax , arguments ) ;
break ;
case 0x1b :
// As Marshal cannot handle unaligned arrays, we do everything by hand here.
GetConfigurationArguments configArgument = GetConfigurationArguments . FromSpan ( arguments ) ;
result = GetConfig ( configArgument ) ;
if ( result = = NvInternalResult . Success )
{
configArgument . CopyTo ( arguments ) ;
}
break ;
2020-04-19 01:25:57 +00:00
case 0x1c :
result = CallIoctlMethod < uint > ( EventSignal , arguments ) ;
break ;
2019-11-02 22:47:56 +00:00
case 0x1d :
result = CallIoctlMethod < EventWaitArguments > ( EventWait , arguments ) ;
break ;
case 0x1e :
result = CallIoctlMethod < EventWaitArguments > ( EventWaitAsync , arguments ) ;
break ;
case 0x1f :
result = CallIoctlMethod < uint > ( EventRegister , arguments ) ;
break ;
2020-04-19 01:25:57 +00:00
case 0x20 :
result = CallIoctlMethod < uint > ( EventUnregister , arguments ) ;
break ;
case 0x21 :
result = CallIoctlMethod < ulong > ( EventKill , arguments ) ;
break ;
2019-11-02 22:47:56 +00:00
}
}
return result ;
}
2020-04-19 01:25:57 +00:00
private KEvent QueryEvent ( uint eventId )
{
uint eventSlot ;
uint syncpointId ;
if ( ( eventId > > 28 ) = = 1 )
{
eventSlot = eventId & 0xFFFF ;
syncpointId = ( eventId > > 16 ) & 0xFFF ;
}
else
{
eventSlot = eventId & 0xFF ;
syncpointId = eventId > > 4 ;
}
if ( eventSlot > = EventsCount | | _events [ eventSlot ] = = null | | _events [ eventSlot ] . Fence . Id ! = syncpointId )
{
return null ;
}
return _events [ eventSlot ] . Event ;
}
2019-11-02 22:47:56 +00:00
public override NvInternalResult QueryEvent ( out int eventHandle , uint eventId )
{
2020-04-19 01:25:57 +00:00
KEvent targetEvent = QueryEvent ( eventId ) ;
2019-11-02 22:47:56 +00:00
if ( targetEvent ! = null )
{
if ( Owner . HandleTable . GenerateHandle ( targetEvent . ReadableEvent , out eventHandle ) ! = KernelResult . Success )
{
throw new InvalidOperationException ( "Out of handles!" ) ;
}
}
else
{
eventHandle = 0 ;
return NvInternalResult . InvalidInput ;
}
return NvInternalResult . Success ;
}
private NvInternalResult SyncptRead ( ref NvFence arguments )
{
return SyncptReadMinOrMax ( ref arguments , max : false ) ;
}
private NvInternalResult SyncptIncr ( ref uint id )
{
2020-04-19 01:25:57 +00:00
if ( id > = SynchronizationManager . MaxHardwareSyncpoints )
2019-11-02 22:47:56 +00:00
{
return NvInternalResult . InvalidInput ;
}
2020-04-19 01:25:57 +00:00
_device . System . HostSyncpoint . Increment ( id ) ;
2019-11-02 22:47:56 +00:00
return NvInternalResult . Success ;
}
private NvInternalResult SyncptWait ( ref SyncptWaitArguments arguments )
{
2020-04-19 01:25:57 +00:00
uint dummyValue = 0 ;
return EventWait ( ref arguments . Fence , ref dummyValue , arguments . Timeout , isWaitEventAsyncCmd : false , isWaitEventCmd : false ) ;
2019-11-02 22:47:56 +00:00
}
private NvInternalResult SyncptWaitEx ( ref SyncptWaitExArguments arguments )
{
2020-04-19 01:25:57 +00:00
return EventWait ( ref arguments . Input . Fence , ref arguments . Value , arguments . Input . Timeout , isWaitEventAsyncCmd : false , isWaitEventCmd : false ) ;
2019-11-02 22:47:56 +00:00
}
private NvInternalResult SyncptReadMax ( ref NvFence arguments )
{
return SyncptReadMinOrMax ( ref arguments , max : true ) ;
}
private NvInternalResult GetConfig ( GetConfigurationArguments arguments )
{
if ( ! _isProductionMode & & NxSettings . Settings . TryGetValue ( $"{arguments.Domain}!{arguments.Parameter}" . ToLower ( ) , out object nvSetting ) )
{
byte [ ] settingBuffer = new byte [ 0x101 ] ;
if ( nvSetting is string stringValue )
{
if ( stringValue . Length > 0x100 )
{
Logger . PrintError ( LogClass . ServiceNv , $"{arguments.Domain}!{arguments.Parameter} String value size is too big!" ) ;
}
else
{
settingBuffer = Encoding . ASCII . GetBytes ( stringValue + "\0" ) ;
}
}
else if ( nvSetting is int intValue )
{
settingBuffer = BitConverter . GetBytes ( intValue ) ;
}
else if ( nvSetting is bool boolValue )
{
settingBuffer [ 0 ] = boolValue ? ( byte ) 1 : ( byte ) 0 ;
}
else
{
throw new NotImplementedException ( nvSetting . GetType ( ) . Name ) ;
}
Logger . PrintDebug ( LogClass . ServiceNv , $"Got setting {arguments.Domain}!{arguments.Parameter}" ) ;
arguments . Configuration = settingBuffer ;
return NvInternalResult . Success ;
}
// NOTE: This actually return NotAvailableInProduction but this is directly translated as a InvalidInput before returning the ioctl.
//return NvInternalResult.NotAvailableInProduction;
return NvInternalResult . InvalidInput ;
}
private NvInternalResult EventWait ( ref EventWaitArguments arguments )
{
2020-04-19 01:25:57 +00:00
return EventWait ( ref arguments . Fence , ref arguments . Value , arguments . Timeout , isWaitEventAsyncCmd : false , isWaitEventCmd : true ) ;
2019-11-02 22:47:56 +00:00
}
private NvInternalResult EventWaitAsync ( ref EventWaitArguments arguments )
{
2020-04-19 01:25:57 +00:00
return EventWait ( ref arguments . Fence , ref arguments . Value , arguments . Timeout , isWaitEventAsyncCmd : true , isWaitEventCmd : false ) ;
2019-11-02 22:47:56 +00:00
}
private NvInternalResult EventRegister ( ref uint userEventId )
{
2020-04-19 01:25:57 +00:00
NvInternalResult result = EventUnregister ( ref userEventId ) ;
2019-11-02 22:47:56 +00:00
2020-04-19 01:25:57 +00:00
if ( result = = NvInternalResult . Success )
{
_events [ userEventId ] = new NvHostEvent ( _device . System . HostSyncpoint , userEventId , _device . System ) ;
}
return result ;
2019-11-02 22:47:56 +00:00
}
2020-04-19 01:25:57 +00:00
private NvInternalResult EventUnregister ( ref uint userEventId )
2019-11-02 22:47:56 +00:00
{
2020-04-19 01:25:57 +00:00
if ( userEventId > = EventsCount )
2019-11-02 22:47:56 +00:00
{
return NvInternalResult . InvalidInput ;
}
2020-04-19 01:25:57 +00:00
NvHostEvent hostEvent = _events [ userEventId ] ;
if ( hostEvent = = null )
2019-11-02 22:47:56 +00:00
{
2020-04-19 01:25:57 +00:00
return NvInternalResult . Success ;
2019-11-02 22:47:56 +00:00
}
2020-04-19 01:25:57 +00:00
if ( hostEvent . State = = NvHostEventState . Available | |
hostEvent . State = = NvHostEventState . Cancelled | |
hostEvent . State = = NvHostEventState . Signaled )
2019-11-02 22:47:56 +00:00
{
2020-04-19 01:25:57 +00:00
_events [ userEventId ] . Dispose ( ) ;
_events [ userEventId ] = null ;
return NvInternalResult . Success ;
2019-11-02 22:47:56 +00:00
}
2020-04-19 01:25:57 +00:00
return NvInternalResult . Busy ;
2019-11-02 22:47:56 +00:00
}
2020-04-19 01:25:57 +00:00
private NvInternalResult EventKill ( ref ulong eventMask )
2019-11-02 22:47:56 +00:00
{
2020-04-19 01:25:57 +00:00
NvInternalResult result = NvInternalResult . Success ;
for ( uint eventId = 0 ; eventId < EventsCount ; eventId + + )
2019-11-02 22:47:56 +00:00
{
2020-04-19 01:25:57 +00:00
if ( ( eventMask & ( 1 UL < < ( int ) eventId ) ) ! = 0 )
{
NvInternalResult tmp = EventUnregister ( ref eventId ) ;
2019-11-02 22:47:56 +00:00
2020-04-19 01:25:57 +00:00
if ( tmp ! = NvInternalResult . Success )
{
result = tmp ;
}
}
2019-11-02 22:47:56 +00:00
}
2020-04-19 01:25:57 +00:00
return result ;
}
private NvInternalResult EventSignal ( ref uint userEventId )
{
uint eventId = userEventId & ushort . MaxValue ;
2019-11-02 22:47:56 +00:00
2020-04-19 01:25:57 +00:00
if ( eventId > = EventsCount )
2019-11-02 22:47:56 +00:00
{
2020-04-19 01:25:57 +00:00
return NvInternalResult . InvalidInput ;
2019-11-02 22:47:56 +00:00
}
2020-04-19 01:25:57 +00:00
NvHostEvent hostEvent = _events [ eventId ] ;
if ( hostEvent = = null )
2019-11-02 22:47:56 +00:00
{
2020-04-19 01:25:57 +00:00
return NvInternalResult . InvalidInput ;
2019-11-02 22:47:56 +00:00
}
2020-04-19 01:25:57 +00:00
NvHostEventState oldState = hostEvent . State ;
2019-11-02 22:47:56 +00:00
2020-04-19 01:25:57 +00:00
if ( oldState = = NvHostEventState . Waiting )
{
hostEvent . State = NvHostEventState . Cancelling ;
2019-11-02 22:47:56 +00:00
2020-04-19 01:25:57 +00:00
hostEvent . Cancel ( _device . Gpu ) ;
}
2019-11-02 22:47:56 +00:00
2020-04-19 01:25:57 +00:00
hostEvent . State = NvHostEventState . Cancelled ;
2019-11-02 22:47:56 +00:00
2020-04-19 01:25:57 +00:00
return NvInternalResult . Success ;
}
2019-11-02 22:47:56 +00:00
2020-04-19 01:25:57 +00:00
private NvInternalResult SyncptReadMinOrMax ( ref NvFence arguments , bool max )
{
if ( arguments . Id > = SynchronizationManager . MaxHardwareSyncpoints )
{
return NvInternalResult . InvalidInput ;
2019-11-02 22:47:56 +00:00
}
2020-04-19 01:25:57 +00:00
if ( max )
{
arguments . Value = _device . System . HostSyncpoint . ReadSyncpointMaxValue ( arguments . Id ) ;
}
else
{
arguments . Value = _device . System . HostSyncpoint . ReadSyncpointValue ( arguments . Id ) ;
}
2019-11-02 22:47:56 +00:00
2020-04-19 01:25:57 +00:00
return NvInternalResult . Success ;
2019-11-02 22:47:56 +00:00
}
2020-04-19 01:25:57 +00:00
private NvInternalResult EventWait ( ref NvFence fence , ref uint value , int timeout , bool isWaitEventAsyncCmd , bool isWaitEventCmd )
2019-11-02 22:47:56 +00:00
{
2020-04-19 01:25:57 +00:00
if ( fence . Id > = SynchronizationManager . MaxHardwareSyncpoints )
2019-11-02 22:47:56 +00:00
{
return NvInternalResult . InvalidInput ;
}
2020-04-19 01:25:57 +00:00
// First try to check if the syncpoint is already expired on the CPU side
if ( _device . System . HostSyncpoint . IsSyncpointExpired ( fence . Id , fence . Value ) )
2019-11-02 22:47:56 +00:00
{
2020-04-19 01:25:57 +00:00
value = _device . System . HostSyncpoint . ReadSyncpointMinValue ( fence . Id ) ;
2019-11-02 22:47:56 +00:00
return NvInternalResult . Success ;
}
2020-04-19 01:25:57 +00:00
// Try to invalidate the CPU cache and check for expiration again.
uint newCachedSyncpointValue = _device . System . HostSyncpoint . UpdateMin ( fence . Id ) ;
// Has the fence already expired?
if ( _device . System . HostSyncpoint . IsSyncpointExpired ( fence . Id , fence . Value ) )
2019-11-02 22:47:56 +00:00
{
2020-04-19 01:25:57 +00:00
value = newCachedSyncpointValue ;
return NvInternalResult . Success ;
2019-11-02 22:47:56 +00:00
}
2020-04-19 01:25:57 +00:00
// If the timeout is 0, directly return.
if ( timeout = = 0 )
2019-11-02 22:47:56 +00:00
{
return NvInternalResult . TryAgain ;
}
2020-04-19 01:25:57 +00:00
// The syncpoint value isn't at the fence yet, we need to wait.
if ( ! isWaitEventAsyncCmd )
{
value = 0 ;
}
NvHostEvent hostEvent ;
2019-11-02 22:47:56 +00:00
NvInternalResult result ;
2020-04-19 01:25:57 +00:00
uint eventIndex ;
2019-11-02 22:47:56 +00:00
2020-04-19 01:25:57 +00:00
if ( isWaitEventAsyncCmd )
2019-11-02 22:47:56 +00:00
{
2020-04-19 01:25:57 +00:00
eventIndex = value ;
2019-11-02 22:47:56 +00:00
2020-04-19 01:25:57 +00:00
if ( eventIndex > = EventsCount )
2019-11-02 22:47:56 +00:00
{
return NvInternalResult . InvalidInput ;
}
2020-04-19 01:25:57 +00:00
hostEvent = _events [ eventIndex ] ;
2019-11-02 22:47:56 +00:00
}
else
{
2020-04-19 01:25:57 +00:00
hostEvent = GetFreeEvent ( fence . Id , out eventIndex ) ;
2019-11-02 22:47:56 +00:00
}
2020-04-19 01:25:57 +00:00
if ( hostEvent ! = null & &
( hostEvent . State = = NvHostEventState . Available | |
hostEvent . State = = NvHostEventState . Signaled | |
hostEvent . State = = NvHostEventState . Cancelled ) )
2019-11-02 22:47:56 +00:00
{
2020-04-19 01:25:57 +00:00
hostEvent . Wait ( _device . Gpu , fence ) ;
2019-11-02 22:47:56 +00:00
2020-04-19 01:25:57 +00:00
if ( isWaitEventCmd )
2019-11-02 22:47:56 +00:00
{
2020-04-19 01:25:57 +00:00
value = ( ( fence . Id & 0xfff ) < < 16 ) | 0x10000000 ;
2019-11-02 22:47:56 +00:00
}
else
{
2020-04-19 01:25:57 +00:00
value = fence . Id < < 4 ;
2019-11-02 22:47:56 +00:00
}
2020-04-19 01:25:57 +00:00
value | = eventIndex ;
2019-11-02 22:47:56 +00:00
result = NvInternalResult . TryAgain ;
}
else
{
2020-04-19 01:25:57 +00:00
Logger . PrintError ( LogClass . ServiceNv , $"Invalid Event at index {eventIndex} (isWaitEventAsyncCmd: {isWaitEventAsyncCmd}, isWaitEventCmd: {isWaitEventCmd})" ) ;
if ( hostEvent ! = null )
{
Logger . PrintError ( LogClass . ServiceNv , hostEvent . DumpState ( _device . Gpu ) ) ;
}
2019-11-02 22:47:56 +00:00
result = NvInternalResult . InvalidInput ;
}
return result ;
}
2020-04-19 01:25:57 +00:00
public NvHostEvent GetFreeEvent ( uint id , out uint eventIndex )
2019-11-02 22:47:56 +00:00
{
eventIndex = EventsCount ;
2020-04-19 01:25:57 +00:00
uint nullIndex = EventsCount ;
2019-11-02 22:47:56 +00:00
2020-04-19 01:25:57 +00:00
for ( uint index = 0 ; index < EventsCount ; index + + )
2019-11-02 22:47:56 +00:00
{
NvHostEvent Event = _events [ index ] ;
if ( Event ! = null )
{
2020-04-19 01:25:57 +00:00
if ( Event . State = = NvHostEventState . Available | |
Event . State = = NvHostEventState . Signaled | |
Event . State = = NvHostEventState . Cancelled )
2019-11-02 22:47:56 +00:00
{
eventIndex = index ;
2020-04-19 01:25:57 +00:00
if ( Event . Fence . Id = = id )
2019-11-02 22:47:56 +00:00
{
return Event ;
}
}
}
else if ( nullIndex = = EventsCount )
{
nullIndex = index ;
}
}
if ( nullIndex < EventsCount )
{
eventIndex = nullIndex ;
2020-04-19 01:25:57 +00:00
EventRegister ( ref eventIndex ) ;
return _events [ nullIndex ] ;
2019-11-02 22:47:56 +00:00
}
if ( eventIndex < EventsCount )
{
return _events [ eventIndex ] ;
}
return null ;
}
2020-04-19 01:25:57 +00:00
public override void Close ( )
{
Logger . PrintWarning ( LogClass . ServiceNv , "Closing channel" ) ;
// If the device file need to be closed, cancel all user events and dispose events.
for ( int i = 0 ; i < _events . Length ; i + + )
{
NvHostEvent evnt = _events [ i ] ;
if ( evnt ! = null )
{
if ( evnt . State = = NvHostEventState . Waiting )
{
evnt . State = NvHostEventState . Cancelling ;
evnt . Cancel ( _device . Gpu ) ;
}
else if ( evnt . State = = NvHostEventState . Signaling )
{
// Wait at max 9ms if the guest app is trying to signal the event while closing it..
int retryCount = 0 ;
do
{
if ( retryCount + + > 9 )
{
break ;
}
// TODO: This should be handled by the kernel (reschedule the current thread ect), waiting for Kernel decoupling work.
Thread . Sleep ( 1 ) ;
} while ( evnt . State ! = NvHostEventState . Signaled ) ;
}
evnt . Dispose ( ) ;
_events [ i ] = null ;
}
}
}
2019-11-02 22:47:56 +00:00
}
}