#include "prx/libkernel/Time/include/Time.hpp" #include "prx/libkernel/Time/include/TimedWait.hpp" #include "prx/libkernel/Time/include/SleepWake.hpp" #include "prx/libkernel/Pthread/include/Cancel.hpp" #include "prx/libc/include/General.hpp" #include "prx/libkernel/KernelErrors.hpp" #include "prx/libkernel/Pthread/include/Pthread.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef _WIN32 #include #else #include #include #include #include #ifdef __linux__ #include #include #endif // __linux__ #endif extern "C" int* APS5_VABI __error_nid_postfix(); static constexpr int GUEST_EFAULT = 14; static constexpr int GUEST_EINVAL = 22; static constexpr int GUEST_CLOCK_REALTIME = 0; static constexpr int GUEST_CLOCK_THREAD_CPUTIME_ID = 14; static bool IsCpuClock(int clockId) { const auto bits = static_cast(clockId); if ((bits & CPU_CLOCK_BIT) == 0) return false; if ((bits & CPU_CLOCK_PROCESS_BIT) != 0) throw std::runtime_error("clock: unsupported process CPU clock_id " + std::to_string(clockId)); return true; } static std::uint64_t RawMonotonicNanos() { #ifdef _WIN32 return TimedWait::NowNanos(); #else struct timespec ts{}; clock_gettime(CLOCK_MONOTONIC, &ts); return static_cast(ts.tv_sec) * 1000000000ULL + static_cast(ts.tv_nsec); #endif } // Debug aid: APS5_TIME_SCALE= slows (or speeds) the guest's monotonic clocks and TSC, so a // title running far below its frame rate sees plausible frame times. Sleeps still take real time. static double TimeScale() { static const double scale = [] { const char* value = std::getenv("APS5_TIME_SCALE"); const double parsed = value ? std::strtod(value, nullptr) : 1.0; return parsed > 0.0 ? parsed : 1.0; }(); return scale; } static std::uint64_t ClockOrigin() { static const std::uint64_t origin = RawMonotonicNanos(); return origin; } static std::uint64_t GetMonotonicNanos() { if (TimeScale() == 1.0) return RawMonotonicNanos(); const auto origin = ClockOrigin(); const auto raw = RawMonotonicNanos(); return origin + static_cast(static_cast(raw - origin) * TimeScale()); } static std::uint64_t GetStartNanos() { static const std::uint64_t start = GetMonotonicNanos(); return start; } static const std::uint64_t g_processStart = GetStartNanos(); #ifdef _WIN32 // Every relative wait in the process (winpthreads nanosleep, std::condition_variable::wait_for, the // GPU driver's poll sleeps) rounds up to the scheduler tick, 15.6 ms by default; a console game and // its driver hand labels between queues dozens of times per frame, so the tick is raised to 0.5 ms at // load, and Windows 11 is told not to drop it while the window is unfocused. APS5_NO_TIMER_RESOLUTION=1 // keeps the default for comparison. static const bool g_timerResolutionRaised = [] { if (std::getenv("APS5_NO_TIMER_RESOLUTION") != nullptr) return false; using NtSetTimerResolutionFn = LONG(WINAPI*)(ULONG, BOOLEAN, PULONG); const HMODULE ntdll = GetModuleHandleW(L"ntdll.dll"); const auto setResolution = ntdll ? reinterpret_cast(reinterpret_cast(GetProcAddress(ntdll, "NtSetTimerResolution"))) : nullptr; ULONG previous = 0; const bool raised = setResolution != nullptr && setResolution(5000, TRUE, &previous) >= 0; PROCESS_POWER_THROTTLING_STATE throttling{}; throttling.Version = PROCESS_POWER_THROTTLING_CURRENT_VERSION; throttling.ControlMask = PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION; throttling.StateMask = 0; SetProcessInformation(GetCurrentProcess(), ProcessPowerThrottling, &throttling, sizeof(throttling)); using NtQueryTimerResolutionFn = LONG(WINAPI*)(PULONG, PULONG, PULONG); const auto queryResolution = ntdll ? reinterpret_cast(reinterpret_cast(GetProcAddress(ntdll, "NtQueryTimerResolution"))) : nullptr; ULONG minimum = 0, maximum = 0, current = 0; if (queryResolution) queryResolution(&minimum, &maximum, ¤t); std::fprintf(stderr, "[time] scheduler tick %s (was %.2f ms, now %.2f ms, timed waits %s)\n", raised ? "raised to 0.5 ms" : "unchanged", previous / 10000.0, current / 10000.0, TimedWait::Coarse() ? "coarse" : "fine"); return raised; }(); extern "C" { int APS5_VABI scePthreadCondInit(PthreadCond* cond, const PthreadCondattr* attr, const char* name); int APS5_VABI scePthreadCondDestroy(PthreadCond* cond); int APS5_VABI scePthreadCondTimedwait(PthreadCond* cond, PthreadMutex* mutex, unsigned int usec); int APS5_VABI scePthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr* attr, const char* name); int APS5_VABI scePthreadMutexDestroy(PthreadMutex* mutex); int APS5_VABI scePthreadMutexLock(PthreadMutex* mutex); int APS5_VABI scePthreadMutexUnlock(PthreadMutex* mutex); } static void ReportTimedWait(const char* name, const std::function& body) { std::vector samples(200); for (auto& sample : samples) { const std::uint64_t start = TimedWait::NowNanos(); body(); sample = static_cast(TimedWait::NowNanos() - start) / 1000.0; } std::sort(samples.begin(), samples.end()); double sum = 0.0; for (const double sample : samples) sum += sample; std::fprintf(stderr, "[timers] %-44s mean %8.1f p50 %8.1f max %8.1f us\n", name, sum / static_cast(samples.size()), samples[samples.size() / 2], samples.back()); } static const bool g_timerSelfTest = [] { if (std::getenv("APS5_TRACE_TIMERS") == nullptr) return false; std::mutex mutex; std::condition_variable cv; ReportTimedWait("std::condition_variable::wait_for(1 ms)", [&] { std::unique_lock lock(mutex); cv.wait_for(lock, std::chrono::milliseconds(1)); }); PthreadCond cond = nullptr; PthreadMutex guestMutex = nullptr; scePthreadCondInit(&cond, nullptr, nullptr); scePthreadMutexInit(&guestMutex, nullptr, nullptr); scePthreadMutexLock(&guestMutex); ReportTimedWait("scePthreadCondTimedwait(1000 us)", [&] { scePthreadCondTimedwait(&cond, &guestMutex, 1000); }); ReportTimedWait("scePthreadCondTimedwait(13000 us)", [&] { scePthreadCondTimedwait(&cond, &guestMutex, 13000); }); scePthreadMutexUnlock(&guestMutex); scePthreadMutexDestroy(&guestMutex); scePthreadCondDestroy(&cond); ReportTimedWait("sceKernelUsleep(1000)", [&] { sceKernelUsleep_nid_postfix(1000); }); ReportTimedWait("Sleep(1)", [&] { Sleep(1); }); HANDLE event = CreateEventW(nullptr, TRUE, FALSE, nullptr); ReportTimedWait("WaitForSingleObject(event, 1)", [&] { WaitForSingleObject(event, 1); }); CloseHandle(event); HANDLE timer = CreateWaitableTimerExW(nullptr, nullptr, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS); ReportTimedWait("high-resolution waitable timer 1 ms", [&] { LARGE_INTEGER due{}; due.QuadPart = -10000; SetWaitableTimer(timer, &due, 0, nullptr, nullptr, FALSE); WaitForSingleObject(timer, INFINITE); }); CloseHandle(timer); return true; }(); #endif extern "C" { std::uint64_t APS5_VABI sceKernelGetProcessTime() { const std::uint64_t start = GetStartNanos(); return (GetMonotonicNanos() - start) / 1000ULL; } std::uint64_t APS5_VABI sceKernelGetProcessTimeCounter() { const std::uint64_t nanos = GetMonotonicNanos() - GetStartNanos(); const std::uint64_t frequency = sceKernelGetTscFrequency(); return nanos / 1000000000ULL * frequency + nanos % 1000000000ULL * frequency / 1000000000ULL; } std::uint64_t APS5_VABI sceKernelGetProcessTimeCounterFrequency() { return sceKernelGetTscFrequency(); } static std::string DescribeCallSite(const void* caller) { const auto address = reinterpret_cast(caller); char text[192]; #ifdef _WIN32 const auto image = reinterpret_cast(GetModuleHandleW(nullptr)); std::snprintf(text, sizeof(text), "exe+0x%llx", static_cast(address - image)); #elif defined(__linux__) Dl_info info{}; link_map* map = nullptr; if (dladdr1(caller, &info, reinterpret_cast(&map), RTLD_DL_LINKMAP) == 0 || map == nullptr) { std::snprintf(text, sizeof(text), "0x%llx", static_cast(address)); return text; } const char* path = map->l_name; const char* slash = path == nullptr ? nullptr : std::strrchr(path, '/'); const char* module = path == nullptr || *path == '\0' ? "exe" : slash == nullptr ? path : slash + 1; std::snprintf(text, sizeof(text), "%s+0x%llx", module, static_cast(address - map->l_addr)); #else std::snprintf(text, sizeof(text), "0x%llx", static_cast(address)); #endif return text; } static unsigned long CurrentThreadIdForTrace() { #ifdef _WIN32 return GetCurrentThreadId(); #elif defined(__linux__) return static_cast(gettid()); #else return 0; #endif } // Debug aid: APS5_TRACE_USLEEP reports every 2000 calls which guest call sites sleep, so a CPU // thread polling for GPU or I/O completion can be found (offsets are relative to the executable). static void TraceSleep(const void* caller, std::uint64_t microseconds) { static const bool enabled = std::getenv("APS5_TRACE_USLEEP") != nullptr; if (!enabled) return; static std::mutex mutex; static std::unordered_map> sites; static std::uint64_t calls = 0; std::lock_guard lock(mutex); auto& site = sites[reinterpret_cast(caller)]; ++site.first; site.second += microseconds; if (++calls % 2000 != 0) return; std::vector>> hot(sites.begin(), sites.end()); std::sort(hot.begin(), hot.end(), [](const auto& a, const auto& b) { return a.second.first > b.second.first; }); std::fprintf(stderr, "[usleep] %llu calls:", static_cast(calls)); for (std::size_t i = 0; i < hot.size() && i < 6; ++i) std::fprintf(stderr, " %s x%llu (%llu us)", DescribeCallSite(reinterpret_cast(hot[i].first)).c_str(), static_cast(hot[i].second.first), static_cast(hot[i].second.second)); std::fprintf(stderr, "\n"); } void KernelTraceWait_nid_postfix(const char* kind, const void* caller, std::uint64_t waitedNanos, bool timedOut) { static const bool enabled = std::getenv("APS5_TRACE_WAITS") != nullptr; if (!enabled) return; struct Site { std::uint64_t calls = 0; std::uint64_t timeouts = 0; std::uint64_t nanos = 0; std::uint64_t longest = 0; unsigned long lastThread = 0; }; static std::mutex mutex; static std::unordered_map sites; static std::uint64_t lastReport = 0; static std::unordered_map callSites; char key[224]; const unsigned long thread = CurrentThreadIdForTrace(); std::lock_guard lock(mutex); auto callSite = callSites.find(caller); if (callSite == callSites.end()) callSite = callSites.emplace(caller, DescribeCallSite(caller)).first; std::snprintf(key, sizeof(key), "%s %s", kind, callSite->second.c_str()); auto& site = sites[key]; ++site.calls; site.timeouts += timedOut ? 1u : 0u; site.nanos += waitedNanos; site.longest = std::max(site.longest, waitedNanos); site.lastThread = thread; const auto now = GetMonotonicNanos(); if (lastReport == 0) lastReport = now; if (now - lastReport < 3000000000ULL) return; lastReport = now; std::vector> hot(sites.begin(), sites.end()); std::sort(hot.begin(), hot.end(), [](const auto& a, const auto& b) { return a.second.nanos > b.second.nanos; }); std::fprintf(stderr, "[waits]"); for (std::size_t i = 0; i < hot.size() && i < 8; ++i) { const auto& s = hot[i].second; std::fprintf(stderr, " | %s x%llu %.1fs (avg %.1f ms, max %.0f ms, %llu timeouts, tid %lu)", hot[i].first.c_str(), static_cast(s.calls), s.nanos / 1e9, s.nanos / 1e6 / static_cast(s.calls), s.longest / 1e6, static_cast(s.timeouts), s.lastThread); } std::fprintf(stderr, "\n"); for (auto& [name, s] : sites) s = Site{}; } static void CancellableSleep(std::uint64_t nanos) { ThreadCancel::Check(); SleepWake::Sleep(nanos); ThreadCancel::Check(); } int APS5_VABI sceKernelUsleep_nid_postfix(KernelUseconds microseconds) { TraceSleep(__builtin_return_address(0), microseconds); CancellableSleep(static_cast(microseconds) * 1000ULL); return 0; } static int SleepForRequest(const KernelTimespec* rqtp) { constexpr int guestFault = 14; constexpr int guestInvalid = 22; if (rqtp == nullptr) return guestFault; if (rqtp->tv_nsec < 0 || rqtp->tv_nsec >= 1000000000LL) return guestInvalid; constexpr std::int64_t longestSleepSeconds = INT32_MAX / 2; if (rqtp->tv_sec >= 0) { CancellableSleep(static_cast(std::min(static_cast(rqtp->tv_sec), longestSleepSeconds)) * 1000000000ULL + static_cast(rqtp->tv_nsec)); } return 0; } int APS5_VABI sceKernelNanosleep(const KernelTimespec* rqtp, KernelTimespec* rmtp) { const int error = SleepForRequest(rqtp); return error == 0 ? 0 : SceKernelError(error); } int APS5_VABI nanosleep_nid_postfix(const KernelTimespec* rqtp, KernelTimespec* rmtp) { const int error = SleepForRequest(rqtp); if (error == 0) return 0; *__error_nid_postfix() = error; return -1; } int APS5_VABI _nanosleep_nid_postfix(const KernelTimespec* rqtp, KernelTimespec* rmtp) { return nanosleep_nid_postfix(rqtp, rmtp); } int APS5_VABI usleep_nid_postfix(KernelUseconds microseconds) { CancellableSleep(static_cast(microseconds) * 1000ULL); return 0; } #ifdef _WIN32 static constexpr int GUEST_EIO = 5; static constexpr int GUEST_EPERM = 1; int APS5_VABI ClockTimeWin32ErrorToGuestErrno_nid_no_patch(std::uint32_t error) { if (error == ERROR_NOACCESS) return GUEST_EFAULT; if (error == ERROR_ACCESS_DENIED) return GUEST_EPERM; return GUEST_EIO; } static bool CpuTimeNanos(bool callingThread, bool countKernel, std::uint64_t* nanos) { FILETIME creation{}; FILETIME exitTime{}; FILETIME kernel{}; FILETIME user{}; if (callingThread) { if (!GetThreadTimes(GetCurrentThread(), &creation, &exitTime, &kernel, &user)) { *__error_nid_postfix() = ClockTimeWin32ErrorToGuestErrno_nid_no_patch(GetLastError()); return false; } } else { if (!GetProcessTimes(GetCurrentProcess(), &creation, &exitTime, &kernel, &user)) { *__error_nid_postfix() = ClockTimeWin32ErrorToGuestErrno_nid_no_patch(GetLastError()); return false; } } const auto toTicks = [](const FILETIME& time) { return (static_cast(time.dwHighDateTime) << 32) | time.dwLowDateTime; }; *nanos = (toTicks(user) + (countKernel ? toTicks(kernel) : 0ULL)) * 100ULL; return true; } static bool CpuTimeResolutionNanos(std::uint64_t* nanos) { DWORD adjustment = 0; DWORD increment = 0; BOOL adjustmentDisabled = FALSE; if (!GetSystemTimeAdjustment(&adjustment, &increment, &adjustmentDisabled)) { *__error_nid_postfix() = ClockTimeWin32ErrorToGuestErrno_nid_no_patch(GetLastError()); return false; } if (increment == 0) throw std::runtime_error("clock_getres: GetSystemTimeAdjustment reported no clock interrupt period"); *nanos = static_cast(increment) * 100ULL; return true; } static bool ProcessCpuResolutionNanos(std::uint64_t* nanos) { return CpuTimeResolutionNanos(nanos); } #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 bool ProcessCpuResolutionNanos(std::uint64_t* nanos) { const std::uint64_t rate = ClockTicksPerSecond(); *nanos = (1000000000ULL + rate - 1ULL) / rate; return true; } #endif static int ReadClock(int clockId, KernelTimespec* tp) { if (IsCpuClock(clockId)) { std::uint64_t nanos = 0; if (!GuestThreadCpuNanos(static_cast(static_cast(clockId) & CPU_CLOCK_ID_MASK), &nanos)) { *__error_nid_postfix() = GUEST_EINVAL; return -1; } tp->tv_sec = static_cast(nanos / 1000000000ULL); tp->tv_nsec = static_cast(nanos % 1000000000ULL); return 0; } if (clockId == 1 || clockId == 2) { #ifdef _WIN32 std::uint64_t nanos = 0; if (!CpuTimeNanos(false, clockId == 2, &nanos)) return -1; #else const std::uint64_t nanos = ProcessCpuNanos(clockId == 2); #endif 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{}; GetSystemTimePreciseAsFileTime(&ft); std::uint64_t t = (static_cast(ft.dwHighDateTime) << 32) | ft.dwLowDateTime; t -= 116444736000000000ULL; t *= 100ULL; tp->tv_sec = static_cast(t / 1000000000ULL); tp->tv_nsec = static_cast(t % 1000000000ULL); return 0; } if (clockId == 10 || clockId == 13) { FILETIME ft{}; GetSystemTimeAsFileTime(&ft); std::uint64_t t = (static_cast(ft.dwHighDateTime) << 32) | ft.dwLowDateTime; t -= 116444736000000000ULL; t *= 100ULL; tp->tv_sec = static_cast(t / 1000000000ULL); tp->tv_nsec = clockId == 13 ? 0 : static_cast(t % 1000000000ULL); return 0; } 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) { std::uint64_t nanos = 0; if (!CpuTimeNanos(clockId == 14, true, &nanos)) return -1; tp->tv_sec = static_cast(nanos / 1000000000ULL); tp->tv_nsec = static_cast(nanos % 1000000000ULL); return 0; } throw std::runtime_error(std::string("clock_gettime") + ": unsupported clock_id " + std::to_string(clockId)); #else clockid_t nativeId; switch (clockId) { case 0: case 9: nativeId = CLOCK_REALTIME; break; case 10: case 13: #ifdef CLOCK_REALTIME_COARSE nativeId = CLOCK_REALTIME_COARSE; #else nativeId = CLOCK_REALTIME; #endif break; case 4: case 7: case 11: nativeId = CLOCK_MONOTONIC; break; case 5: case 8: case 12: nativeId = CLOCK_MONOTONIC; break; case 14: nativeId = CLOCK_THREAD_CPUTIME_ID; break; case 15: nativeId = CLOCK_PROCESS_CPUTIME_ID; break; default: throw std::runtime_error(std::string("clock_gettime") + ": unsupported clock_id " + std::to_string(clockId)); } struct timespec ts{}; if (clock_gettime(nativeId, &ts) != 0) { return -1; } tp->tv_sec = static_cast(ts.tv_sec); tp->tv_nsec = clockId == 13 ? 0 : static_cast(ts.tv_nsec); return 0; #endif } int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* tp) { KernelTimespec value{}; if (ReadClock(clockId, &value) != 0) return -1; if (tp == nullptr) { *__error_nid_postfix() = GUEST_EFAULT; return -1; } *tp = value; return 0; } int APS5_VABI gettimeofday_nid_postfix(KernelTimeval* tv, KernelTimezone* tz) { KernelTimeval ignoredTime{}; if (tv == nullptr) tv = &ignoredTime; #ifdef _WIN32 FILETIME ft{}; GetSystemTimePreciseAsFileTime(&ft); std::uint64_t t = (static_cast(ft.dwHighDateTime) << 32) | ft.dwLowDateTime; t -= 116444736000000000ULL; tv->tv_sec = static_cast(t / 10000000ULL); tv->tv_usec = static_cast((t % 10000000ULL) / 10ULL); #else struct timeval native{}; if (gettimeofday(&native, nullptr) != 0) { return -1; } tv->tv_sec = static_cast(native.tv_sec); tv->tv_usec = static_cast(native.tv_usec); #endif if (tz != nullptr) { tz->tz_minuteswest = 0; tz->tz_dsttime = 0; } return 0; } int APS5_VABI clock_getres_nid_postfix(int clockId, KernelTimespec* res) { if (res == nullptr) return 0; if (IsCpuClock(clockId)) return clock_getres_nid_postfix(GUEST_CLOCK_THREAD_CPUTIME_ID, res); if (clockId == 1 || clockId == 2) { std::uint64_t nanos = 0; if (!ProcessCpuResolutionNanos(&nanos)) return -1; 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 == 5 || clockId == 7 || clockId == 8 || clockId == 10 || clockId == 11 || clockId == 12) { static const std::uint64_t freq = [] { LARGE_INTEGER f{}; QueryPerformanceFrequency(&f); return static_cast(f.QuadPart); }(); std::uint64_t nsPerTick = (1000000000ULL + freq - 1ULL) / freq; res->tv_sec = 0; res->tv_nsec = static_cast(nsPerTick); return 0; } if (clockId == 14 || clockId == 15) { std::uint64_t nanos = 0; if (!CpuTimeResolutionNanos(&nanos)) return -1; res->tv_sec = static_cast(nanos / 1000000000ULL); res->tv_nsec = static_cast(nanos % 1000000000ULL); return 0; } throw std::runtime_error(std::string(__func__) + ": unsupported clock_id " + std::to_string(clockId)); #else clockid_t nativeId; switch (clockId) { case 0: case 9: nativeId = CLOCK_REALTIME; break; case 10: #ifdef CLOCK_REALTIME_COARSE nativeId = CLOCK_REALTIME_COARSE; #else nativeId = CLOCK_REALTIME; #endif break; case 4: case 7: case 11: nativeId = CLOCK_MONOTONIC; break; case 5: case 8: case 12: nativeId = CLOCK_MONOTONIC; break; case 14: nativeId = CLOCK_THREAD_CPUTIME_ID; break; case 15: nativeId = CLOCK_PROCESS_CPUTIME_ID; break; default: throw std::runtime_error(std::string(__func__) + ": unsupported clock_id " + std::to_string(clockId)); } struct timespec ts{}; if (clock_getres(nativeId, &ts) != 0) { return -1; } res->tv_sec = static_cast(ts.tv_sec); res->tv_nsec = static_cast(ts.tv_nsec); return 0; #endif } int APS5_VABI clock_settime_nid_postfix(int clockId, const KernelTimespec* tp) { if (tp == nullptr) { *__error_nid_postfix() = GUEST_EFAULT; return -1; } if (clockId != GUEST_CLOCK_REALTIME || tp->tv_nsec < 0 || tp->tv_nsec >= 1000000000 || tp->tv_sec < 0) { *__error_nid_postfix() = GUEST_EINVAL; return -1; } char requested[48]; std::snprintf(requested, sizeof(requested), "%lld.%09lld", static_cast(tp->tv_sec), static_cast(tp->tv_nsec)); throw std::runtime_error(std::string("clock_settime: setting CLOCK_REALTIME to ") + requested + " is not supported, the host clock is not changed"); } // --------------------------------------------------------------------------- // Moved as-is (not yet implemented) from the monolithic libkernel/Export.cpp. // --------------------------------------------------------------------------- static constexpr int SceKernelErrorEfault = static_cast(0x8002000e); struct HostZone { std::int64_t standardEast; std::int64_t daylight; }; static HostZone HostZoneAt(std::int64_t utcTime) { std::tm local{}; #ifdef _WIN32 constexpr std::int64_t cycleSeconds = 146097LL * 86400LL; constexpr std::int64_t lastHostSecond = 32535215999LL; const std::int64_t cycles = utcTime >= 0 && utcTime <= lastHostSecond ? 0 : (utcTime >= 0 ? utcTime / cycleSeconds : -((-(utcTime + 1)) / cycleSeconds + 1)); const std::time_t t = static_cast(utcTime - cycles * cycleSeconds); _tzset(); if (localtime_s(&local, &t) != 0) throw std::runtime_error(std::string(__func__) + ": no host local time for " + std::to_string(utcTime)); long standardBias = 0; long daylightBias = 0; _get_timezone(&standardBias); _get_dstbias(&daylightBias); return {-static_cast(standardBias), local.tm_isdst > 0 ? -static_cast(daylightBias) : 0}; #else const std::time_t t = static_cast(utcTime); ::tzset(); if (localtime_r(&t, &local) == nullptr) throw std::runtime_error(std::string(__func__) + ": no host local time for " + std::to_string(utcTime)); const std::int64_t daylight = local.tm_isdst > 0 ? static_cast(local.tm_gmtoff) + ::timezone : 0; return {static_cast(local.tm_gmtoff) - daylight, daylight}; #endif } static std::int64_t HostDaylightAmount(std::int64_t utcTime) { constexpr std::int64_t monthSeconds = 2629746; for (int month = 0; month <= 11; ++month) { for (const int direction : {1, -1}) { const HostZone zone = HostZoneAt(utcTime + direction * month * monthSeconds); if (zone.daylight != 0) return zone.daylight; } } return 0; } static KernelTimesec ZoneState(std::int64_t time, const HostZone& zone) { return {time, static_cast(zone.standardEast), static_cast(zone.daylight)}; } int APS5_VABI sceKernelClockGetres(KernelClockid clock_id, KernelTimespec* tp) { if (tp == nullptr) return SceKernelErrorEfault; if (clock_getres_nid_postfix(static_cast(clock_id), tp) != 0) return SceKernelError(*__error_nid_postfix()); return 0; } int APS5_VABI sceKernelClockGettime(KernelClockid clock_id, KernelTimespec* tp) { if (tp == nullptr) return SceKernelErrorEfault; if (clock_gettime_nid_postfix(static_cast(clock_id), tp) != 0) return SceKernelError(*__error_nid_postfix()); return 0; } int APS5_VABI sceKernelConvertLocaltimeToUtc(int64_t local_time, int64_t reserved, int64_t* utc_time, KernelTimesec* timezone, int32_t* dst_seconds) { (void)reserved; const HostZone guess = HostZoneAt(local_time); const HostZone zone = HostZoneAt(local_time - guess.standardEast - guess.daylight); if (utc_time != nullptr) *utc_time = local_time - zone.standardEast - zone.daylight; if (timezone != nullptr) *timezone = ZoneState(local_time, zone); if (dst_seconds != nullptr) *dst_seconds = static_cast(HostDaylightAmount(local_time - zone.standardEast - zone.daylight)); return 0; } int APS5_VABI sceKernelConvertUtcToLocaltime(int64_t utc_time, int64_t* local_time, KernelTimesec* st, int32_t* dst_sec) { const HostZone zone = HostZoneAt(utc_time); if (local_time != nullptr) *local_time = utc_time + zone.standardEast + zone.daylight; if (st != nullptr) *st = ZoneState(utc_time, zone); if (dst_sec != nullptr) *dst_sec = static_cast(zone.daylight); return 0; } int APS5_VABI sceKernelGettimeofday(KernelTimeval* tp) { if (tp == nullptr) return SceKernelErrorEfault; return gettimeofday_nid_postfix(tp, nullptr); } int APS5_VABI sceKernelGettimezone(KernelTimezone* tz) { if (tz == nullptr) return SceKernelErrorEfault; *tz = {0, 0}; return 0; } uint64_t APS5_VABI sceKernelReadTsc(void) { if (TimeScale() == 1.0) return __rdtsc(); static const std::uint64_t origin = __rdtsc(); return origin + static_cast(static_cast(__rdtsc() - origin) * TimeScale()); } uint64_t APS5_VABI sceKernelGetTscFrequency(void) { static const std::uint64_t frequency = [] { const std::uint64_t startNanos = RawMonotonicNanos(); const std::uint64_t startTicks = __rdtsc(); TimedWait::SleepNanos(20000000ULL); const std::uint64_t elapsedNanos = RawMonotonicNanos() - startNanos; const std::uint64_t elapsedTicks = __rdtsc() - startTicks; return static_cast(static_cast(elapsedTicks) * 1000000000.0L / static_cast(elapsedNanos)); }(); return frequency; } std::int64_t APS5_VABI sceKernelGetCpuFrequency(void) { return static_cast(sceKernelGetTscFrequency()); } unsigned int APS5_VABI sceKernelSleep(unsigned int seconds) { CancellableSleep(static_cast(seconds) * 1000000000ULL); return 0; } }