Rewrite system clocks to be accurate

This commit is contained in:
Thog 2019-07-14 16:52:54 +02:00
commit 689c5854ae
No known key found for this signature in database
GPG key ID: 0CD291558FAFDBC6
12 changed files with 327 additions and 70 deletions

View file

@ -200,7 +200,7 @@ namespace Ryujinx.HLE.HOS
// NOTE: Now we set the default internal offset of the steady clock like Nintendo does... even if it's strange this is accurate. // 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) // 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); DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
SteadyClockCore.Instance.SetInternalOffset(new TimeSpanType(((ulong)(DateTime.Now.ToUniversalTime() - UnixEpoch).TotalSeconds) * 1000)); SteadyClockCore.Instance.SetInternalOffset(new TimeSpanType(((ulong)(DateTime.Now.ToUniversalTime() - UnixEpoch).TotalSeconds) * 1000000000));
} }
public void LoadCart(string exeFsDir, string romFsFile = null) public void LoadCart(string exeFsDir, string romFsFile = null)

View file

@ -16,13 +16,14 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
public ulong ToSeconds() public ulong ToSeconds()
{ {
return NanoSeconds * 1000000000; return NanoSeconds / 1000000000;
} }
public static TimeSpanType FromTicks(ulong ticks, ulong frequency) public static TimeSpanType FromTicks(ulong ticks, ulong frequency)
{ {
return new TimeSpanType((ticks * 1000) / frequency); return new TimeSpanType(ticks * 1000000000 / frequency);
} }
} }
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
@ -31,4 +32,13 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
public ulong TimePoint; public ulong TimePoint;
public UInt128 ClockSourceId; public UInt128 ClockSourceId;
} }
[StructLayout(LayoutKind.Sequential)]
public struct SystemClockContext
{
public ulong Offset;
public SteadyClockTimePoint SteadyTimePoint;
}
} }

View file

@ -0,0 +1,59 @@
using Ryujinx.HLE.HOS.Kernel.Threading;
namespace Ryujinx.HLE.HOS.Services.Time.Clock
{
class StandardLocalSystemClockCore : SystemClockCore
{
private SteadyClockCore _steadyClockCore;
private SystemClockContext _context;
private static StandardLocalSystemClockCore instance;
public static StandardLocalSystemClockCore Instance
{
get
{
if (instance == null)
{
instance = new StandardLocalSystemClockCore(SteadyClockCore.Instance);
}
return instance;
}
}
public StandardLocalSystemClockCore(SteadyClockCore steadyClockCore)
{
_steadyClockCore = steadyClockCore;
_context = new SystemClockContext();
_context.SteadyTimePoint.ClockSourceId = steadyClockCore.GetClockSourceId();
}
public override ResultCode Flush(SystemClockContext context)
{
// TODO: set:sys SetUserSystemClockContext
return ResultCode.Success;
}
public override SteadyClockCore GetSteadyClockCore()
{
return _steadyClockCore;
}
public override ResultCode GetSystemClockContext(KThread thread, out SystemClockContext context)
{
context = _context;
return ResultCode.Success;
}
public override ResultCode SetSystemClockContext(SystemClockContext context)
{
_context = context;
return ResultCode.Success;
}
}
}

View file

@ -0,0 +1,59 @@
using Ryujinx.HLE.HOS.Kernel.Threading;
namespace Ryujinx.HLE.HOS.Services.Time.Clock
{
class StandardNetworkSystemClockCore : SystemClockCore
{
private SteadyClockCore _steadyClockCore;
private SystemClockContext _context;
private static StandardNetworkSystemClockCore instance;
public static StandardNetworkSystemClockCore Instance
{
get
{
if (instance == null)
{
instance = new StandardNetworkSystemClockCore(SteadyClockCore.Instance);
}
return instance;
}
}
public StandardNetworkSystemClockCore(SteadyClockCore steadyClockCore)
{
_steadyClockCore = steadyClockCore;
_context = new SystemClockContext();
_context.SteadyTimePoint.ClockSourceId = steadyClockCore.GetClockSourceId();
}
public override ResultCode Flush(SystemClockContext context)
{
// TODO: set:sys SetNetworkSystemClockContext
return ResultCode.Success;
}
public override SteadyClockCore GetSteadyClockCore()
{
return _steadyClockCore;
}
public override ResultCode GetSystemClockContext(KThread thread, out SystemClockContext context)
{
context = _context;
return ResultCode.Success;
}
public override ResultCode SetSystemClockContext(SystemClockContext context)
{
_context = context;
return ResultCode.Success;
}
}
}

View file

@ -0,0 +1,96 @@
using Ryujinx.HLE.HOS.Kernel.Threading;
namespace Ryujinx.HLE.HOS.Services.Time.Clock
{
class StandardUserSystemClockCore : SystemClockCore
{
private StandardLocalSystemClockCore _localSystemClockCore;
private StandardNetworkSystemClockCore _networkSystemClockCore;
private bool _autoCorrectionEnabled;
private static StandardUserSystemClockCore instance;
public static StandardUserSystemClockCore Instance
{
get
{
if (instance == null)
{
instance = new StandardUserSystemClockCore(StandardLocalSystemClockCore.Instance, StandardNetworkSystemClockCore.Instance);
}
return instance;
}
}
public StandardUserSystemClockCore(StandardLocalSystemClockCore localSystemClockCore, StandardNetworkSystemClockCore networkSystemClockCore)
{
_localSystemClockCore = localSystemClockCore;
_networkSystemClockCore = networkSystemClockCore;
_autoCorrectionEnabled = false;
}
public override ResultCode Flush(SystemClockContext context)
{
return ResultCode.NotImplemented;
}
public override SteadyClockCore GetSteadyClockCore()
{
return _localSystemClockCore.GetSteadyClockCore();
}
public override ResultCode GetSystemClockContext(KThread thread, out SystemClockContext context)
{
ResultCode result = ApplyAutomaticCorrection(thread, false);
context = new SystemClockContext();
if (result == 0)
{
return _localSystemClockCore.GetSystemClockContext(thread, out context);
}
return result;
}
public override ResultCode SetSystemClockContext(SystemClockContext context)
{
return ResultCode.NotImplemented;
}
private ResultCode ApplyAutomaticCorrection(KThread thread, bool autoCorrectionEnabled)
{
ResultCode result = ResultCode.Success;
if (_autoCorrectionEnabled != autoCorrectionEnabled && _networkSystemClockCore.IsClockSetup(thread))
{
result = _networkSystemClockCore.GetSystemClockContext(thread, out SystemClockContext context);
if (result == 0)
{
_localSystemClockCore.SetSystemClockContext(context);
}
}
return result;
}
public ResultCode SetAutomaticCorrectionEnabled(KThread thread, bool autoCorrectionEnabled)
{
ResultCode result = ApplyAutomaticCorrection(thread, autoCorrectionEnabled);
if (result == 0)
{
_autoCorrectionEnabled = autoCorrectionEnabled;
}
return result;
}
public bool IsAutomaticCorrectionEnabled()
{
return _autoCorrectionEnabled;
}
}
}

View file

@ -40,10 +40,17 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
ClockSourceId = _clockSourceId ClockSourceId = _clockSourceId
}; };
result.TimePoint = _internalOffset.ToSeconds() + TimeSpanType.FromTicks(thread.Context.ThreadState.CntpctEl0, thread.Context.ThreadState.CntfrqEl0).ToSeconds(); TimeSpanType ticksTimeSpan = TimeSpanType.FromTicks(thread.Context.ThreadState.CntpctEl0, thread.Context.ThreadState.CntfrqEl0);
result.TimePoint = _internalOffset.ToSeconds() + ticksTimeSpan.ToSeconds();
return result; return result;
} }
public UInt128 GetClockSourceId()
{
return _clockSourceId;
}
public SteadyClockTimePoint GetCurrentTimePoint(KThread thread) public SteadyClockTimePoint GetCurrentTimePoint(KThread thread)
{ {
SteadyClockTimePoint result = GetTimePoint(thread); SteadyClockTimePoint result = GetTimePoint(thread);

View file

@ -0,0 +1,31 @@
using Ryujinx.HLE.HOS.Kernel.Threading;
namespace Ryujinx.HLE.HOS.Services.Time.Clock
{
abstract class SystemClockCore
{
public abstract SteadyClockCore GetSteadyClockCore();
public abstract ResultCode GetSystemClockContext(KThread thread, out SystemClockContext context);
public abstract ResultCode SetSystemClockContext(SystemClockContext context);
public abstract ResultCode Flush(SystemClockContext context);
public bool IsClockSetup(KThread thread)
{
ResultCode result = GetSystemClockContext(thread, out SystemClockContext context);
if (result == ResultCode.Success)
{
SteadyClockCore steadyClockCore = GetSteadyClockCore();
SteadyClockTimePoint steadyClockTimePoint = steadyClockCore.GetCurrentTimePoint(thread);
return steadyClockTimePoint.ClockSourceId == context.SteadyTimePoint.ClockSourceId;
}
return false;
}
}
}

View file

@ -1,5 +1,6 @@
using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Services.Time.Clock;
using System; using System;
namespace Ryujinx.HLE.HOS.Services.Time namespace Ryujinx.HLE.HOS.Services.Time
@ -19,7 +20,7 @@ namespace Ryujinx.HLE.HOS.Services.Time
// GetStandardUserSystemClock() -> object<nn::timesrv::detail::service::ISystemClock> // GetStandardUserSystemClock() -> object<nn::timesrv::detail::service::ISystemClock>
public ResultCode GetStandardUserSystemClock(ServiceCtx context) public ResultCode GetStandardUserSystemClock(ServiceCtx context)
{ {
MakeObject(context, new ISystemClock(SystemClockType.User)); MakeObject(context, new ISystemClock(StandardUserSystemClockCore.Instance));
return ResultCode.Success; return ResultCode.Success;
} }
@ -28,7 +29,7 @@ namespace Ryujinx.HLE.HOS.Services.Time
// GetStandardNetworkSystemClock() -> object<nn::timesrv::detail::service::ISystemClock> // GetStandardNetworkSystemClock() -> object<nn::timesrv::detail::service::ISystemClock>
public ResultCode GetStandardNetworkSystemClock(ServiceCtx context) public ResultCode GetStandardNetworkSystemClock(ServiceCtx context)
{ {
MakeObject(context, new ISystemClock(SystemClockType.Network)); MakeObject(context, new ISystemClock(StandardNetworkSystemClockCore.Instance));
return ResultCode.Success; return ResultCode.Success;
} }
@ -55,7 +56,7 @@ namespace Ryujinx.HLE.HOS.Services.Time
// GetStandardLocalSystemClock() -> object<nn::timesrv::detail::service::ISystemClock> // GetStandardLocalSystemClock() -> object<nn::timesrv::detail::service::ISystemClock>
public ResultCode GetStandardLocalSystemClock(ServiceCtx context) public ResultCode GetStandardLocalSystemClock(ServiceCtx context)
{ {
MakeObject(context, new ISystemClock(SystemClockType.Local)); MakeObject(context, new ISystemClock(StandardLocalSystemClockCore.Instance));
return ResultCode.Success; return ResultCode.Success;
} }
@ -81,6 +82,7 @@ namespace Ryujinx.HLE.HOS.Services.Time
// CalculateMonotonicSystemClockBaseTimePoint(nn::time::SystemClockContext) -> u64 // CalculateMonotonicSystemClockBaseTimePoint(nn::time::SystemClockContext) -> u64
public ResultCode CalculateMonotonicSystemClockBaseTimePoint(ServiceCtx context) public ResultCode CalculateMonotonicSystemClockBaseTimePoint(ServiceCtx context)
{ {
// TODO: reimplement this
long timeOffset = (long)(DateTime.UtcNow - StartupDate).TotalSeconds; long timeOffset = (long)(DateTime.UtcNow - StartupDate).TotalSeconds;
long systemClockContextEpoch = context.RequestData.ReadInt64(); long systemClockContextEpoch = context.RequestData.ReadInt64();

View file

@ -39,16 +39,16 @@ namespace Ryujinx.HLE.HOS.Services.Time
[Command(200)] // 3.0.0+ [Command(200)] // 3.0.0+
// GetInternalOffset() -> nn::TimeSpanType // GetInternalOffset() -> nn::TimeSpanType
public long GetInternalOffset(ServiceCtx context) public ResultCode GetInternalOffset(ServiceCtx context)
{ {
context.ResponseData.WriteStruct(SteadyClockCore.Instance.GetInternalOffset()); context.ResponseData.WriteStruct(SteadyClockCore.Instance.GetInternalOffset());
return 0; return ResultCode.Success;
} }
[Command(201)] // 3.0.0-3.0.2 [Command(201)] // 3.0.0-3.0.2
// SetInternalOffset(nn::TimeSpanType) // SetInternalOffset(nn::TimeSpanType)
public long SetInternalOffset(ServiceCtx context) public ResultCode SetInternalOffset(ServiceCtx context)
{ {
TimeSpanType internalOffset = context.RequestData.ReadStruct<TimeSpanType>(); TimeSpanType internalOffset = context.RequestData.ReadStruct<TimeSpanType>();

View file

@ -1,97 +1,98 @@
using System; using Ryujinx.Common;
using Ryujinx.HLE.HOS.Services.Time.Clock;
namespace Ryujinx.HLE.HOS.Services.Time namespace Ryujinx.HLE.HOS.Services.Time
{ {
class ISystemClock : IpcService class ISystemClock : IpcService
{ {
private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); private SystemClockCore _clockCore;
private SystemClockType _clockType; public ISystemClock(SystemClockCore clockCore)
private DateTime _systemClockContextEpoch;
private long _systemClockTimePoint;
private byte[] _systemClockContextEnding;
private long _timeOffset;
public ISystemClock(SystemClockType clockType)
{ {
_clockType = clockType; _clockCore = clockCore;
_systemClockContextEpoch = System.Diagnostics.Process.GetCurrentProcess().StartTime;
_systemClockContextEnding = new byte[0x10];
_timeOffset = 0;
if (clockType == SystemClockType.User ||
clockType == SystemClockType.Network)
{
_systemClockContextEpoch = _systemClockContextEpoch.ToUniversalTime();
}
_systemClockTimePoint = (long)(_systemClockContextEpoch - Epoch).TotalSeconds;
} }
[Command(0)] [Command(0)]
// GetCurrentTime() -> nn::time::PosixTime // GetCurrentTime() -> nn::time::PosixTime
public ResultCode GetCurrentTime(ServiceCtx context) public ResultCode GetCurrentTime(ServiceCtx context)
{ {
DateTime currentTime = DateTime.Now; SteadyClockCore steadyClockCore = _clockCore.GetSteadyClockCore();
if (_clockType == SystemClockType.User || SteadyClockTimePoint currentTimePoint = steadyClockCore.GetCurrentTimePoint(context.Thread);
_clockType == SystemClockType.Network)
ResultCode result = _clockCore.GetSystemClockContext(context.Thread, out SystemClockContext clockContext);
if (result == ResultCode.Success)
{ {
currentTime = currentTime.ToUniversalTime(); result = ResultCode.TimeMismatch;
if (currentTimePoint.ClockSourceId == clockContext.SteadyTimePoint.ClockSourceId)
{
ulong posixTime = clockContext.Offset + currentTimePoint.TimePoint;
context.ResponseData.Write(posixTime);
result = 0;
}
} }
context.ResponseData.Write((long)((currentTime - Epoch).TotalSeconds) + _timeOffset); return result;
return ResultCode.Success;
} }
[Command(1)] [Command(1)]
// SetCurrentTime(nn::time::PosixTime) // SetCurrentTime(nn::time::PosixTime)
public ResultCode SetCurrentTime(ServiceCtx context) public ResultCode SetCurrentTime(ServiceCtx context)
{ {
DateTime currentTime = DateTime.Now; ulong posixTime = context.RequestData.ReadUInt64();
if (_clockType == SystemClockType.User || SteadyClockCore steadyClockCore = _clockCore.GetSteadyClockCore();
_clockType == SystemClockType.Network)
SteadyClockTimePoint currentTimePoint = steadyClockCore.GetCurrentTimePoint(context.Thread);
SystemClockContext clockContext = new SystemClockContext()
{ {
currentTime = currentTime.ToUniversalTime(); Offset = posixTime - currentTimePoint.TimePoint,
SteadyTimePoint = currentTimePoint
};
ResultCode result = _clockCore.SetSystemClockContext(clockContext);
if (result == ResultCode.Success)
{
result = _clockCore.Flush(clockContext);
} }
_timeOffset = (context.RequestData.ReadInt64() - (long)(currentTime - Epoch).TotalSeconds); return result;
return ResultCode.Success;
} }
[Command(2)] [Command(2)]
// GetSystemClockContext() -> nn::time::SystemClockContext // GetSystemClockContext() -> nn::time::SystemClockContext
public ResultCode GetSystemClockContext(ServiceCtx context) public ResultCode GetSystemClockContext(ServiceCtx context)
{ {
context.ResponseData.Write((long)(_systemClockContextEpoch - Epoch).TotalSeconds); ResultCode result = _clockCore.GetSystemClockContext(context.Thread, out SystemClockContext clockContext);
// The point in time, TODO: is there a link between epoch and this? if (result == ResultCode.Success)
context.ResponseData.Write(_systemClockTimePoint);
// This seems to be some kind of identifier?
for (int i = 0; i < 0x10; i++)
{ {
context.ResponseData.Write(_systemClockContextEnding[i]); context.ResponseData.WriteStruct(clockContext);
} }
return ResultCode.Success; return result;
} }
[Command(3)] [Command(3)]
// SetSystemClockContext(nn::time::SystemClockContext) // SetSystemClockContext(nn::time::SystemClockContext)
public ResultCode SetSystemClockContext(ServiceCtx context) public ResultCode SetSystemClockContext(ServiceCtx context)
{ {
long newSystemClockEpoch = context.RequestData.ReadInt64(); SystemClockContext clockContext = context.RequestData.ReadStruct<SystemClockContext>();
long newSystemClockTimePoint = context.RequestData.ReadInt64();
_systemClockContextEpoch = Epoch.Add(TimeSpan.FromSeconds(newSystemClockEpoch)); ResultCode result = _clockCore.SetSystemClockContext(clockContext);
_systemClockTimePoint = newSystemClockTimePoint;
_systemClockContextEnding = context.RequestData.ReadBytes(0x10);
return ResultCode.Success; if (result == ResultCode.Success)
{
result = _clockCore.Flush(clockContext);
}
return result;
} }
} }
} }

View file

@ -7,11 +7,13 @@
Success = 0, Success = 0,
TimeMismatch = (102 << ErrorCodeShift) | ModuleId,
TimeNotFound = (200 << ErrorCodeShift) | ModuleId, TimeNotFound = (200 << ErrorCodeShift) | ModuleId,
Overflow = (201 << ErrorCodeShift) | ModuleId, Overflow = (201 << ErrorCodeShift) | ModuleId,
LocationNameTooLong = (801 << ErrorCodeShift) | ModuleId, LocationNameTooLong = (801 << ErrorCodeShift) | ModuleId,
OutOfRange = (902 << ErrorCodeShift) | ModuleId, OutOfRange = (902 << ErrorCodeShift) | ModuleId,
TimeZoneConversionFailed = (903 << ErrorCodeShift) | ModuleId, TimeZoneConversionFailed = (903 << ErrorCodeShift) | ModuleId,
TimeZoneNotFound = (989 << ErrorCodeShift) | ModuleId TimeZoneNotFound = (989 << ErrorCodeShift) | ModuleId,
NotImplemented = (990 << ErrorCodeShift) | ModuleId,
} }
} }

View file

@ -1,10 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Time
{
enum SystemClockType
{
User,
Network,
Local,
EphemeralNetwork
}
}