Procházet zdrojové kódy

Implement time:* 2.0.0 & 3.0.0 commands (#735)

* Finish ISteadyClock implementation

* Implement IsStandardNetworkSystemClockAccuracySufficient

Also use signed values for offsets and TimeSpanType

* Address comments

* Fix one missing nit and improve one comment
Thomas Guillemard před 6 roky
rodič
revize
1f3a34dd7a

+ 11 - 4
Ryujinx.HLE/HOS/Horizon.cs

@@ -197,10 +197,17 @@ namespace Ryujinx.HLE.HOS
 
             ContentManager = new ContentManager(device);
 
-            // NOTE: Now we set the default internal offset of the steady clock like Nintendo does... even if it's strange this is accurate.
-            // TODO: use bpc:r and set:sys (and set external clock source id from settings)
-            DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
-            SteadyClockCore.Instance.SetInternalOffset(new TimeSpanType(((ulong)(DateTime.Now.ToUniversalTime() - UnixEpoch).TotalSeconds) * 1000000000));
+            // TODO: use set:sys (and set external clock source id from settings)
+            // TODO: use "time!standard_steady_clock_rtc_update_interval_minutes" and implement a worker thread to be accurate.
+            SteadyClockCore.Instance.ConfigureSetupValue();
+
+            if (Services.Set.NxSettings.Settings.TryGetValue("time!standard_network_clock_sufficient_accuracy_minutes", out object standardNetworkClockSufficientAccuracyMinutes))
+            {
+                TimeSpanType standardNetworkClockSufficientAccuracy = new TimeSpanType((int)standardNetworkClockSufficientAccuracyMinutes * 60000000000);
+
+                StandardNetworkSystemClockCore.Instance.SetStandardNetworkClockSufficientAccuracy(standardNetworkClockSufficientAccuracy);
+            }
+
         }
 
         public void LoadCart(string exeFsDir, string romFsFile = null)

+ 34 - 0
Ryujinx.HLE/HOS/Services/Bpc/IRtcManager.cs

@@ -0,0 +1,34 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Bpc
+{
+    [Service("bpc:r")]
+    class IRtcManager : IpcService
+    {
+        public IRtcManager(ServiceCtx context) { }
+
+        [Command(0)]
+        // GetRtcTime() -> u64
+        public ResultCode GetRtcTime(ServiceCtx context)
+        {
+            ResultCode result = GetExternalRtcValue(out ulong rtcValue);
+
+            if (result == ResultCode.Success)
+            {
+                context.ResponseData.Write(rtcValue);
+            }
+
+            return result;
+        }
+
+        public static ResultCode GetExternalRtcValue(out ulong rtcValue)
+        {
+            // TODO: emulate MAX77620/MAX77812 RTC
+            DateTime unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+
+            rtcValue = (ulong)(DateTime.Now.ToUniversalTime() - unixEpoch).TotalSeconds;
+
+            return ResultCode.Success;
+        }
+    }
+}

+ 28 - 6
Ryujinx.HLE/HOS/Services/Time/Clock/ClockTypes.cs

@@ -1,4 +1,5 @@
 using Ryujinx.HLE.Utilities;
+using System;
 using System.Runtime.InteropServices;
 
 namespace Ryujinx.HLE.HOS.Services.Time.Clock
@@ -6,35 +7,56 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
     [StructLayout(LayoutKind.Sequential)]
     struct TimeSpanType
     {
-        public ulong NanoSeconds;
+        public long NanoSeconds;
 
-        public TimeSpanType(ulong nanoSeconds)
+        public TimeSpanType(long nanoSeconds)
         {
             NanoSeconds = nanoSeconds;
         }
 
-        public ulong ToSeconds()
+        public long ToSeconds()
         {
             return NanoSeconds / 1000000000;
         }
 
         public static TimeSpanType FromTicks(ulong ticks, ulong frequency)
         {
-            return new TimeSpanType(ticks * 1000000000 / frequency);
+            return new TimeSpanType((long)ticks * 1000000000 / (long)frequency);
         }
     }
 
     [StructLayout(LayoutKind.Sequential)]
     struct SteadyClockTimePoint
     {
-        public ulong   TimePoint;
+        public long    TimePoint;
         public UInt128 ClockSourceId;
+
+        public ResultCode GetSpanBetween(SteadyClockTimePoint other, out long outSpan)
+        {
+            outSpan = 0;
+
+            if (ClockSourceId == other.ClockSourceId)
+            {
+                try
+                {
+                    outSpan = checked(other.TimePoint - TimePoint);
+
+                    return ResultCode.Success;
+                }
+                catch (OverflowException)
+                {
+                    return ResultCode.Overflow;
+                }
+            }
+
+            return ResultCode.Overflow;
+        }
     }
 
     [StructLayout(LayoutKind.Sequential)]
     struct SystemClockContext
     {
-        public ulong                Offset;
+        public long                 Offset;
         public SteadyClockTimePoint SteadyTimePoint;
     }
 }

+ 25 - 2
Ryujinx.HLE/HOS/Services/Time/Clock/StandardNetworkSystemClockCore.cs

@@ -1,4 +1,5 @@
-using Ryujinx.HLE.HOS.Kernel.Threading;
+using System;
+using Ryujinx.HLE.HOS.Kernel.Threading;
 
 namespace Ryujinx.HLE.HOS.Services.Time.Clock
 {
@@ -6,6 +7,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
     {
         private SteadyClockCore    _steadyClockCore;
         private SystemClockContext _context;
+        private TimeSpanType       _standardNetworkClockSufficientAccuracy;
 
         private static StandardNetworkSystemClockCore instance;
 
@@ -27,7 +29,8 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
             _steadyClockCore = steadyClockCore;
             _context         = new SystemClockContext();
 
-            _context.SteadyTimePoint.ClockSourceId = steadyClockCore.GetClockSourceId();
+            _context.SteadyTimePoint.ClockSourceId  = steadyClockCore.GetClockSourceId();
+            _standardNetworkClockSufficientAccuracy = new TimeSpanType(0);
         }
 
         public override ResultCode Flush(SystemClockContext context)
@@ -55,5 +58,25 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
 
             return ResultCode.Success;
         }
+
+        public bool IsStandardNetworkSystemClockAccuracySufficient(KThread thread)
+        {
+            SteadyClockCore      steadyClockCore  = GetSteadyClockCore();
+            SteadyClockTimePoint currentTimePoint = steadyClockCore.GetCurrentTimePoint(thread);
+
+            bool isStandardNetworkClockSufficientAccuracy = false;
+
+            if (_context.SteadyTimePoint.GetSpanBetween(currentTimePoint, out long outSpan) == ResultCode.Success)
+            {
+                isStandardNetworkClockSufficientAccuracy = outSpan * 1000000000 < _standardNetworkClockSufficientAccuracy.NanoSeconds;
+            }
+
+            return isStandardNetworkClockSufficientAccuracy;
+        }
+
+        public void SetStandardNetworkClockSufficientAccuracy(TimeSpanType standardNetworkClockSufficientAccuracy)
+        {
+            _standardNetworkClockSufficientAccuracy = standardNetworkClockSufficientAccuracy;
+        }
     }
 }

+ 48 - 3
Ryujinx.HLE/HOS/Services/Time/Clock/SteadyClockCore.cs

@@ -1,4 +1,5 @@
 using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.HLE.HOS.Services.Bpc;
 using Ryujinx.HLE.Utilities;
 using System;
 
@@ -6,6 +7,9 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
 {
     class SteadyClockCore
     {
+        private long         _setupValue;
+        private ResultCode   _setupResultCode;
+        private bool         _isRtcResetDetected;
         private TimeSpanType _testOffset;
         private TimeSpanType _internalOffset;
         private UInt128      _clockSourceId;
@@ -42,7 +46,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
 
             TimeSpanType ticksTimeSpan = TimeSpanType.FromTicks(thread.Context.ThreadState.CntpctEl0, thread.Context.ThreadState.CntfrqEl0);
 
-            result.TimePoint = _internalOffset.ToSeconds() + ticksTimeSpan.ToSeconds();
+            result.TimePoint = _setupValue + ticksTimeSpan.ToSeconds();
 
             return result;
         }
@@ -57,6 +61,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
             SteadyClockTimePoint result = GetTimePoint(thread);
 
             result.TimePoint += _testOffset.ToSeconds();
+            result.TimePoint += _internalOffset.ToSeconds();
 
             return result;
         }
@@ -71,16 +76,56 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
             _testOffset = testOffset;
         }
 
-        // TODO: check if this is accurate
+        public ResultCode GetRtcValue(out ulong rtcValue)
+        {
+            return (ResultCode)IRtcManager.GetExternalRtcValue(out rtcValue);
+        }
+
+        public bool IsRtcResetDetected()
+        {
+            return _isRtcResetDetected;
+        }
+
+        public ResultCode GetSetupResultCode()
+        {
+            return _setupResultCode;
+        }
+
         public TimeSpanType GetInternalOffset()
         {
             return _internalOffset;
         }
 
-        // TODO: check if this is accurate
         public void SetInternalOffset(TimeSpanType internalOffset)
         {
             _internalOffset = internalOffset;
         }
+
+        public ResultCode GetSetupResultValue()
+        {
+            return _setupResultCode;
+        }
+
+        public void ConfigureSetupValue()
+        {
+            int retry = 0;
+
+            ResultCode result = ResultCode.Success;
+
+            while (retry < 20)
+            {
+                result = (ResultCode)IRtcManager.GetExternalRtcValue(out ulong rtcValue);
+
+                if (result == ResultCode.Success)
+                {
+                    _setupValue = (long)rtcValue;
+                    break;
+                }
+
+                retry++;
+            }
+
+            _setupResultCode = result;
+        }
     }
 }

+ 9 - 0
Ryujinx.HLE/HOS/Services/Time/IStaticService.cs

@@ -106,6 +106,15 @@ namespace Ryujinx.HLE.HOS.Services.Time
             return StandardUserSystemClockCore.Instance.SetAutomaticCorrectionEnabled(context.Thread, autoCorrectionEnabled);
         }
 
+        [Command(200)] // 3.0.0+
+        // IsStandardNetworkSystemClockAccuracySufficient() -> bool
+        public ResultCode IsStandardNetworkSystemClockAccuracySufficient(ServiceCtx context)
+        {
+            context.ResponseData.Write(StandardNetworkSystemClockCore.Instance.IsStandardNetworkSystemClockAccuracySufficient(context.Thread));
+
+            return ResultCode.Success;
+        }
+
         [Command(300)] // 4.0.0+
         // CalculateMonotonicSystemClockBaseTimePoint(nn::time::SystemClockContext) -> u64
         public ResultCode CalculateMonotonicSystemClockBaseTimePoint(ServiceCtx context)

+ 32 - 0
Ryujinx.HLE/HOS/Services/Time/ISteadyClock.cs

@@ -36,6 +36,38 @@ namespace Ryujinx.HLE.HOS.Services.Time
             return 0;
         }
 
+        [Command(100)] // 2.0.0+
+        // GetRtcValue() -> u64
+        public ResultCode GetRtcValue(ServiceCtx context)
+        {
+            ResultCode result = SteadyClockCore.Instance.GetRtcValue(out ulong rtcValue);
+
+            if (result == ResultCode.Success)
+            {
+                context.ResponseData.Write(rtcValue);
+            }
+
+            return result;
+        }
+
+        [Command(101)] // 2.0.0+
+        // IsRtcResetDetected() -> bool
+        public ResultCode IsRtcResetDetected(ServiceCtx context)
+        {
+            context.ResponseData.Write(SteadyClockCore.Instance.IsRtcResetDetected());
+
+            return ResultCode.Success;
+        }
+
+        [Command(102)] // 2.0.0+
+        // GetSetupResultValue() -> u32
+        public ResultCode GetSetupResultValue(ServiceCtx context)
+        {
+            context.ResponseData.Write((uint)SteadyClockCore.Instance.GetSetupResultCode());
+
+            return ResultCode.Success;
+        }
+
         [Command(200)] // 3.0.0+
         // GetInternalOffset() -> nn::TimeSpanType
         public ResultCode GetInternalOffset(ServiceCtx context)

+ 2 - 2
Ryujinx.HLE/HOS/Services/Time/ISystemClock.cs

@@ -29,7 +29,7 @@ namespace Ryujinx.HLE.HOS.Services.Time
 
                 if (currentTimePoint.ClockSourceId == clockContext.SteadyTimePoint.ClockSourceId)
                 {
-                    ulong posixTime = clockContext.Offset + currentTimePoint.TimePoint;
+                    long posixTime = clockContext.Offset + currentTimePoint.TimePoint;
 
                     context.ResponseData.Write(posixTime);
 
@@ -49,7 +49,7 @@ namespace Ryujinx.HLE.HOS.Services.Time
                 return ResultCode.PermissionDenied;
             }
 
-            ulong                posixTime        = context.RequestData.ReadUInt64();
+            long                 posixTime        = context.RequestData.ReadInt64();
             SteadyClockCore      steadyClockCore  = _clockCore.GetSteadyClockCore();
             SteadyClockTimePoint currentTimePoint = steadyClockCore.GetCurrentTimePoint(context.Thread);