VirtualBox

source: vbox/trunk/src/VBox/Runtime/testcase/tstTSC.cpp

Last change on this file was 106929, checked in by vboxsync, 3 weeks ago

IPRT/tstTSC: Nor is RTMpCpuId available on solaris. jiraref:VBP-1449

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 16.0 KB
Line 
1/* $Id: tstTSC.cpp 106929 2024-11-11 15:19:28Z vboxsync $ */
2/** @file
3 * IPRT Testcase - SMP TSC testcase.
4 */
5
6/*
7 * Copyright (C) 2006-2024 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * The contents of this file may alternatively be used under the terms
26 * of the Common Development and Distribution License Version 1.0
27 * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
28 * in the VirtualBox distribution, in which case the provisions of the
29 * CDDL are applicable instead of those of the GPL.
30 *
31 * You may elect to license modified versions of this file under the
32 * terms and conditions of either the GPL or the CDDL or both.
33 *
34 * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
35 */
36
37
38/*********************************************************************************************************************************
39* Header Files *
40*********************************************************************************************************************************/
41#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86)
42# include <iprt/asm-amd64-x86.h>
43#elif defined(RT_ARCH_ARM64)
44# include <iprt/asm-arm.h>
45#endif
46#include <iprt/asm.h>
47#include <iprt/getopt.h>
48#include <iprt/initterm.h>
49#include <iprt/mp.h>
50#include <iprt/stream.h>
51#include <iprt/string.h>
52#include <iprt/thread.h>
53#include <iprt/time.h>
54
55
56/*********************************************************************************************************************************
57* Defined Constants And Macros *
58*********************************************************************************************************************************/
59/** @todo this depends on TSC frequency, which is not necessarily related
60 * to the CPU speed on arm. */
61#define MAX_TSC_DELTA 2750 /* WARNING: This is just a guess, increase if it doesn't work for you. */
62
63
64/*********************************************************************************************************************************
65* Structures and Typedefs *
66*********************************************************************************************************************************/
67typedef struct TSCDATA
68{
69 /** The TSC. */
70 uint64_t volatile TSC;
71 /** The CPU ID or APIC ID. */
72 RTCPUID volatile idCpu;
73 /** Did it succeed? */
74 bool volatile fRead;
75 /** Did it fail? */
76 bool volatile fFailed;
77 /** The thread handle. */
78 RTTHREAD Thread;
79} TSCDATA, *PTSCDATA;
80
81
82/*********************************************************************************************************************************
83* Global Variables *
84*********************************************************************************************************************************/
85/** The number of CPUs waiting on their user event semaphore. */
86static volatile uint32_t g_cWaiting;
87/** The number of CPUs ready (in spin) to do the TSC read. */
88static volatile uint32_t g_cReady;
89/** The variable the CPUs are spinning on.
90 * 0: Spin.
91 * 1: Go ahead.
92 * 2: You're too late, back to square one. */
93static volatile uint32_t g_u32Go;
94/** The number of CPUs that managed to read the TSC. */
95static volatile uint32_t g_cRead;
96/** The number of CPUs that failed to read the TSC. */
97static volatile uint32_t g_cFailed;
98
99/** Indicator forcing the threads to quit. */
100static volatile bool g_fDone;
101
102
103/*********************************************************************************************************************************
104* Internal Functions *
105*********************************************************************************************************************************/
106static DECLCALLBACK(int) ThreadFunction(RTTHREAD Thread, void *pvUser);
107
108
109/** Wrapper around RTMpCpuId/ASMGetStuff. */
110static RTCPUID MyGetCpuId(void)
111{
112#if (!defined(RT_OS_LINUX) && !defined(RT_OS_DARWIN) && !defined(RT_OS_SOLARIS)) || defined(RT_ARCH_ARM64) || defined(RT_ARCH_ARM32)
113 RTCPUID idCpu = RTMpCpuId();
114 if (idCpu != NIL_RTCPUID)
115 return idCpu;
116#endif
117#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86)
118 return ASMGetApicId();
119#elif defined(RT_ARCH_ARM64)
120 return (RTCPUID)ASMGetThreadIdRoEL0();
121#else
122 return idCpu;
123#endif
124}
125
126/**
127 * Thread function for catching the other cpus.
128 *
129 * @returns VINF_SUCCESS (we don't care).
130 * @param Thread The thread handle.
131 * @param pvUser PTSCDATA.
132 */
133static DECLCALLBACK(int) ThreadFunction(RTTHREAD Thread, void *pvUser)
134{
135 PTSCDATA pTscData = (PTSCDATA)pvUser;
136
137 while (!g_fDone)
138 {
139 /*
140 * Wait.
141 */
142 ASMAtomicIncU32(&g_cWaiting);
143 RTThreadUserWait(Thread, RT_INDEFINITE_WAIT);
144 RTThreadUserReset(Thread);
145 ASMAtomicDecU32(&g_cWaiting);
146 if (g_fDone)
147 break;
148
149 /*
150 * Spin.
151 */
152 ASMAtomicIncU32(&g_cReady);
153 while (!g_fDone)
154 {
155 const RTCPUID idCpu1 = MyGetCpuId();
156 const uint64_t TSC1 = ASMReadTSC();
157 const uint32_t u32Go = g_u32Go;
158 if (u32Go == 0)
159 continue;
160
161 if (u32Go == 1)
162 {
163 /* do the reading. */
164 const RTCPUID idCpu2 = MyGetCpuId();
165 const uint64_t TSC2 = ASMReadTSC();
166 const RTCPUID idCpu3 = MyGetCpuId();
167 const uint64_t TSC3 = ASMReadTSC();
168 const uint8_t idCpu4 = MyGetCpuId();
169
170 if ( idCpu1 == idCpu2
171 && idCpu1 == idCpu3
172 && idCpu1 == idCpu4
173 && TSC3 - TSC1 < MAX_TSC_DELTA
174 && TSC2 - TSC1 < TSC3 - TSC1
175 )
176 {
177 /* succeeded. */
178 pTscData->TSC = TSC2;
179 pTscData->idCpu = idCpu1;
180 pTscData->fFailed = false;
181 pTscData->fRead = true;
182 ASMAtomicIncU32(&g_cRead);
183 break;
184 }
185 }
186
187 /* failed */
188 pTscData->fFailed = true;
189 pTscData->fRead = false;
190 ASMAtomicIncU32(&g_cFailed);
191 break;
192 }
193 }
194
195 return VINF_SUCCESS;
196}
197
198static int tstTSCCalcDrift(void)
199{
200 /*
201 * This is only relevant to on SMP systems.
202 */
203 const unsigned cCpus = RTMpGetOnlineCount();
204 if (cCpus <= 1)
205 {
206 RTPrintf("tstTSC: SKIPPED - Only relevant on SMP systems\n");
207 return 0;
208 }
209
210 /*
211 * Create the threads.
212 */
213 static TSCDATA s_aData[254];
214 uint32_t i;
215 if (cCpus > RT_ELEMENTS(s_aData))
216 {
217 RTPrintf("tstTSC: FAILED - too many CPUs (%u)\n", cCpus);
218 return 1;
219 }
220
221 /* ourselves. */
222 s_aData[0].Thread = RTThreadSelf();
223
224 /* the others */
225 for (i = 1; i < cCpus; i++)
226 {
227 int rc = RTThreadCreate(&s_aData[i].Thread, ThreadFunction, &s_aData[i], 0, RTTHREADTYPE_TIMER, RTTHREADFLAGS_WAITABLE, "OTHERCPU");
228 if (RT_FAILURE(rc))
229 {
230 RTPrintf("tstTSC: FAILURE - RTThreatCreate failed when creating thread #%u, rc=%Rrc!\n", i, rc);
231 ASMAtomicXchgSize(&g_fDone, true);
232 while (i-- > 1)
233 {
234 RTThreadUserSignal(s_aData[i].Thread);
235 RTThreadWait(s_aData[i].Thread, 5000, NULL);
236 }
237 return 1;
238 }
239 }
240
241 /*
242 * Retry until we get lucky (or give up).
243 */
244 for (unsigned cTries = 0; ; cTries++)
245 {
246 if (cTries > 10240)
247 {
248 RTPrintf("tstTSC: FAILURE - %d attempts, giving.\n", cTries);
249 break;
250 }
251
252 /*
253 * Wait for the other threads to get ready (brute force active wait, I'm lazy).
254 */
255 i = 0;
256 while (g_cWaiting < cCpus - 1)
257 {
258 if (i++ > _2G32)
259 break;
260 RTThreadSleep(i & 0xf);
261 }
262 if (g_cWaiting != cCpus - 1)
263 {
264 RTPrintf("tstTSC: FAILURE - threads failed to get waiting (%d != %d (i=%d))\n", g_cWaiting + 1, cCpus, i);
265 break;
266 }
267
268 /*
269 * Send them spinning.
270 */
271 ASMAtomicXchgU32(&g_cReady, 0);
272 ASMAtomicXchgU32(&g_u32Go, 0);
273 ASMAtomicXchgU32(&g_cRead, 0);
274 ASMAtomicXchgU32(&g_cFailed, 0);
275 for (i = 1; i < cCpus; i++)
276 {
277 ASMAtomicXchgSize(&s_aData[i].fFailed, false);
278 ASMAtomicXchgSize(&s_aData[i].fRead, false);
279 ASMAtomicXchgU32(&s_aData[i].idCpu, NIL_RTCPUID);
280
281 int rc = RTThreadUserSignal(s_aData[i].Thread);
282 if (RT_FAILURE(rc))
283 RTPrintf("tstTSC: WARNING - RTThreadUserSignal(%#u) -> rc=%Rrc!\n", i, rc);
284 }
285
286 /* wait for them to get ready. */
287 i = 0;
288 while (g_cReady < cCpus - 1)
289 {
290 if (i++ > _2G32)
291 break;
292 }
293 if (g_cReady != cCpus - 1)
294 {
295 RTPrintf("tstTSC: FAILURE - threads failed to get ready (%d != %d, i=%d)\n", g_cWaiting + 1, cCpus, i);
296 break;
297 }
298
299 /*
300 * Flip the "go" switch and do our readings.
301 * We give the other threads the slack it takes to two extra TSC and CPU ID reads.
302 */
303 const RTCPUID idCpu1 = MyGetCpuId();
304 const uint64_t TSC1 = ASMReadTSC();
305 ASMAtomicXchgU32(&g_u32Go, 1);
306 const RTCPUID idCpu2 = MyGetCpuId();
307 const uint64_t TSC2 = ASMReadTSC();
308 const RTCPUID idCpu3 = MyGetCpuId();
309 const uint64_t TSC3 = ASMReadTSC();
310 const RTCPUID idCpu4 = MyGetCpuId();
311 const uint64_t TSC4 = ASMReadTSC();
312 ASMAtomicXchgU32(&g_u32Go, 2);
313 const RTCPUID idCpu5 = MyGetCpuId();
314 const uint64_t TSC5 = ASMReadTSC();
315 const RTCPUID idCpu6 = MyGetCpuId();
316
317 /* Compose our own result. */
318 if ( idCpu1 == idCpu2
319 && idCpu1 == idCpu3
320 && idCpu1 == idCpu4
321 && idCpu1 == idCpu5
322 && idCpu1 == idCpu6
323 && TSC5 - TSC1 < MAX_TSC_DELTA
324 && TSC4 - TSC1 < TSC5 - TSC1
325 && TSC3 - TSC1 < TSC4 - TSC1
326 && TSC2 - TSC1 < TSC3 - TSC1
327 )
328 {
329 /* succeeded. */
330 s_aData[0].TSC = TSC2;
331 s_aData[0].idCpu = idCpu1;
332 s_aData[0].fFailed = false;
333 s_aData[0].fRead = true;
334 ASMAtomicIncU32(&g_cRead);
335 }
336 else
337 {
338 /* failed */
339 s_aData[0].fFailed = true;
340 s_aData[0].fRead = false;
341 ASMAtomicIncU32(&g_cFailed);
342 }
343
344 /*
345 * Wait a little while to let the other ones to finish.
346 */
347 i = 0;
348 while (g_cRead + g_cFailed < cCpus)
349 {
350 if (i++ > _2G32)
351 break;
352 if (i > _1M)
353 RTThreadSleep(i & 0xf);
354 }
355 if (g_cRead + g_cFailed != cCpus)
356 {
357 RTPrintf("tstTSC: FAILURE - threads failed to complete reading (%d + %d != %d)\n", g_cRead, g_cFailed, cCpus);
358 break;
359 }
360
361 /*
362 * If everone succeeded, print the results.
363 */
364 if (!g_cFailed)
365 {
366 /* sort it by apic id first. */
367 bool fDone;
368 do
369 {
370 for (i = 1, fDone = true; i < cCpus; i++)
371 if (s_aData[i - 1].idCpu > s_aData[i].idCpu)
372 {
373 TSCDATA Tmp = s_aData[i - 1];
374 s_aData[i - 1] = s_aData[i];
375 s_aData[i] = Tmp;
376 fDone = false;
377 }
378 } while (!fDone);
379
380 RTPrintf(" # ID TSC delta0 (decimal)\n"
381 "-----------------------------------------\n");
382 RTPrintf("%2d %02x %RX64\n", 0, s_aData[0].idCpu, s_aData[0].TSC);
383 for (i = 1; i < cCpus; i++)
384 RTPrintf("%2d %02x %RX64 %s%lld\n", i, s_aData[i].idCpu, s_aData[i].TSC,
385 s_aData[i].TSC > s_aData[0].TSC ? "+" : "", s_aData[i].TSC - s_aData[0].TSC);
386 RTPrintf("(Needed %u attempt%s.)\n", cTries + 1, cTries ? "s" : "");
387 break;
388 }
389 }
390
391 /*
392 * Destroy the threads.
393 */
394 ASMAtomicXchgSize(&g_fDone, true);
395 for (i = 0; i < cCpus; i++)
396 if (s_aData[i].Thread != RTThreadSelf())
397 {
398 int rc = RTThreadUserSignal(s_aData[i].Thread);
399 if (RT_FAILURE(rc))
400 RTPrintf("tstTSC: WARNING - RTThreadUserSignal(%#u) -> rc=%Rrc! (2)\n", i, rc);
401 }
402 for (i = 0; i < cCpus; i++)
403 if (s_aData[i].Thread != RTThreadSelf())
404 {
405 int rc = RTThreadWait(s_aData[i].Thread, 5000, NULL);
406 if (RT_FAILURE(rc))
407 RTPrintf("tstTSC: WARNING - RTThreadWait(%#u) -> rc=%Rrc!\n", i, rc);
408 }
409
410 return g_cFailed != 0 || g_cRead != cCpus;
411}
412
413
414static int tstTSCCalcFrequency(uint32_t cMsDuration)
415{
416 /*
417 * Sample the TSC and time, sleep the requested time and calc the deltas.
418 */
419 uint64_t uNanoTS = RTTimeSystemNanoTS();
420 uint64_t uTSC = ASMReadTSC();
421 RTThreadSleep(cMsDuration);
422 uNanoTS = RTTimeSystemNanoTS() - uNanoTS;
423 uTSC = ASMReadTSC() - uTSC;
424
425 /*
426 * Calc the frequency.
427 */
428 RTPrintf("tstTSC: %RU64 ticks in %RU64 ns\n", uTSC, uNanoTS);
429 uint64_t cHz = (uint64_t)((long double)uTSC / ((long double)uNanoTS / (long double)1000000000));
430 RTPrintf("tstTSC: Frequency %RU64 Hz", cHz);
431 if (cHz > _1G)
432 {
433 cHz += _1G / 20;
434 RTPrintf(" %RU64.%RU64 GHz", cHz / _1G, (cHz % _1G) / (_1G / 10));
435 }
436 else if (cHz > _1M)
437 {
438 cHz += _1M / 20;
439 RTPrintf(" %RU64.%RU64 MHz", cHz / _1M, (cHz % _1M) / (_1M / 10));
440 }
441 RTPrintf("\n");
442 return 0;
443}
444
445
446int main(int argc, char **argv)
447{
448 RTR3InitExe(argc, &argv, 0);
449
450 /*
451 * Parse arguments.
452 */
453 bool fCalcFrequency = false;
454 uint32_t cMsDuration = 1000; /* 1 sec */
455 static const RTGETOPTDEF s_aOptions[] =
456 {
457 { "--duration", 'd', RTGETOPT_REQ_UINT32 },
458 { "--calc-frequency", 'f', RTGETOPT_REQ_NOTHING },
459 };
460 int ch;
461 RTGETOPTUNION Value;
462 RTGETOPTSTATE GetState;
463 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0 /* fFlags */);
464 while ((ch = RTGetOpt(&GetState, &Value)))
465 switch (ch)
466 {
467 case 'd': cMsDuration = Value.u32;
468 break;
469
470 case 'f': fCalcFrequency = true;
471 break;
472
473 case 'h':
474 RTPrintf("usage: tstTSC\n"
475 " or: tstTSC <-f|--calc-frequency> [--duration|-d ms]\n");
476 return 1;
477
478 case 'V':
479 RTPrintf("$Revision: 106929 $\n");
480 return 0;
481
482 default:
483 return RTGetOptPrintError(ch, &Value);
484 }
485
486 if (fCalcFrequency)
487 return tstTSCCalcFrequency(cMsDuration);
488 return tstTSCCalcDrift();
489}
490
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette