// Copyright (C) 2026 SharpEmu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later using SharpEmu.Libs.Kernel; using Xunit; namespace SharpEmu.Libs.Tests.Kernel; // sceKernelGetTscFrequency must describe the same clock that sceKernelReadTsc returns. ReadTsc // only returns the CPU's RDTSC when the host RDTSC reader is available (64-bit Windows) and // otherwise falls back to the QPC-based Stopwatch, so the frequency selection has to follow suit. public sealed class KernelRuntimeCompatExportsTests { private static KernelRuntimeCompatExports.TryGetFrequency Yields(ulong hz) => (out ulong frequencyHz) => { frequencyHz = hz; return true; }; private static readonly KernelRuntimeCompatExports.TryGetFrequency Fails = (out ulong frequencyHz) => { frequencyHz = 0; return false; }; [Fact] public void WithoutHostRdtsc_ReportsStopwatchFrequency_NotHardwareTsc() { // Regression: on Linux/macOS ReadTsc returns the Stopwatch counter, so the reported // frequency must be the Stopwatch's, never the CPU's much larger hardware TSC frequency. var (frequencyHz, source) = KernelRuntimeCompatExports.SelectKernelTscFrequency( rdtscAvailable: false, overrideHzText: null, tryCalibrate: Yields(2_400_000_000UL), tryResolveCpuid: Yields(3_000_000_000UL), stopwatchFrequency: 10_000_000); Assert.Equal(10_000_000UL, frequencyHz); Assert.Equal("qpc", source); } [Fact] public void WithHostRdtsc_PrefersCalibratedFrequency() { var (frequencyHz, source) = KernelRuntimeCompatExports.SelectKernelTscFrequency( rdtscAvailable: true, overrideHzText: null, tryCalibrate: Yields(2_400_000_000UL), tryResolveCpuid: Yields(3_000_000_000UL), stopwatchFrequency: 10_000_000); Assert.Equal(2_400_000_000UL, frequencyHz); Assert.Equal("calibrated-rdtsc", source); } [Fact] public void WithHostRdtsc_FallsBackToCpuid_WhenCalibrationFails() { var (frequencyHz, source) = KernelRuntimeCompatExports.SelectKernelTscFrequency( rdtscAvailable: true, overrideHzText: null, tryCalibrate: Fails, tryResolveCpuid: Yields(3_000_000_000UL), stopwatchFrequency: 10_000_000); Assert.Equal(3_000_000_000UL, frequencyHz); Assert.Equal("cpuid", source); } [Fact] public void WithHostRdtsc_UsesStopwatch_WhenRdtscFrequencyUnknown() { var (frequencyHz, source) = KernelRuntimeCompatExports.SelectKernelTscFrequency( rdtscAvailable: true, overrideHzText: null, tryCalibrate: Fails, tryResolveCpuid: Fails, stopwatchFrequency: 10_000_000); Assert.Equal(10_000_000UL, frequencyHz); Assert.Equal("qpc", source); } [Theory] [InlineData(true)] [InlineData(false)] public void EnvOverride_Wins_WhenSane(bool rdtscAvailable) { var (frequencyHz, source) = KernelRuntimeCompatExports.SelectKernelTscFrequency( rdtscAvailable, overrideHzText: "1500000000", tryCalibrate: Yields(2_400_000_000UL), tryResolveCpuid: Yields(3_000_000_000UL), stopwatchFrequency: 10_000_000); Assert.Equal(1_500_000_000UL, frequencyHz); Assert.Equal("env", source); } [Fact] public void EnvOverride_BelowMinimum_IsIgnored() { // 500 kHz is below the sanity floor, so it is dropped; with rdtsc unavailable the // hardware-TSC path is gated off and the Stopwatch frequency is used. var (frequencyHz, _) = KernelRuntimeCompatExports.SelectKernelTscFrequency( rdtscAvailable: false, overrideHzText: "500000", tryCalibrate: Fails, tryResolveCpuid: Yields(3_000_000_000UL), stopwatchFrequency: 10_000_000); Assert.Equal(10_000_000UL, frequencyHz); } [Fact] public void NonPositiveStopwatchFrequency_FallsBackToDefault() { var (frequencyHz, source) = KernelRuntimeCompatExports.SelectKernelTscFrequency( rdtscAvailable: false, overrideHzText: null, tryCalibrate: Fails, tryResolveCpuid: Fails, stopwatchFrequency: 0); Assert.Equal(10_000_000UL, frequencyHz); // DefaultKernelTscFrequency Assert.Equal("qpc", source); } }