diff options
Diffstat (limited to 'src/VBox/Runtime/testcase/tstTSC.cpp')
-rw-r--r-- | src/VBox/Runtime/testcase/tstTSC.cpp | 461 |
1 files changed, 461 insertions, 0 deletions
diff --git a/src/VBox/Runtime/testcase/tstTSC.cpp b/src/VBox/Runtime/testcase/tstTSC.cpp new file mode 100644 index 00000000..c69dd103 --- /dev/null +++ b/src/VBox/Runtime/testcase/tstTSC.cpp @@ -0,0 +1,461 @@ +/* $Id: tstTSC.cpp $ */ +/** @file + * IPRT Testcase - SMP TSC testcase. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/asm-amd64-x86.h> +#include <iprt/asm.h> +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/mp.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/thread.h> +#include <iprt/time.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct TSCDATA +{ + /** The TSC. */ + uint64_t volatile TSC; + /** The APIC ID. */ + uint8_t volatile u8ApicId; + /** Did it succeed? */ + bool volatile fRead; + /** Did it fail? */ + bool volatile fFailed; + /** The thread handle. */ + RTTHREAD Thread; +} TSCDATA, *PTSCDATA; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The number of CPUs waiting on their user event semaphore. */ +static volatile uint32_t g_cWaiting; +/** The number of CPUs ready (in spin) to do the TSC read. */ +static volatile uint32_t g_cReady; +/** The variable the CPUs are spinning on. + * 0: Spin. + * 1: Go ahead. + * 2: You're too late, back to square one. */ +static volatile uint32_t g_u32Go; +/** The number of CPUs that managed to read the TSC. */ +static volatile uint32_t g_cRead; +/** The number of CPUs that failed to read the TSC. */ +static volatile uint32_t g_cFailed; + +/** Indicator forcing the threads to quit. */ +static volatile bool g_fDone; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static DECLCALLBACK(int) ThreadFunction(RTTHREAD Thread, void *pvUser); + + +/** + * Thread function for catching the other cpus. + * + * @returns VINF_SUCCESS (we don't care). + * @param Thread The thread handle. + * @param pvUser PTSCDATA. + */ +static DECLCALLBACK(int) ThreadFunction(RTTHREAD Thread, void *pvUser) +{ + PTSCDATA pTscData = (PTSCDATA)pvUser; + + while (!g_fDone) + { + /* + * Wait. + */ + ASMAtomicIncU32(&g_cWaiting); + RTThreadUserWait(Thread, RT_INDEFINITE_WAIT); + RTThreadUserReset(Thread); + ASMAtomicDecU32(&g_cWaiting); + if (g_fDone) + break; + + /* + * Spin. + */ + ASMAtomicIncU32(&g_cReady); + while (!g_fDone) + { + const uint8_t ApicId1 = ASMGetApicId(); + const uint64_t TSC1 = ASMReadTSC(); + const uint32_t u32Go = g_u32Go; + if (u32Go == 0) + continue; + + if (u32Go == 1) + { + /* do the reading. */ + const uint8_t ApicId2 = ASMGetApicId(); + const uint64_t TSC2 = ASMReadTSC(); + const uint8_t ApicId3 = ASMGetApicId(); + const uint64_t TSC3 = ASMReadTSC(); + const uint8_t ApicId4 = ASMGetApicId(); + + if ( ApicId1 == ApicId2 + && ApicId1 == ApicId3 + && ApicId1 == ApicId4 + && TSC3 - TSC1 < 2250 /* WARNING: This is just a guess, increase if it doesn't work for you. */ + && TSC2 - TSC1 < TSC3 - TSC1 + ) + { + /* succeeded. */ + pTscData->TSC = TSC2; + pTscData->u8ApicId = ApicId1; + pTscData->fFailed = false; + pTscData->fRead = true; + ASMAtomicIncU32(&g_cRead); + break; + } + } + + /* failed */ + pTscData->fFailed = true; + pTscData->fRead = false; + ASMAtomicIncU32(&g_cFailed); + break; + } + } + + return VINF_SUCCESS; +} + +static int tstTSCCalcDrift(void) +{ + /* + * This is only relevant to on SMP systems. + */ + const unsigned cCpus = RTMpGetOnlineCount(); + if (cCpus <= 1) + { + RTPrintf("tstTSC: SKIPPED - Only relevant on SMP systems\n"); + return 0; + } + + /* + * Create the threads. + */ + static TSCDATA s_aData[254]; + uint32_t i; + if (cCpus > RT_ELEMENTS(s_aData)) + { + RTPrintf("tstTSC: FAILED - too many CPUs (%u)\n", cCpus); + return 1; + } + + /* ourselves. */ + s_aData[0].Thread = RTThreadSelf(); + + /* the others */ + for (i = 1; i < cCpus; i++) + { + int rc = RTThreadCreate(&s_aData[i].Thread, ThreadFunction, &s_aData[i], 0, RTTHREADTYPE_TIMER, RTTHREADFLAGS_WAITABLE, "OTHERCPU"); + if (RT_FAILURE(rc)) + { + RTPrintf("tstTSC: FAILURE - RTThreatCreate failed when creating thread #%u, rc=%Rrc!\n", i, rc); + ASMAtomicXchgSize(&g_fDone, true); + while (i-- > 1) + { + RTThreadUserSignal(s_aData[i].Thread); + RTThreadWait(s_aData[i].Thread, 5000, NULL); + } + return 1; + } + } + + /* + * Retry until we get lucky (or give up). + */ + for (unsigned cTries = 0; ; cTries++) + { + if (cTries > 10240) + { + RTPrintf("tstTSC: FAILURE - %d attempts, giving.\n", cTries); + break; + } + + /* + * Wait for the other threads to get ready (brute force active wait, I'm lazy). + */ + i = 0; + while (g_cWaiting < cCpus - 1) + { + if (i++ > _2G32) + break; + RTThreadSleep(i & 0xf); + } + if (g_cWaiting != cCpus - 1) + { + RTPrintf("tstTSC: FAILURE - threads failed to get waiting (%d != %d (i=%d))\n", g_cWaiting + 1, cCpus, i); + break; + } + + /* + * Send them spinning. + */ + ASMAtomicXchgU32(&g_cReady, 0); + ASMAtomicXchgU32(&g_u32Go, 0); + ASMAtomicXchgU32(&g_cRead, 0); + ASMAtomicXchgU32(&g_cFailed, 0); + for (i = 1; i < cCpus; i++) + { + ASMAtomicXchgSize(&s_aData[i].fFailed, false); + ASMAtomicXchgSize(&s_aData[i].fRead, false); + ASMAtomicXchgU8(&s_aData[i].u8ApicId, 0xff); + + int rc = RTThreadUserSignal(s_aData[i].Thread); + if (RT_FAILURE(rc)) + RTPrintf("tstTSC: WARNING - RTThreadUserSignal(%#u) -> rc=%Rrc!\n", i, rc); + } + + /* wait for them to get ready. */ + i = 0; + while (g_cReady < cCpus - 1) + { + if (i++ > _2G32) + break; + } + if (g_cReady != cCpus - 1) + { + RTPrintf("tstTSC: FAILURE - threads failed to get ready (%d != %d, i=%d)\n", g_cWaiting + 1, cCpus, i); + break; + } + + /* + * Flip the "go" switch and do our readings. + * We give the other threads the slack it takes to two extra TSC and APIC ID reads. + */ + const uint8_t ApicId1 = ASMGetApicId(); + const uint64_t TSC1 = ASMReadTSC(); + ASMAtomicXchgU32(&g_u32Go, 1); + const uint8_t ApicId2 = ASMGetApicId(); + const uint64_t TSC2 = ASMReadTSC(); + const uint8_t ApicId3 = ASMGetApicId(); + const uint64_t TSC3 = ASMReadTSC(); + const uint8_t ApicId4 = ASMGetApicId(); + const uint64_t TSC4 = ASMReadTSC(); + ASMAtomicXchgU32(&g_u32Go, 2); + const uint8_t ApicId5 = ASMGetApicId(); + const uint64_t TSC5 = ASMReadTSC(); + const uint8_t ApicId6 = ASMGetApicId(); + + /* Compose our own result. */ + if ( ApicId1 == ApicId2 + && ApicId1 == ApicId3 + && ApicId1 == ApicId4 + && ApicId1 == ApicId5 + && ApicId1 == ApicId6 + && TSC5 - TSC1 < 2750 /* WARNING: This is just a guess, increase if it doesn't work for you. */ + && TSC4 - TSC1 < TSC5 - TSC1 + && TSC3 - TSC1 < TSC4 - TSC1 + && TSC2 - TSC1 < TSC3 - TSC1 + ) + { + /* succeeded. */ + s_aData[0].TSC = TSC2; + s_aData[0].u8ApicId = ApicId1; + s_aData[0].fFailed = false; + s_aData[0].fRead = true; + ASMAtomicIncU32(&g_cRead); + } + else + { + /* failed */ + s_aData[0].fFailed = true; + s_aData[0].fRead = false; + ASMAtomicIncU32(&g_cFailed); + } + + /* + * Wait a little while to let the other ones to finish. + */ + i = 0; + while (g_cRead + g_cFailed < cCpus) + { + if (i++ > _2G32) + break; + if (i > _1M) + RTThreadSleep(i & 0xf); + } + if (g_cRead + g_cFailed != cCpus) + { + RTPrintf("tstTSC: FAILURE - threads failed to complete reading (%d + %d != %d)\n", g_cRead, g_cFailed, cCpus); + break; + } + + /* + * If everone succeeded, print the results. + */ + if (!g_cFailed) + { + /* sort it by apic id first. */ + bool fDone; + do + { + for (i = 1, fDone = true; i < cCpus; i++) + if (s_aData[i - 1].u8ApicId > s_aData[i].u8ApicId) + { + TSCDATA Tmp = s_aData[i - 1]; + s_aData[i - 1] = s_aData[i]; + s_aData[i] = Tmp; + fDone = false; + } + } while (!fDone); + + RTPrintf(" # ID TSC delta0 (decimal)\n" + "-----------------------------------------\n"); + RTPrintf("%2d %02x %RX64\n", 0, s_aData[0].u8ApicId, s_aData[0].TSC); + for (i = 1; i < cCpus; i++) + RTPrintf("%2d %02x %RX64 %s%lld\n", i, s_aData[i].u8ApicId, s_aData[i].TSC, + s_aData[i].TSC > s_aData[0].TSC ? "+" : "", s_aData[i].TSC - s_aData[0].TSC); + RTPrintf("(Needed %u attempt%s.)\n", cTries + 1, cTries ? "s" : ""); + break; + } + } + + /* + * Destroy the threads. + */ + ASMAtomicXchgSize(&g_fDone, true); + for (i = 0; i < cCpus; i++) + if (s_aData[i].Thread != RTThreadSelf()) + { + int rc = RTThreadUserSignal(s_aData[i].Thread); + if (RT_FAILURE(rc)) + RTPrintf("tstTSC: WARNING - RTThreadUserSignal(%#u) -> rc=%Rrc! (2)\n", i, rc); + } + for (i = 0; i < cCpus; i++) + if (s_aData[i].Thread != RTThreadSelf()) + { + int rc = RTThreadWait(s_aData[i].Thread, 5000, NULL); + if (RT_FAILURE(rc)) + RTPrintf("tstTSC: WARNING - RTThreadWait(%#u) -> rc=%Rrc!\n", i, rc); + } + + return g_cFailed != 0 || g_cRead != cCpus; +} + + +static int tstTSCCalcFrequency(uint32_t cMsDuration) +{ + /* + * Sample the TSC and time, sleep the requested time and calc the deltas. + */ + uint64_t uNanoTS = RTTimeSystemNanoTS(); + uint64_t uTSC = ASMReadTSC(); + RTThreadSleep(cMsDuration); + uNanoTS = RTTimeSystemNanoTS() - uNanoTS; + uTSC = ASMReadTSC() - uTSC; + + /* + * Calc the frequency. + */ + RTPrintf("tstTSC: %RU64 ticks in %RU64 ns\n", uTSC, uNanoTS); + uint64_t cHz = (uint64_t)((long double)uTSC / ((long double)uNanoTS / (long double)1000000000)); + RTPrintf("tstTSC: Frequency %RU64 Hz", cHz); + if (cHz > _1G) + { + cHz += _1G / 20; + RTPrintf(" %RU64.%RU64 GHz", cHz / _1G, (cHz % _1G) / (_1G / 10)); + } + else if (cHz > _1M) + { + cHz += _1M / 20; + RTPrintf(" %RU64.%RU64 MHz", cHz / _1M, (cHz % _1M) / (_1M / 10)); + } + RTPrintf("\n"); + return 0; +} + + +int main(int argc, char **argv) +{ + RTR3InitExe(argc, &argv, 0); + + /* + * Parse arguments. + */ + bool fCalcFrequency = false; + uint32_t cMsDuration = 1000; /* 1 sec */ + static const RTGETOPTDEF s_aOptions[] = + { + { "--duration", 'd', RTGETOPT_REQ_UINT32 }, + { "--calc-frequency", 'f', RTGETOPT_REQ_NOTHING }, + }; + int ch; + RTGETOPTUNION Value; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0 /* fFlags */); + while ((ch = RTGetOpt(&GetState, &Value))) + switch (ch) + { + case 'd': cMsDuration = Value.u32; + break; + + case 'f': fCalcFrequency = true; + break; + + case 'h': + RTPrintf("usage: tstTSC\n" + " or: tstTSC <-f|--calc-frequency> [--duration|-d ms]\n"); + return 1; + + case 'V': + RTPrintf("$Revision: 155244 $\n"); + return 0; + + default: + return RTGetOptPrintError(ch, &Value); + } + + if (fCalcFrequency) + return tstTSCCalcFrequency(cMsDuration); + return tstTSCCalcDrift(); +} + |