1 | /* $Id: timesupref.h 62477 2016-07-22 18:27:37Z vboxsync $ */
|
---|
2 | /** @file
|
---|
3 | * IPRT - Time using SUPLib, the C Code Template.
|
---|
4 | */
|
---|
5 |
|
---|
6 | /*
|
---|
7 | * Copyright (C) 2006-2016 Oracle Corporation
|
---|
8 | *
|
---|
9 | * This file is part of VirtualBox Open Source Edition (OSE), as
|
---|
10 | * available from http://www.virtualbox.org. This file is free software;
|
---|
11 | * you can redistribute it and/or modify it under the terms of the GNU
|
---|
12 | * General Public License (GPL) as published by the Free Software
|
---|
13 | * Foundation, in version 2 as it comes in the "COPYING" file of the
|
---|
14 | * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
|
---|
15 | * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
|
---|
16 | *
|
---|
17 | * The contents of this file may alternatively be used under the terms
|
---|
18 | * of the Common Development and Distribution License Version 1.0
|
---|
19 | * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
|
---|
20 | * VirtualBox OSE distribution, in which case the provisions of the
|
---|
21 | * CDDL are applicable instead of those of the GPL.
|
---|
22 | *
|
---|
23 | * You may elect to license modified versions of this file under the
|
---|
24 | * terms and conditions of either the GPL or the CDDL or both.
|
---|
25 | */
|
---|
26 |
|
---|
27 |
|
---|
28 | /**
|
---|
29 | * The C reference implementation of the assembly routines.
|
---|
30 | *
|
---|
31 | * Calculate NanoTS using the information in the global information page (GIP)
|
---|
32 | * which the support library (SUPLib) exports.
|
---|
33 | *
|
---|
34 | * This function guarantees that the returned timestamp is later (in time) than
|
---|
35 | * any previous calls in the same thread.
|
---|
36 | *
|
---|
37 | * @remark The way the ever increasing time guarantee is currently implemented means
|
---|
38 | * that if you call this function at a frequency higher than 1GHz you're in for
|
---|
39 | * trouble. We currently assume that no idiot will do that for real life purposes.
|
---|
40 | *
|
---|
41 | * @returns Nanosecond timestamp.
|
---|
42 | * @param pData Pointer to the data structure.
|
---|
43 | */
|
---|
44 | RTDECL(uint64_t) rtTimeNanoTSInternalRef(PRTTIMENANOTSDATA pData)
|
---|
45 | {
|
---|
46 | #if TMPL_MODE == TMPL_MODE_SYNC_INVAR_WITH_DELTA && defined(IN_RING3)
|
---|
47 | PSUPGIPCPU pGipCpuAttemptedTscRecalibration = NULL;
|
---|
48 | #endif
|
---|
49 | AssertCompile(RT_IS_POWER_OF_TWO(RTCPUSET_MAX_CPUS));
|
---|
50 |
|
---|
51 | for (;;)
|
---|
52 | {
|
---|
53 | #ifndef IN_RING3 /* This simplifies and improves everything. */
|
---|
54 | RTCCUINTREG const uFlags = ASMIntDisableFlags();
|
---|
55 | #endif
|
---|
56 |
|
---|
57 | /*
|
---|
58 | * Check that the GIP is sane and that the premises for this worker function
|
---|
59 | * hasn't changed (CPU onlined with bad delta or missing features).
|
---|
60 | */
|
---|
61 | PSUPGLOBALINFOPAGE pGip = g_pSUPGlobalInfoPage;
|
---|
62 | if ( RT_LIKELY(pGip)
|
---|
63 | && RT_LIKELY(pGip->u32Magic == SUPGLOBALINFOPAGE_MAGIC)
|
---|
64 | #if TMPL_MODE == TMPL_MODE_SYNC_INVAR_WITH_DELTA
|
---|
65 | && RT_LIKELY(pGip->enmUseTscDelta >= SUPGIPUSETSCDELTA_PRACTICALLY_ZERO)
|
---|
66 | #else
|
---|
67 | && RT_LIKELY(pGip->enmUseTscDelta <= SUPGIPUSETSCDELTA_ROUGHLY_ZERO)
|
---|
68 | #endif
|
---|
69 | #if defined(IN_RING3) && TMPL_GET_CPU_METHOD != 0 && TMPL_GET_CPU_METHOD != SUPGIPGETCPU_APIC_ID
|
---|
70 | && RT_LIKELY(pGip->fGetGipCpu & TMPL_GET_CPU_METHOD)
|
---|
71 | #endif
|
---|
72 | )
|
---|
73 | {
|
---|
74 | /*
|
---|
75 | * Resolve pGipCpu if needed. If the instruction is serializing, we
|
---|
76 | * read the transaction id first if possible.
|
---|
77 | */
|
---|
78 | #if TMPL_MODE == TMPL_MODE_ASYNC || TMPL_MODE == TMPL_MODE_SYNC_INVAR_WITH_DELTA
|
---|
79 | # if defined(IN_RING0)
|
---|
80 | uint32_t const iCpuSet = RTMpCurSetIndex();
|
---|
81 | uint16_t const iGipCpu = iCpuSet < RT_ELEMENTS(pGip->aiCpuFromCpuSetIdx)
|
---|
82 | ? pGip->aiCpuFromCpuSetIdx[iCpuSet] : UINT16_MAX;
|
---|
83 | # elif defined(IN_RC)
|
---|
84 | uint32_t const iCpuSet = VMMGetCpu(&g_VM)->iHostCpuSet;
|
---|
85 | uint16_t const iGipCpu = iCpuSet < RT_ELEMENTS(pGip->aiCpuFromCpuSetIdx)
|
---|
86 | ? pGip->aiCpuFromCpuSetIdx[iCpuSet] : UINT16_MAX;
|
---|
87 | # elif TMPL_GET_CPU_METHOD == SUPGIPGETCPU_APIC_ID
|
---|
88 | # if TMPL_MODE != TMPL_MODE_ASYNC
|
---|
89 | uint32_t const u32TransactionId = pGip->aCPUs[0].u32TransactionId;
|
---|
90 | # endif
|
---|
91 | uint8_t const idApic = ASMGetApicId();
|
---|
92 | uint16_t const iGipCpu = pGip->aiCpuFromApicId[idApic];
|
---|
93 | # elif TMPL_GET_CPU_METHOD == SUPGIPGETCPU_RDTSCP_MASK_MAX_SET_CPUS
|
---|
94 | # if TMPL_MODE != TMPL_MODE_ASYNC
|
---|
95 | uint32_t const u32TransactionId = pGip->aCPUs[0].u32TransactionId;
|
---|
96 | # endif
|
---|
97 | uint32_t uAux;
|
---|
98 | ASMReadTscWithAux(&uAux);
|
---|
99 | uint16_t const iCpuSet = uAux & (RTCPUSET_MAX_CPUS - 1);
|
---|
100 | uint16_t const iGipCpu = pGip->aiCpuFromCpuSetIdx[iCpuSet];
|
---|
101 | # elif TMPL_GET_CPU_METHOD == SUPGIPGETCPU_IDTR_LIMIT_MASK_MAX_SET_CPUS
|
---|
102 | uint16_t const cbLim = ASMGetIdtrLimit();
|
---|
103 | uint16_t const iCpuSet = (cbLim - 256 * (ARCH_BITS == 64 ? 16 : 8)) & (RTCPUSET_MAX_CPUS - 1);
|
---|
104 | uint16_t const iGipCpu = pGip->aiCpuFromCpuSetIdx[iCpuSet];
|
---|
105 | # else
|
---|
106 | # error "What?"
|
---|
107 | # endif
|
---|
108 | if (RT_LIKELY(iGipCpu < pGip->cCpus))
|
---|
109 | {
|
---|
110 | PSUPGIPCPU pGipCpu = &pGip->aCPUs[iGipCpu];
|
---|
111 | #else
|
---|
112 | {
|
---|
113 | #endif
|
---|
114 | /*
|
---|
115 | * Get the transaction ID if necessary and we haven't already
|
---|
116 | * read it before a serializing instruction above. We can skip
|
---|
117 | * this for ASYNC_TSC mode in ring-0 and raw-mode context since
|
---|
118 | * we disable interrupts.
|
---|
119 | */
|
---|
120 | #if TMPL_MODE == TMPL_MODE_ASYNC && defined(IN_RING3)
|
---|
121 | uint32_t const u32TransactionId = pGipCpu->u32TransactionId;
|
---|
122 | ASMCompilerBarrier();
|
---|
123 | TMPL_READ_FENCE();
|
---|
124 | #elif TMPL_MODE != TMPL_MODE_ASYNC \
|
---|
125 | && TMPL_GET_CPU_METHOD != SUPGIPGETCPU_APIC_ID \
|
---|
126 | && TMPL_GET_CPU_METHOD != SUPGIPGETCPU_RDTSCP_MASK_MAX_SET_CPUS
|
---|
127 | uint32_t const u32TransactionId = pGip->aCPUs[0].u32TransactionId;
|
---|
128 | ASMCompilerBarrier();
|
---|
129 | TMPL_READ_FENCE();
|
---|
130 | #endif
|
---|
131 |
|
---|
132 | /*
|
---|
133 | * Gather all the data we need. The mess at the end is to make
|
---|
134 | * sure all loads are done before we recheck the transaction ID
|
---|
135 | * without triggering serializing twice.
|
---|
136 | */
|
---|
137 | uint32_t u32NanoTSFactor0 = pGip->u32UpdateIntervalNS;
|
---|
138 | #if TMPL_MODE == TMPL_MODE_ASYNC
|
---|
139 | uint32_t u32UpdateIntervalTSC = pGipCpu->u32UpdateIntervalTSC;
|
---|
140 | uint64_t u64NanoTS = pGipCpu->u64NanoTS;
|
---|
141 | uint64_t u64TSC = pGipCpu->u64TSC;
|
---|
142 | #else
|
---|
143 | uint32_t u32UpdateIntervalTSC = pGip->aCPUs[0].u32UpdateIntervalTSC;
|
---|
144 | uint64_t u64NanoTS = pGip->aCPUs[0].u64NanoTS;
|
---|
145 | uint64_t u64TSC = pGip->aCPUs[0].u64TSC;
|
---|
146 | # if TMPL_MODE == TMPL_MODE_SYNC_INVAR_WITH_DELTA
|
---|
147 | int64_t i64TscDelta = pGipCpu->i64TSCDelta;
|
---|
148 | # endif
|
---|
149 | #endif
|
---|
150 | uint64_t u64PrevNanoTS = ASMAtomicUoReadU64(pData->pu64Prev);
|
---|
151 | #if TMPL_GET_CPU_METHOD == SUPGIPGETCPU_RDTSCP_MASK_MAX_SET_CPUS
|
---|
152 | ASMCompilerBarrier();
|
---|
153 | uint32_t uAux2;
|
---|
154 | uint64_t u64Delta = ASMReadTscWithAux(&uAux2); /* serializing */
|
---|
155 | #else
|
---|
156 | uint64_t u64Delta = ASMReadTSC();
|
---|
157 | ASMCompilerBarrier();
|
---|
158 | # if TMPL_GET_CPU_METHOD != SUPGIPGETCPU_APIC_ID /* getting APIC will serialize */ \
|
---|
159 | && (defined(IN_RING3) || TMPL_MODE != TMPL_MODE_ASYNC)
|
---|
160 | TMPL_READ_FENCE(); /* Expensive (~30 ticks). Would like convincing argumentation that let us remove it. */
|
---|
161 | # endif
|
---|
162 | #endif
|
---|
163 |
|
---|
164 | /*
|
---|
165 | * Check that we didn't change CPU.
|
---|
166 | */
|
---|
167 | #if defined(IN_RING3) && ( TMPL_MODE == TMPL_MODE_ASYNC || TMPL_MODE == TMPL_MODE_SYNC_INVAR_WITH_DELTA )
|
---|
168 | # if TMPL_GET_CPU_METHOD == SUPGIPGETCPU_APIC_ID
|
---|
169 | if (RT_LIKELY(ASMGetApicId() == idApic))
|
---|
170 | # elif TMPL_GET_CPU_METHOD == SUPGIPGETCPU_RDTSCP_MASK_MAX_SET_CPUS
|
---|
171 | if (RT_LIKELY(uAux2 == uAux))
|
---|
172 | # elif TMPL_GET_CPU_METHOD == SUPGIPGETCPU_IDTR_LIMIT_MASK_MAX_SET_CPUS
|
---|
173 | if (RT_LIKELY(ASMGetIdtrLimit() == cbLim))
|
---|
174 | # endif
|
---|
175 | #endif
|
---|
176 | {
|
---|
177 | /*
|
---|
178 | * Check the transaction ID (see above for R0/RC + ASYNC).
|
---|
179 | */
|
---|
180 | #if defined(IN_RING3) || TMPL_MODE != TMPL_MODE_ASYNC
|
---|
181 | # if TMPL_MODE == TMPL_MODE_ASYNC
|
---|
182 | if (RT_LIKELY(pGipCpu->u32TransactionId == u32TransactionId && !(u32TransactionId & 1) ))
|
---|
183 | # else
|
---|
184 | if (RT_LIKELY(pGip->aCPUs[0].u32TransactionId == u32TransactionId && !(u32TransactionId & 1) ))
|
---|
185 | # endif
|
---|
186 | #endif
|
---|
187 | {
|
---|
188 |
|
---|
189 | /*
|
---|
190 | * Apply the TSC delta. If the delta is invalid and the
|
---|
191 | * execution allows it, try trigger delta recalibration.
|
---|
192 | */
|
---|
193 | #if TMPL_MODE == TMPL_MODE_SYNC_INVAR_WITH_DELTA && defined(IN_RING3)
|
---|
194 | if (RT_LIKELY( i64TscDelta != INT64_MAX
|
---|
195 | || pGipCpu == pGipCpuAttemptedTscRecalibration))
|
---|
196 | #endif
|
---|
197 | {
|
---|
198 | #if TMPL_MODE == TMPL_MODE_SYNC_INVAR_WITH_DELTA
|
---|
199 | # ifndef IN_RING3
|
---|
200 | if (RT_LIKELY(i64TscDelta != INT64_MAX))
|
---|
201 | # endif
|
---|
202 | u64Delta -= i64TscDelta;
|
---|
203 | #endif
|
---|
204 |
|
---|
205 | /*
|
---|
206 | * Bingo! We've got a consistent set of data.
|
---|
207 | */
|
---|
208 | #ifndef IN_RING3
|
---|
209 | ASMSetFlags(uFlags);
|
---|
210 | #endif
|
---|
211 |
|
---|
212 | /*
|
---|
213 | * Calc NanoTS delta.
|
---|
214 | */
|
---|
215 | u64Delta -= u64TSC;
|
---|
216 | if (RT_LIKELY(u64Delta <= u32UpdateIntervalTSC))
|
---|
217 | { /* MSVC branch hint, probably pointless. */ }
|
---|
218 | else
|
---|
219 | {
|
---|
220 | /*
|
---|
221 | * We've expired the interval, cap it. If we're here for the 2nd
|
---|
222 | * time without any GIP update in-between, the checks against
|
---|
223 | * *pu64Prev below will force 1ns stepping.
|
---|
224 | */
|
---|
225 | ASMAtomicIncU32(&pData->cExpired);
|
---|
226 | u64Delta = u32UpdateIntervalTSC;
|
---|
227 | }
|
---|
228 | #if !defined(_MSC_VER) || !defined(RT_ARCH_X86) /* GCC makes very pretty code from these two inline calls, while MSC cannot. */
|
---|
229 | u64Delta = ASMMult2xU32RetU64((uint32_t)u64Delta, u32NanoTSFactor0);
|
---|
230 | u64Delta = ASMDivU64ByU32RetU32(u64Delta, u32UpdateIntervalTSC);
|
---|
231 | #else
|
---|
232 | __asm
|
---|
233 | {
|
---|
234 | mov eax, dword ptr [u64Delta]
|
---|
235 | mul dword ptr [u32NanoTSFactor0]
|
---|
236 | div dword ptr [u32UpdateIntervalTSC]
|
---|
237 | mov dword ptr [u64Delta], eax
|
---|
238 | xor edx, edx
|
---|
239 | mov dword ptr [u64Delta + 4], edx
|
---|
240 | }
|
---|
241 | #endif
|
---|
242 |
|
---|
243 | /*
|
---|
244 | * Calculate the time and compare it with the previously returned value.
|
---|
245 | */
|
---|
246 | u64NanoTS += u64Delta;
|
---|
247 | uint64_t u64DeltaPrev = u64NanoTS - u64PrevNanoTS;
|
---|
248 | if (RT_LIKELY( u64DeltaPrev > 0
|
---|
249 | && u64DeltaPrev < UINT64_C(86000000000000) /* 24h */))
|
---|
250 | { /* Frequent - less than 24h since last call. */ }
|
---|
251 | else if (RT_LIKELY( (int64_t)u64DeltaPrev <= 0
|
---|
252 | && (int64_t)u64DeltaPrev + u32NanoTSFactor0 * 2 >= 0))
|
---|
253 | {
|
---|
254 | /* Occasional - u64NanoTS is in the recent 'past' relative the previous call. */
|
---|
255 | ASMAtomicIncU32(&pData->c1nsSteps);
|
---|
256 | u64NanoTS = u64PrevNanoTS + 1;
|
---|
257 | }
|
---|
258 | else if (!u64PrevNanoTS)
|
---|
259 | /* We're resuming (see TMVirtualResume). */;
|
---|
260 | else
|
---|
261 | {
|
---|
262 | /* Something has gone bust, if negative offset it's real bad. */
|
---|
263 | ASMAtomicIncU32(&pData->cBadPrev);
|
---|
264 | pData->pfnBad(pData, u64NanoTS, u64DeltaPrev, u64PrevNanoTS);
|
---|
265 | }
|
---|
266 |
|
---|
267 | /*
|
---|
268 | * Attempt updating the previous value, provided we're still ahead of it.
|
---|
269 | *
|
---|
270 | * There is no point in recalculating u64NanoTS because we got preempted or if
|
---|
271 | * we raced somebody while the GIP was updated, since these are events
|
---|
272 | * that might occur at any point in the return path as well.
|
---|
273 | */
|
---|
274 | if (RT_LIKELY(ASMAtomicCmpXchgU64(pData->pu64Prev, u64NanoTS, u64PrevNanoTS)))
|
---|
275 | return u64NanoTS;
|
---|
276 |
|
---|
277 | ASMAtomicIncU32(&pData->cUpdateRaces);
|
---|
278 | for (int cTries = 25; cTries > 0; cTries--)
|
---|
279 | {
|
---|
280 | u64PrevNanoTS = ASMAtomicReadU64(pData->pu64Prev);
|
---|
281 | if (u64PrevNanoTS >= u64NanoTS)
|
---|
282 | break;
|
---|
283 | if (ASMAtomicCmpXchgU64(pData->pu64Prev, u64NanoTS, u64PrevNanoTS))
|
---|
284 | break;
|
---|
285 | ASMNopPause();
|
---|
286 | }
|
---|
287 | return u64NanoTS;
|
---|
288 | }
|
---|
289 |
|
---|
290 | #if TMPL_MODE == TMPL_MODE_SYNC_INVAR_WITH_DELTA && defined(IN_RING3)
|
---|
291 | /*
|
---|
292 | * Call into the support driver to try make it recalculate the delta. We
|
---|
293 | * remember which GIP CPU structure we're probably working on so we won't
|
---|
294 | * end up in a loop if the driver for some reason cannot get the job done.
|
---|
295 | */
|
---|
296 | else /* else is unecessary, but helps checking the preprocessor spaghetti. */
|
---|
297 | {
|
---|
298 | pGipCpuAttemptedTscRecalibration = pGipCpu;
|
---|
299 | uint64_t u64TscTmp;
|
---|
300 | uint16_t idApicUpdate;
|
---|
301 | int rc = SUPR3ReadTsc(&u64TscTmp, &idApicUpdate);
|
---|
302 | if (RT_SUCCESS(rc) && idApicUpdate < RT_ELEMENTS(pGip->aiCpuFromApicId))
|
---|
303 | {
|
---|
304 | uint32_t iUpdateGipCpu = pGip->aiCpuFromApicId[idApicUpdate];
|
---|
305 | if (iUpdateGipCpu < pGip->cCpus)
|
---|
306 | pGipCpuAttemptedTscRecalibration = &pGip->aCPUs[iUpdateGipCpu];
|
---|
307 | }
|
---|
308 | }
|
---|
309 | #endif
|
---|
310 | }
|
---|
311 | }
|
---|
312 |
|
---|
313 | /*
|
---|
314 | * No joy must try again.
|
---|
315 | */
|
---|
316 | #ifdef _MSC_VER
|
---|
317 | # pragma warning(disable: 4702)
|
---|
318 | #endif
|
---|
319 | #ifndef IN_RING3
|
---|
320 | ASMSetFlags(uFlags);
|
---|
321 | #endif
|
---|
322 | ASMNopPause();
|
---|
323 | continue;
|
---|
324 | }
|
---|
325 |
|
---|
326 | #if TMPL_MODE == TMPL_MODE_ASYNC || TMPL_MODE == TMPL_MODE_SYNC_INVAR_WITH_DELTA
|
---|
327 | /*
|
---|
328 | * We've got a bad CPU or APIC index of some kind.
|
---|
329 | */
|
---|
330 | else /* else is unecessary, but helps checking the preprocessor spaghetti. */
|
---|
331 | {
|
---|
332 | # ifndef IN_RING3
|
---|
333 | ASMSetFlags(uFlags);
|
---|
334 | # endif
|
---|
335 | # if defined(IN_RING0) || defined(IN_RC) || TMPL_GET_CPU_METHOD != SUPGIPGETCPU_APIC_ID
|
---|
336 | return pData->pfnBadCpuIndex(pData, UINT16_MAX-1, iCpuSet, iGipCpu);
|
---|
337 | # else
|
---|
338 | return pData->pfnBadCpuIndex(pData, idApic, UINT16_MAX-1, iGipCpu);
|
---|
339 | # endif
|
---|
340 | }
|
---|
341 | #endif
|
---|
342 | }
|
---|
343 |
|
---|
344 | /*
|
---|
345 | * Something changed in the GIP config or it was unmapped, figure out
|
---|
346 | * the right worker function to use now.
|
---|
347 | */
|
---|
348 | #ifndef IN_RING3
|
---|
349 | ASMSetFlags(uFlags);
|
---|
350 | #endif
|
---|
351 | return pData->pfnRediscover(pData);
|
---|
352 | }
|
---|
353 | }
|
---|
354 |
|
---|