mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-22 19:06:15 +08:00
Fix sceKernelGetTscFrequency disagreeing with the counter on non-Windows hosts (#213)
sceKernelReadTsc only returns the CPU's RDTSC when the host RDTSC reader is available (currently 64-bit Windows); on Linux and macOS it falls back to the QPC-based Stopwatch. ResolveKernelTscFrequency, however, still consulted the CPUID-reported hardware TSC frequency in that case, so sceKernelGetTscFrequency reported a multi-GHz rate while ReadTsc was ticking at the Stopwatch frequency. A guest computing elapsed = readTscDelta / frequency then gets the wrong time on those platforms. Gate the calibrated/CPUID frequencies on RDTSC actually being available (the calibration path was already self-gated; the CPUID path was not) and otherwise report the Stopwatch frequency, keeping ReadTsc and GetTscFrequency consistent. The selection logic is extracted into a pure, host-independent helper so both branches can be unit tested, including a regression test asserting that a host without RDTSC reports the Stopwatch frequency rather than the hardware TSC.
This commit is contained in:
@@ -1717,41 +1717,59 @@ public static class KernelRuntimeCompatExports
|
||||
}
|
||||
|
||||
private static ulong ResolveKernelTscFrequency()
|
||||
{
|
||||
var (frequencyHz, source) = SelectKernelTscFrequency(
|
||||
_rdtscReader is not null,
|
||||
Environment.GetEnvironmentVariable("SHARPEMU_TSC_FREQ_HZ"),
|
||||
TryCalibrateHostTscFrequency,
|
||||
TryResolveCpuidTscFrequency,
|
||||
Stopwatch.Frequency);
|
||||
TraceKernelTscFrequency(source, frequencyHz);
|
||||
return frequencyHz;
|
||||
}
|
||||
|
||||
internal delegate bool TryGetFrequency(out ulong frequencyHz);
|
||||
|
||||
// sceKernelReadTsc only returns the CPU's RDTSC when the host RDTSC reader is available
|
||||
// (currently 64-bit Windows); otherwise it falls back to the QPC-based Stopwatch. The
|
||||
// calibrated and CPUID frequencies both describe RDTSC, so reporting either while ReadTsc is
|
||||
// actually returning Stopwatch ticks makes sceKernelGetTscFrequency disagree with the counter,
|
||||
// and a guest computing elapsed = readTscDelta / frequency gets the wrong time on Linux and
|
||||
// macOS. Gate those on rdtscAvailable; otherwise report Stopwatch's own frequency so the pair
|
||||
// stays consistent.
|
||||
internal static (ulong FrequencyHz, string Source) SelectKernelTscFrequency(
|
||||
bool rdtscAvailable,
|
||||
string? overrideHzText,
|
||||
TryGetFrequency tryCalibrate,
|
||||
TryGetFrequency tryResolveCpuid,
|
||||
long stopwatchFrequency)
|
||||
{
|
||||
const ulong minSane = 1_000_000UL;
|
||||
|
||||
var overrideHzText = Environment.GetEnvironmentVariable("SHARPEMU_TSC_FREQ_HZ");
|
||||
if (!string.IsNullOrWhiteSpace(overrideHzText) &&
|
||||
ulong.TryParse(overrideHzText, out var overrideHz) &&
|
||||
overrideHz >= minSane)
|
||||
{
|
||||
TraceKernelTscFrequency("env", overrideHz);
|
||||
return overrideHz;
|
||||
return (overrideHz, "env");
|
||||
}
|
||||
|
||||
if (TryCalibrateHostTscFrequency(out ulong calibratedHz) && calibratedHz >= minSane)
|
||||
if (rdtscAvailable)
|
||||
{
|
||||
TraceKernelTscFrequency("calibrated-rdtsc", calibratedHz);
|
||||
return calibratedHz;
|
||||
if (tryCalibrate(out ulong calibratedHz) && calibratedHz >= minSane)
|
||||
{
|
||||
return (calibratedHz, "calibrated-rdtsc");
|
||||
}
|
||||
|
||||
if (tryResolveCpuid(out ulong cpuidHz) && cpuidHz >= minSane)
|
||||
{
|
||||
return (cpuidHz, "cpuid");
|
||||
}
|
||||
}
|
||||
|
||||
if (TryResolveCpuidTscFrequency(out ulong cpuidHz) && cpuidHz >= minSane)
|
||||
{
|
||||
TraceKernelTscFrequency("cpuid", cpuidHz);
|
||||
return cpuidHz;
|
||||
}
|
||||
|
||||
var hostQpc = Stopwatch.Frequency > 0
|
||||
? unchecked((ulong)Stopwatch.Frequency)
|
||||
var hostQpc = stopwatchFrequency > 0
|
||||
? unchecked((ulong)stopwatchFrequency)
|
||||
: DefaultKernelTscFrequency;
|
||||
if (hostQpc >= minSane)
|
||||
{
|
||||
TraceKernelTscFrequency("qpc", hostQpc);
|
||||
return hostQpc;
|
||||
}
|
||||
|
||||
TraceKernelTscFrequency("default", DefaultKernelTscFrequency);
|
||||
return DefaultKernelTscFrequency;
|
||||
return hostQpc >= minSane ? (hostQpc, "qpc") : (DefaultKernelTscFrequency, "default");
|
||||
}
|
||||
|
||||
private static void TraceKernelTscFrequency(string source, ulong frequencyHz)
|
||||
|
||||
Reference in New Issue
Block a user