diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index 9127d300d..2e5364b1b 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -176,6 +176,16 @@ if(BUILD_TESTING) set_tests_properties(guest_cpu_time_clocks PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_freebsd_clocks_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreebsdClocks.cpp) + target_include_directories(guest_freebsd_clocks_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_freebsd_clocks_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_freebsd_clocks_tests) + add_test(NAME guest_freebsd_clocks COMMAND guest_freebsd_clocks_tests) + set_tests_properties(guest_freebsd_clocks PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_freebsd_clocks PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_nanosleep_tests ${LIBS_INCLUDE_DIR}/tests/GuestNanosleep.cpp) target_include_directories(guest_nanosleep_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_nanosleep_tests PRIVATE libkernel libc) diff --git a/core/libs/prx/libkernel/Time/Time.cpp b/core/libs/prx/libkernel/Time/Time.cpp index 8cde1fc97..395f85829 100644 --- a/core/libs/prx/libkernel/Time/Time.cpp +++ b/core/libs/prx/libkernel/Time/Time.cpp @@ -23,6 +23,8 @@ #else #include #include +#include +#include #endif extern "C" int* APS5_VABI __error_nid_postfix(); @@ -263,7 +265,7 @@ int APS5_VABI usleep_nid_postfix(KernelUseconds microseconds) { } #ifdef _WIN32 -static std::uint64_t CpuTimeNanos(bool callingThread) { +static std::uint64_t CpuTimeNanos(bool callingThread, bool countKernel) { FILETIME creation{}; FILETIME exitTime{}; FILETIME kernel{}; @@ -278,7 +280,7 @@ static std::uint64_t CpuTimeNanos(bool callingThread) { const auto toTicks = [](const FILETIME& time) { return (static_cast(time.dwHighDateTime) << 32) | time.dwLowDateTime; }; - return (toTicks(kernel) + toTicks(user)) * 100ULL; + return (toTicks(user) + (countKernel ? toTicks(kernel) : 0ULL)) * 100ULL; } static std::uint64_t CpuTimeResolutionNanos() { @@ -291,12 +293,48 @@ static std::uint64_t CpuTimeResolutionNanos() { throw std::runtime_error("clock_getres: GetSystemTimeAdjustment reported no clock interrupt period"); return static_cast(increment) * 100ULL; } + +static std::uint64_t ProcessCpuNanos(bool countSystem) { + return CpuTimeNanos(false, countSystem); +} + +static std::uint64_t ProcessCpuResolutionNanos() { + return CpuTimeResolutionNanos(); +} +#else +static std::uint64_t ClockTicksPerSecond() { + const long rate = sysconf(_SC_CLK_TCK); + if (rate <= 0) + throw std::runtime_error("clock_gettime: sysconf(_SC_CLK_TCK) reported no clock tick rate"); + return static_cast(rate); +} + +static std::uint64_t ProcessCpuNanos(bool countSystem) { + struct tms usage{}; + errno = 0; + if (times(&usage) == static_cast(-1) && errno != 0) + throw std::system_error(errno, std::generic_category(), "clock_gettime: times failed"); + const std::uint64_t rate = ClockTicksPerSecond(); + const std::uint64_t ticks = static_cast(usage.tms_utime) + (countSystem ? static_cast(usage.tms_stime) : 0ULL); + return ticks / rate * 1000000000ULL + ticks % rate * 1000000000ULL / rate; +} + +static std::uint64_t ProcessCpuResolutionNanos() { + const std::uint64_t rate = ClockTicksPerSecond(); + return (1000000000ULL + rate - 1ULL) / rate; +} #endif int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* tp) { if (tp == nullptr) { APS5_INVALID_ARG_EX; } + if (clockId == 1 || clockId == 2) { + const std::uint64_t nanos = ProcessCpuNanos(clockId == 2); + tp->tv_sec = static_cast(nanos / 1000000000ULL); + tp->tv_nsec = static_cast(nanos % 1000000000ULL); + return 0; + } #ifdef _WIN32 if (clockId == 0 || clockId == 9) { FILETIME ft{}; @@ -315,17 +353,17 @@ int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* tp) { t -= 116444736000000000ULL; t *= 100ULL; tp->tv_sec = static_cast(t / 1000000000ULL); - tp->tv_nsec = static_cast(t % 1000000000ULL); + tp->tv_nsec = clockId == 13 ? 0 : static_cast(t % 1000000000ULL); return 0; } - if (clockId == 4 || clockId == 1 || clockId == 5 || clockId == 7 || clockId == 8 || clockId == 11 || clockId == 12) { + if (clockId == 4 || clockId == 5 || clockId == 7 || clockId == 8 || clockId == 11 || clockId == 12) { std::uint64_t nanos = GetMonotonicNanos(); tp->tv_sec = static_cast(nanos / 1000000000ULL); tp->tv_nsec = static_cast(nanos % 1000000000ULL); return 0; } if (clockId == 14 || clockId == 15) { - const std::uint64_t nanos = CpuTimeNanos(clockId == 14); + const std::uint64_t nanos = CpuTimeNanos(clockId == 14, true); tp->tv_sec = static_cast(nanos / 1000000000ULL); tp->tv_nsec = static_cast(nanos % 1000000000ULL); return 0; @@ -370,7 +408,7 @@ int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* tp) { return -1; } tp->tv_sec = static_cast(ts.tv_sec); - tp->tv_nsec = static_cast(ts.tv_nsec); + tp->tv_nsec = clockId == 13 ? 0 : static_cast(ts.tv_nsec); return 0; #endif } @@ -405,13 +443,24 @@ int APS5_VABI clock_getres_nid_postfix(int clockId, KernelTimespec* res) { if (res == nullptr) { APS5_INVALID_ARG_EX; } + if (clockId == 1 || clockId == 2) { + const std::uint64_t nanos = ProcessCpuResolutionNanos(); + res->tv_sec = static_cast(nanos / 1000000000ULL); + res->tv_nsec = static_cast(nanos % 1000000000ULL); + return 0; + } + if (clockId == 13) { + res->tv_sec = 1; + res->tv_nsec = 0; + return 0; + } #ifdef _WIN32 if (clockId == 0 || clockId == 9) { res->tv_sec = 0; res->tv_nsec = 100LL; return 0; } - if (clockId == 4 || clockId == 1 || clockId == 5 || clockId == 7 || clockId == 8 || clockId == 10 || clockId == 11 || clockId == 12 || clockId == 13) { + if (clockId == 4 || clockId == 5 || clockId == 7 || clockId == 8 || clockId == 10 || clockId == 11 || clockId == 12) { static const std::uint64_t freq = [] { LARGE_INTEGER f{}; QueryPerformanceFrequency(&f); @@ -437,7 +486,6 @@ int APS5_VABI clock_getres_nid_postfix(int clockId, KernelTimespec* res) { nativeId = CLOCK_REALTIME; break; case 10: - case 13: #ifdef CLOCK_REALTIME_COARSE nativeId = CLOCK_REALTIME_COARSE; #else diff --git a/core/libs/tests/GuestFreebsdClocks.cpp b/core/libs/tests/GuestFreebsdClocks.cpp new file mode 100644 index 000000000..6c7f959f0 --- /dev/null +++ b/core/libs/tests/GuestFreebsdClocks.cpp @@ -0,0 +1,138 @@ +#include "SceTypes.hpp" +#include +#include + +extern "C" { +int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* tp); +int APS5_VABI clock_getres_nid_postfix(int clockId, KernelTimespec* res); +int APS5_VABI sceKernelClockGettime(KernelClockid clockId, KernelTimespec* tp); +int APS5_VABI sceKernelClockGetres(KernelClockid clockId, KernelTimespec* tp); +int APS5_VABI sceKernelUsleep_nid_postfix(KernelUseconds microseconds); +} + +static constexpr int SCE_OK = 0; + +static constexpr int GUEST_CLOCK_REALTIME = 0; +static constexpr int GUEST_CLOCK_VIRTUAL = 1; +static constexpr int GUEST_CLOCK_PROF = 2; +static constexpr int GUEST_CLOCK_MONOTONIC = 4; +static constexpr int GUEST_CLOCK_SECOND = 13; +static constexpr int GUEST_CLOCK_PROCESS_CPUTIME_ID = 15; + +static constexpr std::int64_t NANOS_PER_SECOND = 1000000000LL; +static constexpr std::int64_t NANOS_PER_MILLISECOND = 1000000LL; +static constexpr std::int64_t BURN_NANOS = 200 * NANOS_PER_MILLISECOND; +static constexpr std::int64_t IDLE_LIMIT_NANOS = 100 * NANOS_PER_MILLISECOND; +static constexpr std::int64_t GIVE_UP_NANOS = 10 * NANOS_PER_SECOND; + +static void Require(bool value) { if (!value) std::abort(); } + +static KernelTimespec Read(int clockId) { + KernelTimespec time{-1, -1}; + Require(clock_gettime_nid_postfix(clockId, &time) == SCE_OK); + Require(time.tv_sec >= 0); + Require(time.tv_nsec >= 0 && time.tv_nsec < NANOS_PER_SECOND); + return time; +} + +static std::int64_t Nanos(int clockId) { + const KernelTimespec time = Read(clockId); + return time.tv_sec * NANOS_PER_SECOND + time.tv_nsec; +} + +static std::int64_t ResolutionNanos(int clockId) { + KernelTimespec resolution{-1, -1}; + Require(clock_getres_nid_postfix(clockId, &resolution) == SCE_OK); + Require(resolution.tv_sec == 0); + Require(resolution.tv_nsec > 0 && resolution.tv_nsec < NANOS_PER_SECOND); + KernelTimespec sceResolution{-1, -1}; + Require(sceKernelClockGetres(clockId, &sceResolution) == SCE_OK); + Require(sceResolution.tv_sec == resolution.tv_sec && sceResolution.tv_nsec == resolution.tv_nsec); + return resolution.tv_nsec; +} + +static void RequireNeverDecreases(int clockId) { + std::int64_t previous = Nanos(clockId); + for (int i = 0; i < 10000; ++i) { + const std::int64_t current = Nanos(clockId); + Require(current >= previous); + previous = current; + } +} + +static void SecondClockReportsWholeSeconds() { + KernelTimespec resolution{-1, -1}; + Require(clock_getres_nid_postfix(GUEST_CLOCK_SECOND, &resolution) == SCE_OK); + Require(resolution.tv_sec == 1 && resolution.tv_nsec == 0); + resolution = {-1, -1}; + Require(sceKernelClockGetres(GUEST_CLOCK_SECOND, &resolution) == SCE_OK); + Require(resolution.tv_sec == 1 && resolution.tv_nsec == 0); + + KernelTimespec sceSecond{-1, -1}; + Require(sceKernelClockGettime(GUEST_CLOCK_SECOND, &sceSecond) == SCE_OK); + Require(sceSecond.tv_sec > 0 && sceSecond.tv_nsec == 0); + + std::int64_t previous = 0; + for (int i = 0; i < 1000; ++i) { + const KernelTimespec before = Read(GUEST_CLOCK_REALTIME); + const KernelTimespec second = Read(GUEST_CLOCK_SECOND); + const KernelTimespec after = Read(GUEST_CLOCK_REALTIME); + Require(second.tv_nsec == 0); + Require(second.tv_sec >= before.tv_sec - 1 && second.tv_sec <= after.tv_sec); + Require(second.tv_sec >= previous); + previous = second.tv_sec; + } +} + +static void ProcessClocksAreOrdered() { + const std::int64_t profResolution = ResolutionNanos(GUEST_CLOCK_PROF); + Require(ResolutionNanos(GUEST_CLOCK_VIRTUAL) == profResolution); + for (int i = 0; i < 1000; ++i) { + const std::int64_t user = Nanos(GUEST_CLOCK_VIRTUAL); + const std::int64_t userAndSystem = Nanos(GUEST_CLOCK_PROF); + const std::int64_t execution = Nanos(GUEST_CLOCK_PROCESS_CPUTIME_ID); + Require(user <= userAndSystem); + Require(userAndSystem <= execution + profResolution); + } +} + +static void BusyProcessAccumulatesUserTime() { + const std::int64_t wallStart = Nanos(GUEST_CLOCK_MONOTONIC); + const std::int64_t profStart = Nanos(GUEST_CLOCK_PROF); + const std::int64_t userStart = Nanos(GUEST_CLOCK_VIRTUAL); + std::int64_t user = userStart; + volatile std::uint64_t work = 0; + while (user - userStart < BURN_NANOS) { + Require(Nanos(GUEST_CLOCK_MONOTONIC) - wallStart < GIVE_UP_NANOS); + for (int i = 0; i < 1000000; ++i) work = work + 1; + const std::int64_t current = Nanos(GUEST_CLOCK_VIRTUAL); + Require(current >= user); + user = current; + } + Require(Nanos(GUEST_CLOCK_PROF) - profStart >= user - userStart); +} + +static void IdleProcessAccumulatesNone() { + const std::int64_t userStart = Nanos(GUEST_CLOCK_VIRTUAL); + const std::int64_t profStart = Nanos(GUEST_CLOCK_PROF); + Require(sceKernelUsleep_nid_postfix(200000) == SCE_OK); + Require(Nanos(GUEST_CLOCK_VIRTUAL) - userStart <= IDLE_LIMIT_NANOS); + Require(Nanos(GUEST_CLOCK_PROF) - profStart <= IDLE_LIMIT_NANOS); +} + +int main() { + SecondClockReportsWholeSeconds(); + + KernelTimespec time{-1, -1}; + Require(sceKernelClockGettime(GUEST_CLOCK_VIRTUAL, &time) == SCE_OK); + Require(time.tv_sec >= 0 && time.tv_nsec >= 0 && time.tv_nsec < NANOS_PER_SECOND); + time = {-1, -1}; + Require(sceKernelClockGettime(GUEST_CLOCK_PROF, &time) == SCE_OK); + Require(time.tv_sec >= 0 && time.tv_nsec >= 0 && time.tv_nsec < NANOS_PER_SECOND); + + RequireNeverDecreases(GUEST_CLOCK_VIRTUAL); + RequireNeverDecreases(GUEST_CLOCK_PROF); + ProcessClocksAreOrdered(); + BusyProcessAccumulatesUserTime(); + IdleProcessAccumulatesNone(); +}