VirtualBox

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

Last change on this file was 106061, checked in by vboxsync, 8 weeks ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 15.0 KB
Line 
1/* $Id: tstTSC.cpp 106061 2024-09-16 14:03:52Z 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#include <iprt/asm-amd64-x86.h>
42#include <iprt/asm.h>
43#include <iprt/getopt.h>
44#include <iprt/initterm.h>
45#include <iprt/mp.h>
46#include <iprt/stream.h>
47#include <iprt/string.h>
48#include <iprt/thread.h>
49#include <iprt/time.h>
50
51
52/*********************************************************************************************************************************
53* Structures and Typedefs *
54*********************************************************************************************************************************/
55typedef struct TSCDATA
56{
57 /** The TSC. */
58 uint64_t volatile TSC;
59 /** The APIC ID. */
60 uint8_t volatile u8ApicId;
61 /** Did it succeed? */
62 bool volatile fRead;
63 /** Did it fail? */
64 bool volatile fFailed;
65 /** The thread handle. */
66 RTTHREAD Thread;
67} TSCDATA, *PTSCDATA;
68
69
70/*********************************************************************************************************************************
71* Global Variables *
72*********************************************************************************************************************************/
73/** The number of CPUs waiting on their user event semaphore. */
74static volatile uint32_t g_cWaiting;
75/** The number of CPUs ready (in spin) to do the TSC read. */
76static volatile uint32_t g_cReady;
77/** The variable the CPUs are spinning on.
78 * 0: Spin.
79 * 1: Go ahead.
80 * 2: You're too late, back to square one. */
81static volatile uint32_t g_u32Go;
82/** The number of CPUs that managed to read the TSC. */
83static volatile uint32_t g_cRead;
84/** The number of CPUs that failed to read the TSC. */
85static volatile uint32_t g_cFailed;
86
87/** Indicator forcing the threads to quit. */
88static volatile bool g_fDone;
89
90
91/*********************************************************************************************************************************
92* Internal Functions *
93*********************************************************************************************************************************/
94static DECLCALLBACK(int) ThreadFunction(RTTHREAD Thread, void *pvUser);
95
96
97/**
98 * Thread function for catching the other cpus.
99 *
100 * @returns VINF_SUCCESS (we don't care).
101 * @param Thread The thread handle.
102 * @param pvUser PTSCDATA.
103 */
104static DECLCALLBACK(int) ThreadFunction(RTTHREAD Thread, void *pvUser)
105{
106 PTSCDATA pTscData = (PTSCDATA)pvUser;
107
108 while (!g_fDone)
109 {
110 /*
111 * Wait.
112 */
113 ASMAtomicIncU32(&g_cWaiting);
114 RTThreadUserWait(Thread, RT_INDEFINITE_WAIT);
115 RTThreadUserReset(Thread);
116 ASMAtomicDecU32(&g_cWaiting);
117 if (g_fDone)
118 break;
119
120 /*
121 * Spin.
122 */
123 ASMAtomicIncU32(&g_cReady);
124 while (!g_fDone)
125 {
126 const uint8_t ApicId1 = ASMGetApicId();
127 const uint64_t TSC1 = ASMReadTSC();
128 const uint32_t u32Go = g_u32Go;
129 if (u32Go == 0)
130 continue;
131
132 if (u32Go == 1)
133 {
134 /* do the reading. */
135 const uint8_t ApicId2 = ASMGetApicId();
136 const uint64_t TSC2 = ASMReadTSC();
137 const uint8_t ApicId3 = ASMGetApicId();
138 const uint64_t TSC3 = ASMReadTSC();
139 const uint8_t ApicId4 = ASMGetApicId();
140
141 if ( ApicId1 == ApicId2
142 && ApicId1 == ApicId3
143 && ApicId1 == ApicId4
144 && TSC3 - TSC1 < 2250 /* WARNING: This is just a guess, increase if it doesn't work for you. */
145 && TSC2 - TSC1 < TSC3 - TSC1
146 )
147 {
148 /* succeeded. */
149 pTscData->TSC = TSC2;
150 pTscData->u8ApicId = ApicId1;
151 pTscData->fFailed = false;
152 pTscData->fRead = true;
153 ASMAtomicIncU32(&g_cRead);
154 break;
155 }
156 }
157
158 /* failed */
159 pTscData->fFailed = true;
160 pTscData->fRead = false;
161 ASMAtomicIncU32(&g_cFailed);
162 break;
163 }
164 }
165
166 return VINF_SUCCESS;
167}
168
169static int tstTSCCalcDrift(void)
170{
171 /*
172 * This is only relevant to on SMP systems.
173 */
174 const unsigned cCpus = RTMpGetOnlineCount();
175 if (cCpus <= 1)
176 {
177 RTPrintf("tstTSC: SKIPPED - Only relevant on SMP systems\n");
178 return 0;
179 }
180
181 /*
182 * Create the threads.
183 */
184 static TSCDATA s_aData[254];
185 uint32_t i;
186 if (cCpus > RT_ELEMENTS(s_aData))
187 {
188 RTPrintf("tstTSC: FAILED - too many CPUs (%u)\n", cCpus);
189 return 1;
190 }
191
192 /* ourselves. */
193 s_aData[0].Thread = RTThreadSelf();
194
195 /* the others */
196 for (i = 1; i < cCpus; i++)
197 {
198 int rc = RTThreadCreate(&s_aData[i].Thread, ThreadFunction, &s_aData[i], 0, RTTHREADTYPE_TIMER, RTTHREADFLAGS_WAITABLE, "OTHERCPU");
199 if (RT_FAILURE(rc))
200 {
201 RTPrintf("tstTSC: FAILURE - RTThreatCreate failed when creating thread #%u, rc=%Rrc!\n", i, rc);
202 ASMAtomicXchgSize(&g_fDone, true);
203 while (i-- > 1)
204 {
205 RTThreadUserSignal(s_aData[i].Thread);
206 RTThreadWait(s_aData[i].Thread, 5000, NULL);
207 }
208 return 1;
209 }
210 }
211
212 /*
213 * Retry until we get lucky (or give up).
214 */
215 for (unsigned cTries = 0; ; cTries++)
216 {
217 if (cTries > 10240)
218 {
219 RTPrintf("tstTSC: FAILURE - %d attempts, giving.\n", cTries);
220 break;
221 }
222
223 /*
224 * Wait for the other threads to get ready (brute force active wait, I'm lazy).
225 */
226 i = 0;
227 while (g_cWaiting < cCpus - 1)
228 {
229 if (i++ > _2G32)
230 break;
231 RTThreadSleep(i & 0xf);
232 }
233 if (g_cWaiting != cCpus - 1)
234 {
235 RTPrintf("tstTSC: FAILURE - threads failed to get waiting (%d != %d (i=%d))\n", g_cWaiting + 1, cCpus, i);
236 break;
237 }
238
239 /*
240 * Send them spinning.
241 */
242 ASMAtomicXchgU32(&g_cReady, 0);
243 ASMAtomicXchgU32(&g_u32Go, 0);
244 ASMAtomicXchgU32(&g_cRead, 0);
245 ASMAtomicXchgU32(&g_cFailed, 0);
246 for (i = 1; i < cCpus; i++)
247 {
248 ASMAtomicXchgSize(&s_aData[i].fFailed, false);
249 ASMAtomicXchgSize(&s_aData[i].fRead, false);
250 ASMAtomicXchgU8(&s_aData[i].u8ApicId, 0xff);
251
252 int rc = RTThreadUserSignal(s_aData[i].Thread);
253 if (RT_FAILURE(rc))
254 RTPrintf("tstTSC: WARNING - RTThreadUserSignal(%#u) -> rc=%Rrc!\n", i, rc);
255 }
256
257 /* wait for them to get ready. */
258 i = 0;
259 while (g_cReady < cCpus - 1)
260 {
261 if (i++ > _2G32)
262 break;
263 }
264 if (g_cReady != cCpus - 1)
265 {
266 RTPrintf("tstTSC: FAILURE - threads failed to get ready (%d != %d, i=%d)\n", g_cWaiting + 1, cCpus, i);
267 break;
268 }
269
270 /*
271 * Flip the "go" switch and do our readings.
272 * We give the other threads the slack it takes to two extra TSC and APIC ID reads.
273 */
274 const uint8_t ApicId1 = ASMGetApicId();
275 const uint64_t TSC1 = ASMReadTSC();
276 ASMAtomicXchgU32(&g_u32Go, 1);
277 const uint8_t ApicId2 = ASMGetApicId();
278 const uint64_t TSC2 = ASMReadTSC();
279 const uint8_t ApicId3 = ASMGetApicId();
280 const uint64_t TSC3 = ASMReadTSC();
281 const uint8_t ApicId4 = ASMGetApicId();
282 const uint64_t TSC4 = ASMReadTSC();
283 ASMAtomicXchgU32(&g_u32Go, 2);
284 const uint8_t ApicId5 = ASMGetApicId();
285 const uint64_t TSC5 = ASMReadTSC();
286 const uint8_t ApicId6 = ASMGetApicId();
287
288 /* Compose our own result. */
289 if ( ApicId1 == ApicId2
290 && ApicId1 == ApicId3
291 && ApicId1 == ApicId4
292 && ApicId1 == ApicId5
293 && ApicId1 == ApicId6
294 && TSC5 - TSC1 < 2750 /* WARNING: This is just a guess, increase if it doesn't work for you. */
295 && TSC4 - TSC1 < TSC5 - TSC1
296 && TSC3 - TSC1 < TSC4 - TSC1
297 && TSC2 - TSC1 < TSC3 - TSC1
298 )
299 {
300 /* succeeded. */
301 s_aData[0].TSC = TSC2;
302 s_aData[0].u8ApicId = ApicId1;
303 s_aData[0].fFailed = false;
304 s_aData[0].fRead = true;
305 ASMAtomicIncU32(&g_cRead);
306 }
307 else
308 {
309 /* failed */
310 s_aData[0].fFailed = true;
311 s_aData[0].fRead = false;
312 ASMAtomicIncU32(&g_cFailed);
313 }
314
315 /*
316 * Wait a little while to let the other ones to finish.
317 */
318 i = 0;
319 while (g_cRead + g_cFailed < cCpus)
320 {
321 if (i++ > _2G32)
322 break;
323 if (i > _1M)
324 RTThreadSleep(i & 0xf);
325 }
326 if (g_cRead + g_cFailed != cCpus)
327 {
328 RTPrintf("tstTSC: FAILURE - threads failed to complete reading (%d + %d != %d)\n", g_cRead, g_cFailed, cCpus);
329 break;
330 }
331
332 /*
333 * If everone succeeded, print the results.
334 */
335 if (!g_cFailed)
336 {
337 /* sort it by apic id first. */
338 bool fDone;
339 do
340 {
341 for (i = 1, fDone = true; i < cCpus; i++)
342 if (s_aData[i - 1].u8ApicId > s_aData[i].u8ApicId)
343 {
344 TSCDATA Tmp = s_aData[i - 1];
345 s_aData[i - 1] = s_aData[i];
346 s_aData[i] = Tmp;
347 fDone = false;
348 }
349 } while (!fDone);
350
351 RTPrintf(" # ID TSC delta0 (decimal)\n"
352 "-----------------------------------------\n");
353 RTPrintf("%2d %02x %RX64\n", 0, s_aData[0].u8ApicId, s_aData[0].TSC);
354 for (i = 1; i < cCpus; i++)
355 RTPrintf("%2d %02x %RX64 %s%lld\n", i, s_aData[i].u8ApicId, s_aData[i].TSC,
356 s_aData[i].TSC > s_aData[0].TSC ? "+" : "", s_aData[i].TSC - s_aData[0].TSC);
357 RTPrintf("(Needed %u attempt%s.)\n", cTries + 1, cTries ? "s" : "");
358 break;
359 }
360 }
361
362 /*
363 * Destroy the threads.
364 */
365 ASMAtomicXchgSize(&g_fDone, true);
366 for (i = 0; i < cCpus; i++)
367 if (s_aData[i].Thread != RTThreadSelf())
368 {
369 int rc = RTThreadUserSignal(s_aData[i].Thread);
370 if (RT_FAILURE(rc))
371 RTPrintf("tstTSC: WARNING - RTThreadUserSignal(%#u) -> rc=%Rrc! (2)\n", i, rc);
372 }
373 for (i = 0; i < cCpus; i++)
374 if (s_aData[i].Thread != RTThreadSelf())
375 {
376 int rc = RTThreadWait(s_aData[i].Thread, 5000, NULL);
377 if (RT_FAILURE(rc))
378 RTPrintf("tstTSC: WARNING - RTThreadWait(%#u) -> rc=%Rrc!\n", i, rc);
379 }
380
381 return g_cFailed != 0 || g_cRead != cCpus;
382}
383
384
385static int tstTSCCalcFrequency(uint32_t cMsDuration)
386{
387 /*
388 * Sample the TSC and time, sleep the requested time and calc the deltas.
389 */
390 uint64_t uNanoTS = RTTimeSystemNanoTS();
391 uint64_t uTSC = ASMReadTSC();
392 RTThreadSleep(cMsDuration);
393 uNanoTS = RTTimeSystemNanoTS() - uNanoTS;
394 uTSC = ASMReadTSC() - uTSC;
395
396 /*
397 * Calc the frequency.
398 */
399 RTPrintf("tstTSC: %RU64 ticks in %RU64 ns\n", uTSC, uNanoTS);
400 uint64_t cHz = (uint64_t)((long double)uTSC / ((long double)uNanoTS / (long double)1000000000));
401 RTPrintf("tstTSC: Frequency %RU64 Hz", cHz);
402 if (cHz > _1G)
403 {
404 cHz += _1G / 20;
405 RTPrintf(" %RU64.%RU64 GHz", cHz / _1G, (cHz % _1G) / (_1G / 10));
406 }
407 else if (cHz > _1M)
408 {
409 cHz += _1M / 20;
410 RTPrintf(" %RU64.%RU64 MHz", cHz / _1M, (cHz % _1M) / (_1M / 10));
411 }
412 RTPrintf("\n");
413 return 0;
414}
415
416
417int main(int argc, char **argv)
418{
419 RTR3InitExe(argc, &argv, 0);
420
421 /*
422 * Parse arguments.
423 */
424 bool fCalcFrequency = false;
425 uint32_t cMsDuration = 1000; /* 1 sec */
426 static const RTGETOPTDEF s_aOptions[] =
427 {
428 { "--duration", 'd', RTGETOPT_REQ_UINT32 },
429 { "--calc-frequency", 'f', RTGETOPT_REQ_NOTHING },
430 };
431 int ch;
432 RTGETOPTUNION Value;
433 RTGETOPTSTATE GetState;
434 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0 /* fFlags */);
435 while ((ch = RTGetOpt(&GetState, &Value)))
436 switch (ch)
437 {
438 case 'd': cMsDuration = Value.u32;
439 break;
440
441 case 'f': fCalcFrequency = true;
442 break;
443
444 case 'h':
445 RTPrintf("usage: tstTSC\n"
446 " or: tstTSC <-f|--calc-frequency> [--duration|-d ms]\n");
447 return 1;
448
449 case 'V':
450 RTPrintf("$Revision: 106061 $\n");
451 return 0;
452
453 default:
454 return RTGetOptPrintError(ch, &Value);
455 }
456
457 if (fCalcFrequency)
458 return tstTSCCalcFrequency(cMsDuration);
459 return tstTSCCalcDrift();
460}
461
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