diff --git a/src/common/time.hpp b/src/common/time.hpp index c996a5da..d7e8bc5d 100644 --- a/src/common/time.hpp +++ b/src/common/time.hpp @@ -1,16 +1,58 @@ #pragma once -// TODO: support cross-platform time functions - #include -#include + +#if defined(__x86_64__) || defined(_M_X64) || defined(__amd64__) +#include +#endif #include "common/types.hpp" +#include "core/hw/tegra_x1/cpu/const.hpp" using namespace std::chrono_literals; namespace hydra { -inline u64 get_absolute_time() { return mach_absolute_time(); } +#if defined(__x86_64__) || defined(_M_X64) || defined(__amd64__) + +inline u64 GetSystemTick() { + _mm_lfence(); + u64 res = __rdtsc(); + _mm_lfence(); + return res; +} + +inline u64 GetSystemFrequency() { + auto nsc_start = std::chrono::steady_clock::now().time_since_epoch(); + u64 tsc_start = GetSystemTick(); + // More sleep, more precision. + std::this_thread::sleep_for(10ms); + auto nsc_end = std::chrono::steady_clock::now().time_since_epoch(); + u64 tsc_end = GetSystemTick(); + u64 ns_diff = + static_cast(std::chrono::duration_cast( + nsc_end - nsc_start) + .count()); + u64 res = (tsc_end - tsc_start) * 1000000000ULL / (ns_diff); + res = res + 100'000 / 2; + res -= res % 100'000; + return res; +} + +#elif defined(_M_ARM64) || defined(__aarch64__) + +inline u64 GetSystemTick() { + u64 res; + __asm__ __volatile__("mrs %0, cntvct_el0; " : "=r"(res)::"memory"); + return res; +} + +inline u64 GetSystemFrequency() { + u64 res; + __asm__ __volatile__("mrs %0, cntfrq_el0; isb; " : "=r"(res)::"memory"); + return res; +} + +#endif } // namespace hydra diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index ebd5be1b..e2dd9e06 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -441,6 +441,8 @@ add_library(hydra-core audio/null/stream.hpp audio/null/core.cpp audio/null/core.hpp + hw/wall_clock.cpp + hw/wall_clock.hpp hw/generic_mmu.hpp hw/tegra_x1/cpu/const.hpp hw/tegra_x1/cpu/memory.hpp diff --git a/src/core/emulation_context.cpp b/src/core/emulation_context.cpp index eae205b2..8424de45 100644 --- a/src/core/emulation_context.cpp +++ b/src/core/emulation_context.cpp @@ -129,6 +129,9 @@ void EmulationContext::LoadAndStart(horizon::loader::LoaderBase* loader) { // Check for firmware applets auto controller = new horizon::services::am::LibraryAppletController( horizon::LibraryAppletMode::AllForeground); + // TODO: correct? + u64 system_tick; + os->GetKernel().GetSystemTick(system_tick); switch (loader->GetTitleID()) { case 0x0100000000001003: { // controller // Common args @@ -138,7 +141,7 @@ void EmulationContext::LoadAndStart(horizon::loader::LoaderBase* loader) { .library_applet_api_version = 1, // TODO: correct? .theme_color = 0, // HACK .play_startup_sound = false, // HACK - .system_tick = get_absolute_time(), + .system_tick = system_tick, }; controller->PushInData( new horizon::services::am::IStorage(common_args)); @@ -184,7 +187,7 @@ void EmulationContext::LoadAndStart(horizon::loader::LoaderBase* loader) { .library_applet_api_version = 1, // TODO: correct? .theme_color = 0, // HACK .play_startup_sound = false, // HACK - .system_tick = get_absolute_time(), + .system_tick = system_tick, }; controller->PushInData( new horizon::services::am::IStorage(common_args)); @@ -219,7 +222,7 @@ void EmulationContext::LoadAndStart(horizon::loader::LoaderBase* loader) { .library_applet_api_version = 1, // TODO: correct? .theme_color = 0, // HACK .play_startup_sound = false, // HACK - .system_tick = get_absolute_time(), + .system_tick = system_tick, }; controller->PushInData( new horizon::services::am::IStorage(common_args)); @@ -252,7 +255,7 @@ void EmulationContext::LoadAndStart(horizon::loader::LoaderBase* loader) { .library_applet_api_version = 1, // TODO: correct? .theme_color = 0, // HACK .play_startup_sound = false, // HACK - .system_tick = get_absolute_time(), + .system_tick = system_tick, }; controller->PushInData( new horizon::services::am::IStorage(common_args)); diff --git a/src/core/emulation_context.hpp b/src/core/emulation_context.hpp index 903148d2..719213f1 100644 --- a/src/core/emulation_context.hpp +++ b/src/core/emulation_context.hpp @@ -4,6 +4,7 @@ #include "core/horizon/os.hpp" #include "core/hw/tegra_x1/cpu/cpu.hpp" #include "core/hw/tegra_x1/gpu/gpu.hpp" +#include "core/hw/wall_clock.hpp" namespace hydra { @@ -44,6 +45,7 @@ class EmulationContext { private: // Objects + hw::WallClock wall_clock; hw::tegra_x1::cpu::ICpu* cpu; hw::tegra_x1::gpu::Gpu* gpu; audio::ICore* audio_core; diff --git a/src/core/horizon/kernel/kernel.cpp b/src/core/horizon/kernel/kernel.cpp index 5caeba5b..9dc97230 100644 --- a/src/core/horizon/kernel/kernel.cpp +++ b/src/core/horizon/kernel/kernel.cpp @@ -12,6 +12,7 @@ #include "core/hw/tegra_x1/cpu/cpu.hpp" #include "core/hw/tegra_x1/cpu/mmu.hpp" #include "core/hw/tegra_x1/cpu/thread.hpp" +#include "core/hw/wall_clock.hpp" namespace hydra::horizon::kernel { @@ -855,7 +856,7 @@ result_t Kernel::SignalProcessWideKey(Process* crnt_process, uptr addr, void Kernel::GetSystemTick(u64& out_tick) { LOG_DEBUG(Kernel, "GetSystemTick called"); - out_tick = get_absolute_time(); + out_tick = hw::WallClock::GetInstance().GetCntpct(); // TODO: correct? } result_t Kernel::ConnectToNamedPort(const std::string_view name, diff --git a/src/core/horizon/services/account/internal/user.hpp b/src/core/horizon/services/account/internal/user.hpp index 6c13c582..8045261a 100644 --- a/src/core/horizon/services/account/internal/user.hpp +++ b/src/core/horizon/services/account/internal/user.hpp @@ -6,6 +6,13 @@ namespace hydra::horizon::services::account::internal { constexpr uuid_t INVALID_USER_ID = 0x0; +inline u64 GetTimestamp() { + return static_cast( + std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count()); +} + class User { friend class UserManager; @@ -66,7 +73,7 @@ class User { std::string avatar_path; // Helpers - void NotifyEdit() { base.last_edit_timestamp = get_absolute_time(); } + void NotifyEdit() { base.last_edit_timestamp = GetTimestamp(); } public: CONST_REF_GETTER(base, GetBase); diff --git a/src/core/horizon/services/account/internal/user_manager.cpp b/src/core/horizon/services/account/internal/user_manager.cpp index 2a893eb5..a1d36f3b 100644 --- a/src/core/horizon/services/account/internal/user_manager.cpp +++ b/src/core/horizon/services/account/internal/user_manager.cpp @@ -219,7 +219,7 @@ void UserManager::Serialize(uuid_t user_id) { } ofs.close(); - user_pair.second = get_absolute_time(); + user_pair.second = GetTimestamp(); } void UserManager::Deserialize(uuid_t user_id) { diff --git a/src/core/horizon/services/nvdrv/ioctl/nvhost_ctrl_gpu.cpp b/src/core/horizon/services/nvdrv/ioctl/nvhost_ctrl_gpu.cpp index 63964546..611ad31f 100644 --- a/src/core/horizon/services/nvdrv/ioctl/nvhost_ctrl_gpu.cpp +++ b/src/core/horizon/services/nvdrv/ioctl/nvhost_ctrl_gpu.cpp @@ -1,5 +1,7 @@ #include "core/horizon/services/nvdrv/ioctl/nvhost_ctrl_gpu.hpp" +#include "core/hw/wall_clock.hpp" + namespace hydra::horizon::services::nvdrv::ioctl { DEFINE_IOCTL_TABLE(NvHostCtrlGpu, @@ -159,8 +161,7 @@ NvResult NvHostCtrlGpu::PmuGetGpuLoad(u32* out_load) { NvResult NvHostCtrlGpu::GetGpuTime(u64* out_timestamp, [[maybe_unused]] u64* _out_reserved) { - // TODO: is it okay to just return Mmu time? - *out_timestamp = get_absolute_time(); + *out_timestamp = hw::WallClock::GetInstance().GetGpuTick(); return NvResult::Success; } diff --git a/src/core/horizon/services/pcv/pcv_service.hpp b/src/core/horizon/services/pcv/pcv_service.hpp index 758c9128..8abd582e 100644 --- a/src/core/horizon/services/pcv/pcv_service.hpp +++ b/src/core/horizon/services/pcv/pcv_service.hpp @@ -2,6 +2,7 @@ #include "core/horizon/services/const.hpp" #include "core/hw/tegra_x1/cpu/const.hpp" +#include "core/hw/wall_clock.hpp" namespace hydra::horizon::services::pcv { @@ -105,8 +106,8 @@ class IPcvService : public IService { private: // TODO: other clock rates - u32 clock_rates[(u32)ModuleId::Count] = {hw::tegra_x1::cpu::CLOCK_RATE_HZ, - 0}; + u32 clock_rates[(u32)ModuleId::Count] = {hw::GUEST_CNTFRQ, + hw::GPU_TICK_FREQ}; // Commands result_t SetClockRate(ModuleId module_id, u32 rate); diff --git a/src/core/hw/tegra_x1/cpu/const.hpp b/src/core/hw/tegra_x1/cpu/const.hpp index 529fe4c2..5c14ddee 100644 --- a/src/core/hw/tegra_x1/cpu/const.hpp +++ b/src/core/hw/tegra_x1/cpu/const.hpp @@ -6,6 +6,4 @@ constexpr usize GUEST_PAGE_SIZE = 0x1000; constexpr u32 MAX_STACK_TRACE_DEPTH = 32; -constexpr u32 CLOCK_RATE_HZ = 19'200'000; - } // namespace hydra::hw::tegra_x1::cpu diff --git a/src/core/hw/tegra_x1/cpu/dynarmic/thread.cpp b/src/core/hw/tegra_x1/cpu/dynarmic/thread.cpp index 95802007..cd94ca6e 100644 --- a/src/core/hw/tegra_x1/cpu/dynarmic/thread.cpp +++ b/src/core/hw/tegra_x1/cpu/dynarmic/thread.cpp @@ -5,6 +5,7 @@ #include #include "core/hw/tegra_x1/cpu/dynarmic/mmu.hpp" +#include "core/hw/wall_clock.hpp" #include "dynarmic/interface/optimization_flags.h" ENABLE_ENUM_FORMATTING(Dynarmic::A64::Exception, UnallocatedEncoding, @@ -43,7 +44,7 @@ Thread::Thread(IMmu* mmu, const ThreadCallbacks& callbacks, IMemory* tls_mem, config.tpidr_el0 = &tpidr_el0; // TODO: what is this? config.dczid_el0 = 4; config.ctr_el0 = 0x8444c004; - config.cntfrq_el0 = CLOCK_RATE_HZ; + config.cntfrq_el0 = GUEST_CNTFRQ; // Unpredictable instructions config.define_unpredictable_behaviour = true; @@ -166,7 +167,7 @@ void Thread::ExceptionRaised([[maybe_unused]] u64 pc, jit->HaltExecution(); } -u64 Thread::GetCNTPCT() { return get_absolute_time(); } +u64 Thread::GetCNTPCT() { return WallClock::GetInstance().GetCntpct(); } void Thread::SerializeState() { for (u32 i = 0; i < 29; i++) diff --git a/src/core/hw/tegra_x1/cpu/hypervisor/thread.cpp b/src/core/hw/tegra_x1/cpu/hypervisor/thread.cpp index bff4ea40..3d191a98 100644 --- a/src/core/hw/tegra_x1/cpu/hypervisor/thread.cpp +++ b/src/core/hw/tegra_x1/cpu/hypervisor/thread.cpp @@ -6,6 +6,7 @@ #include "core/debugger/debugger_manager.hpp" #include "core/hw/tegra_x1/cpu/hypervisor/cpu.hpp" #include "core/hw/tegra_x1/cpu/hypervisor/mmu.hpp" +#include "core/hw/wall_clock.hpp" #define CPU (*static_cast(&CPU_INSTANCE)) #define MMU (*static_cast(mmu)) @@ -336,12 +337,10 @@ void Thread::InstructionTrap(u32 esr) { // Op0 Op2 Op1 CRn 00000 CRm switch ((esr >> 1) & 0x1ffe0f) { case 0b11'000'011'1110'00000'0000: // CNTFRQ_EL0 - ONCE(LOG_WARN(Hypervisor, "Frequency")); - // TODO: correct? - state.r[rt] = CLOCK_RATE_HZ; + state.r[rt] = GUEST_CNTFRQ; break; - case 0b11'001'011'1110'00000'0000: // CNTPCT_EL0 - state.r[rt] = get_absolute_time(); // TODO: correct? + case 0b11'001'011'1110'00000'0000: // CNTPCT_EL0 + state.r[rt] = WallClock::GetInstance().GetCntpct(); break; default: LOG_FATAL(Hypervisor, diff --git a/src/core/hw/wall_clock.cpp b/src/core/hw/wall_clock.cpp new file mode 100644 index 00000000..4ed67120 --- /dev/null +++ b/src/core/hw/wall_clock.cpp @@ -0,0 +1,35 @@ +#include "core/hw/wall_clock.hpp" + +namespace hydra::hw { + +namespace { + +u128 GetFactor(u64 num, u64 den) { + return (static_cast(num) << 64) / den; +} + +u64 MultiplyByFactor(u64 num, u128 factor) { return (num * factor) >> 64; } + +} // namespace + +SINGLETON_DEFINE_GET_INSTANCE(WallClock, Other) + +WallClock::WallClock() { + SINGLETON_SET_INSTANCE(WallClock, Other); + + const auto host_freq = GetSystemFrequency(); + guest_factor = GetFactor(GUEST_CNTFRQ, host_freq); + gpu_tick_factor = GetFactor(GPU_TICK_FREQ, host_freq); +} + +WallClock::~WallClock() { SINGLETON_UNSET_INSTANCE(); } + +u64 WallClock::GetCntpct() const { + return MultiplyByFactor(GetSystemTick(), guest_factor); +} + +u64 WallClock::GetGpuTick() const { + return MultiplyByFactor(GetSystemTick(), gpu_tick_factor); +} + +} // namespace hydra::hw diff --git a/src/core/hw/wall_clock.hpp b/src/core/hw/wall_clock.hpp new file mode 100644 index 00000000..b0444bb4 --- /dev/null +++ b/src/core/hw/wall_clock.hpp @@ -0,0 +1,23 @@ +#pragma once + +namespace hydra::hw { + +constexpr u32 GUEST_CNTFRQ = 19'200'000; +constexpr u64 GPU_TICK_FREQ = 614'400'000; + +class WallClock { + public: + static WallClock& GetInstance(); + + WallClock(); + ~WallClock(); + + u64 GetCntpct() const; + u64 GetGpuTick() const; + + private: + u128 guest_factor; + u128 gpu_tick_factor; +}; + +} // namespace hydra::hw