VirtualBox

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

Last change on this file since 106924 was 106924, checked in by vboxsync, 3 months ago

IPRT/testcase: Made the R0 modules build on win.arm64. jiraref:VBP-1449

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 15.8 KB
Line 
1/* $Id: tstTSC.cpp 106924 2024-11-11 11:57:12Z 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 RTCPUID idCpu = RTMpCpuId();
113 if (idCpu != NIL_RTCPUID)
114 return idCpu;
115#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86)
116 return ASMGetApicId();
117#elif defined(RT_ARCH_ARM64)
118 return (RTCPUID)ASMGetThreadIdRoEL0();
119#else
120 return idCpu;
121#endif
122}
123
124/**
125 * Thread function for catching the other cpus.
126 *
127 * @returns VINF_SUCCESS (we don't care).
128 * @param Thread The thread handle.
129 * @param pvUser PTSCDATA.
130 */
131static DECLCALLBACK(int) ThreadFunction(RTTHREAD Thread, void *pvUser)
132{
133 PTSCDATA pTscData = (PTSCDATA)pvUser;
134
135 while (!g_fDone)
136 {
137 /*
138 * Wait.
139 */
140 ASMAtomicIncU32(&g_cWaiting);
141 RTThreadUserWait(Thread, RT_INDEFINITE_WAIT);
142 RTThreadUserReset(Thread);
143 ASMAtomicDecU32(&g_cWaiting);
144 if (g_fDone)
145 break;
146
147 /*
148 * Spin.
149 */
150 ASMAtomicIncU32(&g_cReady);
151 while (!g_fDone)
152 {
153 const RTCPUID idCpu1 = MyGetCpuId();
154 const uint64_t TSC1 = ASMReadTSC();
155 const uint32_t u32Go = g_u32Go;
156 if (u32Go == 0)
157 continue;
158
159 if (u32Go == 1)
160 {
161 /* do the reading. */
162 const RTCPUID idCpu2 = MyGetCpuId();
163 const uint64_t TSC2 = ASMReadTSC();
164 const RTCPUID idCpu3 = MyGetCpuId();
165 const uint64_t TSC3 = ASMReadTSC();
166 const uint8_t idCpu4 = MyGetCpuId();
167
168 if ( idCpu1 == idCpu2
169 && idCpu1 == idCpu3
170 && idCpu1 == idCpu4
171 && TSC3 - TSC1 < MAX_TSC_DELTA
172 && TSC2 - TSC1 < TSC3 - TSC1
173 )
174 {
175 /* succeeded. */
176 pTscData->TSC = TSC2;
177 pTscData->idCpu = idCpu1;
178 pTscData->fFailed = false;
179 pTscData->fRead = true;
180 ASMAtomicIncU32(&g_cRead);
181 break;
182 }
183 }
184
185 /* failed */
186 pTscData->fFailed = true;
187 pTscData->fRead = false;
188 ASMAtomicIncU32(&g_cFailed);
189 break;
190 }
191 }
192
193 return VINF_SUCCESS;
194}
195
196static int tstTSCCalcDrift(void)
197{
198 /*
199 * This is only relevant to on SMP systems.
200 */
201 const unsigned cCpus = RTMpGetOnlineCount();
202 if (cCpus <= 1)
203 {
204 RTPrintf("tstTSC: SKIPPED - Only relevant on SMP systems\n");
205 return 0;
206 }
207
208 /*
209 * Create the threads.
210 */
211 static TSCDATA s_aData[254];
212 uint32_t i;
213 if (cCpus > RT_ELEMENTS(s_aData))
214 {
215 RTPrintf("tstTSC: FAILED - too many CPUs (%u)\n", cCpus);
216 return 1;
217 }
218
219 /* ourselves. */
220 s_aData[0].Thread = RTThreadSelf();
221
222 /* the others */
223 for (i = 1; i < cCpus; i++)
224 {
225 int rc = RTThreadCreate(&s_aData[i].Thread, ThreadFunction, &s_aData[i], 0, RTTHREADTYPE_TIMER, RTTHREADFLAGS_WAITABLE, "OTHERCPU");
226 if (RT_FAILURE(rc))
227 {
228 RTPrintf("tstTSC: FAILURE - RTThreatCreate failed when creating thread #%u, rc=%Rrc!\n", i, rc);
229 ASMAtomicXchgSize(&g_fDone, true);
230 while (i-- > 1)
231 {
232 RTThreadUserSignal(s_aData[i].Thread);
233 RTThreadWait(s_aData[i].Thread, 5000, NULL);
234 }
235 return 1;
236 }
237 }
238
239 /*
240 * Retry until we get lucky (or give up).
241 */
242 for (unsigned cTries = 0; ; cTries++)
243 {
244 if (cTries > 10240)
245 {
246 RTPrintf("tstTSC: FAILURE - %d attempts, giving.\n", cTries);
247 break;
248 }
249
250 /*
251 * Wait for the other threads to get ready (brute force active wait, I'm lazy).
252 */
253 i = 0;
254 while (g_cWaiting < cCpus - 1)
255 {
256 if (i++ > _2G32)
257 break;
258 RTThreadSleep(i & 0xf);
259 }
260 if (g_cWaiting != cCpus - 1)
261 {
262 RTPrintf("tstTSC: FAILURE - threads failed to get waiting (%d != %d (i=%d))\n", g_cWaiting + 1, cCpus, i);
263 break;
264 }
265
266 /*
267 * Send them spinning.
268 */
269 ASMAtomicXchgU32(&g_cReady, 0);
270 ASMAtomicXchgU32(&g_u32Go, 0);
271 ASMAtomicXchgU32(&g_cRead, 0);
272 ASMAtomicXchgU32(&g_cFailed, 0);
273 for (i = 1; i < cCpus; i++)
274 {
275 ASMAtomicXchgSize(&s_aData[i].fFailed, false);
276 ASMAtomicXchgSize(&s_aData[i].fRead, false);
277 ASMAtomicXchgU32(&s_aData[i].idCpu, NIL_RTCPUID);
278
279 int rc = RTThreadUserSignal(s_aData[i].Thread);
280 if (RT_FAILURE(rc))
281 RTPrintf("tstTSC: WARNING - RTThreadUserSignal(%#u) -> rc=%Rrc!\n", i, rc);
282 }
283
284 /* wait for them to get ready. */
285 i = 0;
286 while (g_cReady < cCpus - 1)
287 {
288 if (i++ > _2G32)
289 break;
290 }
291 if (g_cReady != cCpus - 1)
292 {
293 RTPrintf("tstTSC: FAILURE - threads failed to get ready (%d != %d, i=%d)\n", g_cWaiting + 1, cCpus, i);
294 break;
295 }
296
297 /*
298 * Flip the "go" switch and do our readings.
299 * We give the other threads the slack it takes to two extra TSC and CPU ID reads.
300 */
301 const RTCPUID idCpu1 = MyGetCpuId();
302 const uint64_t TSC1 = ASMReadTSC();
303 ASMAtomicXchgU32(&g_u32Go, 1);
304 const RTCPUID idCpu2 = MyGetCpuId();
305 const uint64_t TSC2 = ASMReadTSC();
306 const RTCPUID idCpu3 = MyGetCpuId();
307 const uint64_t TSC3 = ASMReadTSC();
308 const RTCPUID idCpu4 = MyGetCpuId();
309 const uint64_t TSC4 = ASMReadTSC();
310 ASMAtomicXchgU32(&g_u32Go, 2);
311 const RTCPUID idCpu5 = MyGetCpuId();
312 const uint64_t TSC5 = ASMReadTSC();
313 const RTCPUID idCpu6 = MyGetCpuId();
314
315 /* Compose our own result. */
316 if ( idCpu1 == idCpu2
317 && idCpu1 == idCpu3
318 && idCpu1 == idCpu4
319 && idCpu1 == idCpu5
320 && idCpu1 == idCpu6
321 && TSC5 - TSC1 < MAX_TSC_DELTA
322 && TSC4 - TSC1 < TSC5 - TSC1
323 && TSC3 - TSC1 < TSC4 - TSC1
324 && TSC2 - TSC1 < TSC3 - TSC1
325 )
326 {
327 /* succeeded. */
328 s_aData[0].TSC = TSC2;
329 s_aData[0].idCpu = idCpu1;
330 s_aData[0].fFailed = false;
331 s_aData[0].fRead = true;
332 ASMAtomicIncU32(&g_cRead);
333 }
334 else
335 {
336 /* failed */
337 s_aData[0].fFailed = true;
338 s_aData[0].fRead = false;
339 ASMAtomicIncU32(&g_cFailed);
340 }
341
342 /*
343 * Wait a little while to let the other ones to finish.
344 */
345 i = 0;
346 while (g_cRead + g_cFailed < cCpus)
347 {
348 if (i++ > _2G32)
349 break;
350 if (i > _1M)
351 RTThreadSleep(i & 0xf);
352 }
353 if (g_cRead + g_cFailed != cCpus)
354 {
355 RTPrintf("tstTSC: FAILURE - threads failed to complete reading (%d + %d != %d)\n", g_cRead, g_cFailed, cCpus);
356 break;
357 }
358
359 /*
360 * If everone succeeded, print the results.
361 */
362 if (!g_cFailed)
363 {
364 /* sort it by apic id first. */
365 bool fDone;
366 do
367 {
368 for (i = 1, fDone = true; i < cCpus; i++)
369 if (s_aData[i - 1].idCpu > s_aData[i].idCpu)
370 {
371 TSCDATA Tmp = s_aData[i - 1];
372 s_aData[i - 1] = s_aData[i];
373 s_aData[i] = Tmp;
374 fDone = false;
375 }
376 } while (!fDone);
377
378 RTPrintf(" # ID TSC delta0 (decimal)\n"
379 "-----------------------------------------\n");
380 RTPrintf("%2d %02x %RX64\n", 0, s_aData[0].idCpu, s_aData[0].TSC);
381 for (i = 1; i < cCpus; i++)
382 RTPrintf("%2d %02x %RX64 %s%lld\n", i, s_aData[i].idCpu, s_aData[i].TSC,
383 s_aData[i].TSC > s_aData[0].TSC ? "+" : "", s_aData[i].TSC - s_aData[0].TSC);
384 RTPrintf("(Needed %u attempt%s.)\n", cTries + 1, cTries ? "s" : "");
385 break;
386 }
387 }
388
389 /*
390 * Destroy the threads.
391 */
392 ASMAtomicXchgSize(&g_fDone, true);
393 for (i = 0; i < cCpus; i++)
394 if (s_aData[i].Thread != RTThreadSelf())
395 {
396 int rc = RTThreadUserSignal(s_aData[i].Thread);
397 if (RT_FAILURE(rc))
398 RTPrintf("tstTSC: WARNING - RTThreadUserSignal(%#u) -> rc=%Rrc! (2)\n", i, rc);
399 }
400 for (i = 0; i < cCpus; i++)
401 if (s_aData[i].Thread != RTThreadSelf())
402 {
403 int rc = RTThreadWait(s_aData[i].Thread, 5000, NULL);
404 if (RT_FAILURE(rc))
405 RTPrintf("tstTSC: WARNING - RTThreadWait(%#u) -> rc=%Rrc!\n", i, rc);
406 }
407
408 return g_cFailed != 0 || g_cRead != cCpus;
409}
410
411
412static int tstTSCCalcFrequency(uint32_t cMsDuration)
413{
414 /*
415 * Sample the TSC and time, sleep the requested time and calc the deltas.
416 */
417 uint64_t uNanoTS = RTTimeSystemNanoTS();
418 uint64_t uTSC = ASMReadTSC();
419 RTThreadSleep(cMsDuration);
420 uNanoTS = RTTimeSystemNanoTS() - uNanoTS;
421 uTSC = ASMReadTSC() - uTSC;
422
423 /*
424 * Calc the frequency.
425 */
426 RTPrintf("tstTSC: %RU64 ticks in %RU64 ns\n", uTSC, uNanoTS);
427 uint64_t cHz = (uint64_t)((long double)uTSC / ((long double)uNanoTS / (long double)1000000000));
428 RTPrintf("tstTSC: Frequency %RU64 Hz", cHz);
429 if (cHz > _1G)
430 {
431 cHz += _1G / 20;
432 RTPrintf(" %RU64.%RU64 GHz", cHz / _1G, (cHz % _1G) / (_1G / 10));
433 }
434 else if (cHz > _1M)
435 {
436 cHz += _1M / 20;
437 RTPrintf(" %RU64.%RU64 MHz", cHz / _1M, (cHz % _1M) / (_1M / 10));
438 }
439 RTPrintf("\n");
440 return 0;
441}
442
443
444int main(int argc, char **argv)
445{
446 RTR3InitExe(argc, &argv, 0);
447
448 /*
449 * Parse arguments.
450 */
451 bool fCalcFrequency = false;
452 uint32_t cMsDuration = 1000; /* 1 sec */
453 static const RTGETOPTDEF s_aOptions[] =
454 {
455 { "--duration", 'd', RTGETOPT_REQ_UINT32 },
456 { "--calc-frequency", 'f', RTGETOPT_REQ_NOTHING },
457 };
458 int ch;
459 RTGETOPTUNION Value;
460 RTGETOPTSTATE GetState;
461 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0 /* fFlags */);
462 while ((ch = RTGetOpt(&GetState, &Value)))
463 switch (ch)
464 {
465 case 'd': cMsDuration = Value.u32;
466 break;
467
468 case 'f': fCalcFrequency = true;
469 break;
470
471 case 'h':
472 RTPrintf("usage: tstTSC\n"
473 " or: tstTSC <-f|--calc-frequency> [--duration|-d ms]\n");
474 return 1;
475
476 case 'V':
477 RTPrintf("$Revision: 106924 $\n");
478 return 0;
479
480 default:
481 return RTGetOptPrintError(ch, &Value);
482 }
483
484 if (fCalcFrequency)
485 return tstTSCCalcFrequency(cMsDuration);
486 return tstTSCCalcDrift();
487}
488
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