VirtualBox

source: vbox/trunk/src/VBox/VMM/VMMAll/TMAllVirtual.cpp@ 62517

Last change on this file since 62517 was 62478, checked in by vboxsync, 8 years ago

(C) 2016

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 35.9 KB
Line 
1/* $Id: TMAllVirtual.cpp 62478 2016-07-22 18:29:06Z vboxsync $ */
2/** @file
3 * TM - Timeout Manager, Virtual Time, All Contexts.
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
18
19/*********************************************************************************************************************************
20* Header Files *
21*********************************************************************************************************************************/
22#define LOG_GROUP LOG_GROUP_TM
23#include <VBox/vmm/tm.h>
24#include <VBox/vmm/dbgftrace.h>
25#ifdef IN_RING3
26# ifdef VBOX_WITH_REM
27# include <VBox/vmm/rem.h>
28# endif
29# include <iprt/thread.h>
30#endif
31#include "TMInternal.h"
32#include <VBox/vmm/vm.h>
33#include <VBox/vmm/vmm.h>
34#include <VBox/err.h>
35#include <VBox/log.h>
36#include <VBox/sup.h>
37
38#include <iprt/time.h>
39#include <iprt/assert.h>
40#include <iprt/asm.h>
41#include <iprt/asm-math.h>
42
43
44
45/**
46 * @interface_method_impl{RTTIMENANOTSDATA,pfnBad}
47 */
48DECLCALLBACK(DECLEXPORT(void)) tmVirtualNanoTSBad(PRTTIMENANOTSDATA pData, uint64_t u64NanoTS, uint64_t u64DeltaPrev,
49 uint64_t u64PrevNanoTS)
50{
51 PVM pVM = RT_FROM_MEMBER(pData, VM, CTX_SUFF(tm.s.VirtualGetRawData));
52 pData->cBadPrev++;
53 if ((int64_t)u64DeltaPrev < 0)
54 LogRel(("TM: u64DeltaPrev=%RI64 u64PrevNanoTS=0x%016RX64 u64NanoTS=0x%016RX64 pVM=%p\n",
55 u64DeltaPrev, u64PrevNanoTS, u64NanoTS, pVM));
56 else
57 Log(("TM: u64DeltaPrev=%RI64 u64PrevNanoTS=0x%016RX64 u64NanoTS=0x%016RX64 pVM=%p (debugging?)\n",
58 u64DeltaPrev, u64PrevNanoTS, u64NanoTS, pVM));
59}
60
61
62/**
63 * @interface_method_impl{RTTIMENANOTSDATA,pfnRediscover}
64 *
65 * This is the initial worker, so the first call in each context ends up here.
66 * It is also used should the delta rating of the host CPUs change or if the
67 * fGetGipCpu feature the current worker relies upon becomes unavailable. The
68 * last two events may occur as CPUs are taken online.
69 */
70DECLCALLBACK(DECLEXPORT(uint64_t)) tmVirtualNanoTSRediscover(PRTTIMENANOTSDATA pData)
71{
72 PVM pVM = RT_FROM_MEMBER(pData, VM, CTX_SUFF(tm.s.VirtualGetRawData));
73
74 /*
75 * We require a valid GIP for the selection below. Invalid GIP is fatal.
76 */
77 PSUPGLOBALINFOPAGE pGip = g_pSUPGlobalInfoPage;
78 AssertFatalMsg(RT_VALID_PTR(pGip), ("pVM=%p pGip=%p\n", pVM, pGip));
79 AssertFatalMsg(pGip->u32Magic == SUPGLOBALINFOPAGE_MAGIC, ("pVM=%p pGip=%p u32Magic=%#x\n", pVM, pGip, pGip->u32Magic));
80 AssertFatalMsg(pGip->u32Mode > SUPGIPMODE_INVALID && pGip->u32Mode < SUPGIPMODE_END,
81 ("pVM=%p pGip=%p u32Mode=%#x\n", pVM, pGip, pGip->u32Mode));
82
83 /*
84 * Determine the new worker.
85 */
86 PFNTIMENANOTSINTERNAL pfnWorker;
87 bool const fLFence = RT_BOOL(ASMCpuId_EDX(1) & X86_CPUID_FEATURE_EDX_SSE2);
88 switch (pGip->u32Mode)
89 {
90 case SUPGIPMODE_SYNC_TSC:
91 case SUPGIPMODE_INVARIANT_TSC:
92#if defined(IN_RC) || defined(IN_RING0)
93 if (pGip->enmUseTscDelta <= SUPGIPUSETSCDELTA_ROUGHLY_ZERO)
94 pfnWorker = fLFence ? RTTimeNanoTSLFenceSyncInvarNoDelta : RTTimeNanoTSLegacySyncInvarNoDelta;
95 else
96 pfnWorker = fLFence ? RTTimeNanoTSLFenceSyncInvarWithDelta : RTTimeNanoTSLegacySyncInvarWithDelta;
97#else
98 if (pGip->fGetGipCpu & SUPGIPGETCPU_IDTR_LIMIT_MASK_MAX_SET_CPUS)
99 pfnWorker = pGip->enmUseTscDelta <= SUPGIPUSETSCDELTA_PRACTICALLY_ZERO
100 ? fLFence ? RTTimeNanoTSLFenceSyncInvarNoDelta : RTTimeNanoTSLegacySyncInvarNoDelta
101 : fLFence ? RTTimeNanoTSLFenceSyncInvarWithDeltaUseIdtrLim : RTTimeNanoTSLegacySyncInvarWithDeltaUseIdtrLim;
102 else if (pGip->fGetGipCpu & SUPGIPGETCPU_RDTSCP_MASK_MAX_SET_CPUS)
103 pfnWorker = pGip->enmUseTscDelta <= SUPGIPUSETSCDELTA_PRACTICALLY_ZERO
104 ? fLFence ? RTTimeNanoTSLFenceSyncInvarNoDelta : RTTimeNanoTSLegacySyncInvarNoDelta
105 : fLFence ? RTTimeNanoTSLFenceSyncInvarWithDeltaUseRdtscp : RTTimeNanoTSLegacySyncInvarWithDeltaUseRdtscp;
106 else
107 pfnWorker = pGip->enmUseTscDelta <= SUPGIPUSETSCDELTA_ROUGHLY_ZERO
108 ? fLFence ? RTTimeNanoTSLFenceSyncInvarNoDelta : RTTimeNanoTSLegacySyncInvarNoDelta
109 : fLFence ? RTTimeNanoTSLFenceSyncInvarWithDeltaUseApicId : RTTimeNanoTSLegacySyncInvarWithDeltaUseApicId;
110#endif
111 break;
112
113 case SUPGIPMODE_ASYNC_TSC:
114#if defined(IN_RC) || defined(IN_RING0)
115 pfnWorker = fLFence ? RTTimeNanoTSLFenceAsync : RTTimeNanoTSLegacyAsync;
116#else
117 if (pGip->fGetGipCpu & SUPGIPGETCPU_IDTR_LIMIT_MASK_MAX_SET_CPUS)
118 pfnWorker = fLFence ? RTTimeNanoTSLFenceAsyncUseIdtrLim : RTTimeNanoTSLegacyAsyncUseIdtrLim;
119 else if (pGip->fGetGipCpu & SUPGIPGETCPU_RDTSCP_MASK_MAX_SET_CPUS)
120 pfnWorker = fLFence ? RTTimeNanoTSLFenceAsyncUseRdtscp : RTTimeNanoTSLegacyAsyncUseRdtscp;
121 else
122 pfnWorker = fLFence ? RTTimeNanoTSLFenceAsyncUseApicId : RTTimeNanoTSLegacyAsyncUseApicId;
123#endif
124 break;
125
126 default:
127 AssertFatalMsgFailed(("pVM=%p pGip=%p u32Mode=%#x\n", pVM, pGip, pGip->u32Mode));
128 }
129
130 /*
131 * Update the pfnVirtualGetRaw pointer and call the worker we selected.
132 */
133 ASMAtomicWritePtr((void * volatile *)&CTX_SUFF(pVM->tm.s.pfnVirtualGetRaw), (void *)(uintptr_t)pfnWorker);
134 return pfnWorker(pData);
135}
136
137
138/**
139 * @interface_method_impl{RTTIMENANOTSDATA,pfnBadCpuIndex}
140 */
141DECLEXPORT(uint64_t) tmVirtualNanoTSBadCpuIndex(PRTTIMENANOTSDATA pData, uint16_t idApic, uint16_t iCpuSet, uint16_t iGipCpu)
142{
143 PVM pVM = RT_FROM_MEMBER(pData, VM, CTX_SUFF(tm.s.VirtualGetRawData));
144 AssertFatalMsgFailed(("pVM=%p idApic=%#x iCpuSet=%#x iGipCpu=%#x\n", pVM, idApic, iCpuSet, iGipCpu));
145#ifndef _MSC_VER
146 return UINT64_MAX;
147#endif
148}
149
150
151/**
152 * Wrapper around the IPRT GIP time methods.
153 */
154DECLINLINE(uint64_t) tmVirtualGetRawNanoTS(PVM pVM)
155{
156# ifdef IN_RING3
157 uint64_t u64 = CTXALLSUFF(pVM->tm.s.pfnVirtualGetRaw)(&CTXALLSUFF(pVM->tm.s.VirtualGetRawData));
158# else /* !IN_RING3 */
159 uint32_t cPrevSteps = pVM->tm.s.CTX_SUFF(VirtualGetRawData).c1nsSteps;
160 uint64_t u64 = pVM->tm.s.CTX_SUFF(pfnVirtualGetRaw)(&pVM->tm.s.CTX_SUFF(VirtualGetRawData));
161 if (cPrevSteps != pVM->tm.s.CTX_SUFF(VirtualGetRawData).c1nsSteps)
162 VMCPU_FF_SET(VMMGetCpu(pVM), VMCPU_FF_TO_R3);
163# endif /* !IN_RING3 */
164 /*DBGFTRACE_POS_U64(pVM, u64);*/
165 return u64;
166}
167
168
169/**
170 * Get the time when we're not running at 100%
171 *
172 * @returns The timestamp.
173 * @param pVM The cross context VM structure.
174 */
175static uint64_t tmVirtualGetRawNonNormal(PVM pVM)
176{
177 /*
178 * Recalculate the RTTimeNanoTS() value for the period where
179 * warp drive has been enabled.
180 */
181 uint64_t u64 = tmVirtualGetRawNanoTS(pVM);
182 u64 -= pVM->tm.s.u64VirtualWarpDriveStart;
183 u64 *= pVM->tm.s.u32VirtualWarpDrivePercentage;
184 u64 /= 100;
185 u64 += pVM->tm.s.u64VirtualWarpDriveStart;
186
187 /*
188 * Now we apply the virtual time offset.
189 * (Which is the negated tmVirtualGetRawNanoTS() value for when the virtual
190 * machine started if it had been running continuously without any suspends.)
191 */
192 u64 -= pVM->tm.s.u64VirtualOffset;
193 return u64;
194}
195
196
197/**
198 * Get the raw virtual time.
199 *
200 * @returns The current time stamp.
201 * @param pVM The cross context VM structure.
202 */
203DECLINLINE(uint64_t) tmVirtualGetRaw(PVM pVM)
204{
205 if (RT_LIKELY(!pVM->tm.s.fVirtualWarpDrive))
206 return tmVirtualGetRawNanoTS(pVM) - pVM->tm.s.u64VirtualOffset;
207 return tmVirtualGetRawNonNormal(pVM);
208}
209
210
211/**
212 * Inlined version of tmVirtualGetEx.
213 */
214DECLINLINE(uint64_t) tmVirtualGet(PVM pVM, bool fCheckTimers)
215{
216 uint64_t u64;
217 if (RT_LIKELY(pVM->tm.s.cVirtualTicking))
218 {
219 STAM_COUNTER_INC(&pVM->tm.s.StatVirtualGet);
220 u64 = tmVirtualGetRaw(pVM);
221
222 /*
223 * Use the chance to check for expired timers.
224 */
225 if (fCheckTimers)
226 {
227 PVMCPU pVCpuDst = &pVM->aCpus[pVM->tm.s.idTimerCpu];
228 if ( !VMCPU_FF_IS_SET(pVCpuDst, VMCPU_FF_TIMER)
229 && !pVM->tm.s.fRunningQueues
230 && ( pVM->tm.s.CTX_SUFF(paTimerQueues)[TMCLOCK_VIRTUAL].u64Expire <= u64
231 || ( pVM->tm.s.fVirtualSyncTicking
232 && pVM->tm.s.CTX_SUFF(paTimerQueues)[TMCLOCK_VIRTUAL_SYNC].u64Expire <= u64 - pVM->tm.s.offVirtualSync
233 )
234 )
235 && !pVM->tm.s.fRunningQueues
236 )
237 {
238 STAM_COUNTER_INC(&pVM->tm.s.StatVirtualGetSetFF);
239 Log5(("TMAllVirtual(%u): FF: %d -> 1\n", __LINE__, VMCPU_FF_IS_PENDING(pVCpuDst, VMCPU_FF_TIMER)));
240 VMCPU_FF_SET(pVCpuDst, VMCPU_FF_TIMER);
241#ifdef IN_RING3
242# ifdef VBOX_WITH_REM
243 REMR3NotifyTimerPending(pVM, pVCpuDst);
244# endif
245 VMR3NotifyCpuFFU(pVCpuDst->pUVCpu, VMNOTIFYFF_FLAGS_DONE_REM);
246#endif
247 }
248 }
249 }
250 else
251 u64 = pVM->tm.s.u64Virtual;
252 return u64;
253}
254
255
256/**
257 * Gets the current TMCLOCK_VIRTUAL time
258 *
259 * @returns The timestamp.
260 * @param pVM The cross context VM structure.
261 *
262 * @remark While the flow of time will never go backwards, the speed of the
263 * progress varies due to inaccurate RTTimeNanoTS and TSC. The latter can be
264 * influenced by power saving (SpeedStep, PowerNow!), while the former
265 * makes use of TSC and kernel timers.
266 */
267VMM_INT_DECL(uint64_t) TMVirtualGet(PVM pVM)
268{
269 return tmVirtualGet(pVM, true /*fCheckTimers*/);
270}
271
272
273/**
274 * Gets the current TMCLOCK_VIRTUAL time without checking
275 * timers or anything.
276 *
277 * Meaning, this has no side effect on FFs like TMVirtualGet may have.
278 *
279 * @returns The timestamp.
280 * @param pVM The cross context VM structure.
281 *
282 * @remarks See TMVirtualGet.
283 */
284VMM_INT_DECL(uint64_t) TMVirtualGetNoCheck(PVM pVM)
285{
286 return tmVirtualGet(pVM, false /*fCheckTimers*/);
287}
288
289
290/**
291 * Converts the dead line interval from TMCLOCK_VIRTUAL to host nano seconds.
292 *
293 * @returns Host nano second count.
294 * @param pVM The cross context VM structure.
295 * @param cVirtTicksToDeadline The TMCLOCK_VIRTUAL interval.
296 */
297DECLINLINE(uint64_t) tmVirtualVirtToNsDeadline(PVM pVM, uint64_t cVirtTicksToDeadline)
298{
299 if (RT_UNLIKELY(pVM->tm.s.fVirtualWarpDrive))
300 return ASMMultU64ByU32DivByU32(cVirtTicksToDeadline, 100, pVM->tm.s.u32VirtualWarpDrivePercentage);
301 return cVirtTicksToDeadline;
302}
303
304
305/**
306 * tmVirtualSyncGetLocked worker for handling catch-up when owning the lock.
307 *
308 * @returns The timestamp.
309 * @param pVM The cross context VM structure.
310 * @param u64 raw virtual time.
311 * @param off offVirtualSync.
312 * @param pcNsToDeadline Where to return the number of nano seconds to
313 * the next virtual sync timer deadline. Can be
314 * NULL.
315 */
316DECLINLINE(uint64_t) tmVirtualSyncGetHandleCatchUpLocked(PVM pVM, uint64_t u64, uint64_t off, uint64_t *pcNsToDeadline)
317{
318 STAM_COUNTER_INC(&pVM->tm.s.StatVirtualSyncGetLocked);
319
320 /*
321 * Don't make updates until we've check the timer queue.
322 */
323 bool fUpdatePrev = true;
324 bool fUpdateOff = true;
325 bool fStop = false;
326 const uint64_t u64Prev = pVM->tm.s.u64VirtualSyncCatchUpPrev;
327 uint64_t u64Delta = u64 - u64Prev;
328 if (RT_LIKELY(!(u64Delta >> 32)))
329 {
330 uint64_t u64Sub = ASMMultU64ByU32DivByU32(u64Delta, pVM->tm.s.u32VirtualSyncCatchUpPercentage, 100);
331 if (off > u64Sub + pVM->tm.s.offVirtualSyncGivenUp)
332 {
333 off -= u64Sub;
334 Log4(("TM: %'RU64/-%'8RU64: sub %RU32 [vsghcul]\n", u64 - off, off - pVM->tm.s.offVirtualSyncGivenUp, u64Sub));
335 }
336 else
337 {
338 /* we've completely caught up. */
339 STAM_PROFILE_ADV_STOP(&pVM->tm.s.StatVirtualSyncCatchup, c);
340 off = pVM->tm.s.offVirtualSyncGivenUp;
341 fStop = true;
342 Log4(("TM: %'RU64/0: caught up [vsghcul]\n", u64));
343 }
344 }
345 else
346 {
347 /* More than 4 seconds since last time (or negative), ignore it. */
348 fUpdateOff = false;
349 fUpdatePrev = !(u64Delta & RT_BIT_64(63));
350 Log(("TMVirtualGetSync: u64Delta=%RX64\n", u64Delta));
351 }
352
353 /*
354 * Complete the calculation of the current TMCLOCK_VIRTUAL_SYNC time. The current
355 * approach is to never pass the head timer. So, when we do stop the clock and
356 * set the timer pending flag.
357 */
358 u64 -= off;
359
360 uint64_t u64Last = ASMAtomicUoReadU64(&pVM->tm.s.u64VirtualSync);
361 if (u64Last > u64)
362 {
363 u64 = u64Last + 1;
364 STAM_COUNTER_INC(&pVM->tm.s.StatVirtualSyncGetAdjLast);
365 }
366
367 uint64_t u64Expire = ASMAtomicReadU64(&pVM->tm.s.CTX_SUFF(paTimerQueues)[TMCLOCK_VIRTUAL_SYNC].u64Expire);
368 if (u64 < u64Expire)
369 {
370 ASMAtomicWriteU64(&pVM->tm.s.u64VirtualSync, u64);
371 if (fUpdateOff)
372 ASMAtomicWriteU64(&pVM->tm.s.offVirtualSync, off);
373 if (fStop)
374 ASMAtomicWriteBool(&pVM->tm.s.fVirtualSyncCatchUp, false);
375 if (fUpdatePrev)
376 ASMAtomicWriteU64(&pVM->tm.s.u64VirtualSyncCatchUpPrev, u64);
377 if (pcNsToDeadline)
378 {
379 uint64_t cNsToDeadline = u64Expire - u64;
380 if (pVM->tm.s.fVirtualSyncCatchUp)
381 cNsToDeadline = ASMMultU64ByU32DivByU32(cNsToDeadline, 100,
382 pVM->tm.s.u32VirtualSyncCatchUpPercentage + 100);
383 *pcNsToDeadline = tmVirtualVirtToNsDeadline(pVM, cNsToDeadline);
384 }
385 PDMCritSectLeave(&pVM->tm.s.VirtualSyncLock);
386 }
387 else
388 {
389 u64 = u64Expire;
390 ASMAtomicWriteU64(&pVM->tm.s.u64VirtualSync, u64);
391 ASMAtomicWriteBool(&pVM->tm.s.fVirtualSyncTicking, false);
392
393 VM_FF_SET(pVM, VM_FF_TM_VIRTUAL_SYNC);
394 PVMCPU pVCpuDst = &pVM->aCpus[pVM->tm.s.idTimerCpu];
395 VMCPU_FF_SET(pVCpuDst, VMCPU_FF_TIMER);
396 Log5(("TMAllVirtual(%u): FF: %d -> 1\n", __LINE__, VMCPU_FF_IS_PENDING(pVCpuDst, VMCPU_FF_TIMER)));
397 Log4(("TM: %'RU64/-%'8RU64: exp tmr=>ff [vsghcul]\n", u64, pVM->tm.s.offVirtualSync - pVM->tm.s.offVirtualSyncGivenUp));
398 PDMCritSectLeave(&pVM->tm.s.VirtualSyncLock);
399
400 if (pcNsToDeadline)
401 *pcNsToDeadline = 0;
402#ifdef IN_RING3
403# ifdef VBOX_WITH_REM
404 REMR3NotifyTimerPending(pVM, pVCpuDst);
405# endif
406 VMR3NotifyCpuFFU(pVCpuDst->pUVCpu, VMNOTIFYFF_FLAGS_DONE_REM);
407#endif
408 STAM_COUNTER_INC(&pVM->tm.s.StatVirtualSyncGetSetFF);
409 STAM_COUNTER_INC(&pVM->tm.s.StatVirtualSyncGetExpired);
410 }
411 STAM_COUNTER_INC(&pVM->tm.s.StatVirtualSyncGetLocked);
412
413 Log6(("tmVirtualSyncGetHandleCatchUpLocked -> %'RU64\n", u64));
414 DBGFTRACE_U64_TAG(pVM, u64, "tmVirtualSyncGetHandleCatchUpLocked");
415 return u64;
416}
417
418
419/**
420 * tmVirtualSyncGetEx worker for when we get the lock.
421 *
422 * @returns timesamp.
423 * @param pVM The cross context VM structure.
424 * @param u64 The virtual clock timestamp.
425 * @param pcNsToDeadline Where to return the number of nano seconds to
426 * the next virtual sync timer deadline. Can be
427 * NULL.
428 */
429DECLINLINE(uint64_t) tmVirtualSyncGetLocked(PVM pVM, uint64_t u64, uint64_t *pcNsToDeadline)
430{
431 /*
432 * Not ticking?
433 */
434 if (!pVM->tm.s.fVirtualSyncTicking)
435 {
436 u64 = ASMAtomicUoReadU64(&pVM->tm.s.u64VirtualSync);
437 PDMCritSectLeave(&pVM->tm.s.VirtualSyncLock);
438 if (pcNsToDeadline)
439 *pcNsToDeadline = 0;
440 STAM_COUNTER_INC(&pVM->tm.s.StatVirtualSyncGetLocked);
441 Log6(("tmVirtualSyncGetLocked -> %'RU64 [stopped]\n", u64));
442 DBGFTRACE_U64_TAG(pVM, u64, "tmVirtualSyncGetLocked-stopped");
443 return u64;
444 }
445
446 /*
447 * Handle catch up in a separate function.
448 */
449 uint64_t off = ASMAtomicUoReadU64(&pVM->tm.s.offVirtualSync);
450 if (ASMAtomicUoReadBool(&pVM->tm.s.fVirtualSyncCatchUp))
451 return tmVirtualSyncGetHandleCatchUpLocked(pVM, u64, off, pcNsToDeadline);
452
453 /*
454 * Complete the calculation of the current TMCLOCK_VIRTUAL_SYNC time. The current
455 * approach is to never pass the head timer. So, when we do stop the clock and
456 * set the timer pending flag.
457 */
458 u64 -= off;
459
460 uint64_t u64Last = ASMAtomicUoReadU64(&pVM->tm.s.u64VirtualSync);
461 if (u64Last > u64)
462 {
463 u64 = u64Last + 1;
464 STAM_COUNTER_INC(&pVM->tm.s.StatVirtualSyncGetAdjLast);
465 }
466
467 uint64_t u64Expire = ASMAtomicReadU64(&pVM->tm.s.CTX_SUFF(paTimerQueues)[TMCLOCK_VIRTUAL_SYNC].u64Expire);
468 if (u64 < u64Expire)
469 {
470 ASMAtomicWriteU64(&pVM->tm.s.u64VirtualSync, u64);
471 PDMCritSectLeave(&pVM->tm.s.VirtualSyncLock);
472 if (pcNsToDeadline)
473 *pcNsToDeadline = tmVirtualVirtToNsDeadline(pVM, u64Expire - u64);
474 }
475 else
476 {
477 u64 = u64Expire;
478 ASMAtomicWriteU64(&pVM->tm.s.u64VirtualSync, u64);
479 ASMAtomicWriteBool(&pVM->tm.s.fVirtualSyncTicking, false);
480
481 VM_FF_SET(pVM, VM_FF_TM_VIRTUAL_SYNC);
482 PVMCPU pVCpuDst = &pVM->aCpus[pVM->tm.s.idTimerCpu];
483 VMCPU_FF_SET(pVCpuDst, VMCPU_FF_TIMER);
484 Log5(("TMAllVirtual(%u): FF: %d -> 1\n", __LINE__, !!VMCPU_FF_IS_PENDING(pVCpuDst, VMCPU_FF_TIMER)));
485 Log4(("TM: %'RU64/-%'8RU64: exp tmr=>ff [vsgl]\n", u64, pVM->tm.s.offVirtualSync - pVM->tm.s.offVirtualSyncGivenUp));
486 PDMCritSectLeave(&pVM->tm.s.VirtualSyncLock);
487
488#ifdef IN_RING3
489# ifdef VBOX_WITH_REM
490 REMR3NotifyTimerPending(pVM, pVCpuDst);
491# endif
492 VMR3NotifyCpuFFU(pVCpuDst->pUVCpu, VMNOTIFYFF_FLAGS_DONE_REM);
493#endif
494 if (pcNsToDeadline)
495 *pcNsToDeadline = 0;
496 STAM_COUNTER_INC(&pVM->tm.s.StatVirtualSyncGetSetFF);
497 STAM_COUNTER_INC(&pVM->tm.s.StatVirtualSyncGetExpired);
498 }
499 STAM_COUNTER_INC(&pVM->tm.s.StatVirtualSyncGetLocked);
500 Log6(("tmVirtualSyncGetLocked -> %'RU64\n", u64));
501 DBGFTRACE_U64_TAG(pVM, u64, "tmVirtualSyncGetLocked");
502 return u64;
503}
504
505
506/**
507 * Gets the current TMCLOCK_VIRTUAL_SYNC time.
508 *
509 * @returns The timestamp.
510 * @param pVM The cross context VM structure.
511 * @param fCheckTimers Check timers or not
512 * @param pcNsToDeadline Where to return the number of nano seconds to
513 * the next virtual sync timer deadline. Can be
514 * NULL.
515 * @thread EMT.
516 */
517DECLINLINE(uint64_t) tmVirtualSyncGetEx(PVM pVM, bool fCheckTimers, uint64_t *pcNsToDeadline)
518{
519 STAM_COUNTER_INC(&pVM->tm.s.StatVirtualSyncGet);
520
521 uint64_t u64;
522 if (!pVM->tm.s.fVirtualSyncTicking)
523 {
524 if (pcNsToDeadline)
525 *pcNsToDeadline = 0;
526 u64 = pVM->tm.s.u64VirtualSync;
527 DBGFTRACE_U64_TAG(pVM, u64, "tmVirtualSyncGetEx-stopped1");
528 return u64;
529 }
530
531 /*
532 * Query the virtual clock and do the usual expired timer check.
533 */
534 Assert(pVM->tm.s.cVirtualTicking);
535 u64 = tmVirtualGetRaw(pVM);
536 if (fCheckTimers)
537 {
538 PVMCPU pVCpuDst = &pVM->aCpus[pVM->tm.s.idTimerCpu];
539 if ( !VMCPU_FF_IS_SET(pVCpuDst, VMCPU_FF_TIMER)
540 && pVM->tm.s.CTX_SUFF(paTimerQueues)[TMCLOCK_VIRTUAL].u64Expire <= u64)
541 {
542 Log5(("TMAllVirtual(%u): FF: 0 -> 1\n", __LINE__));
543 VMCPU_FF_SET(pVCpuDst, VMCPU_FF_TIMER);
544#ifdef IN_RING3
545# ifdef VBOX_WITH_REM
546 REMR3NotifyTimerPending(pVM, pVCpuDst);
547# endif
548 VMR3NotifyCpuFFU(pVCpuDst->pUVCpu, VMNOTIFYFF_FLAGS_DONE_REM /** @todo |VMNOTIFYFF_FLAGS_POKE*/);
549#endif
550 STAM_COUNTER_INC(&pVM->tm.s.StatVirtualSyncGetSetFF);
551 }
552 }
553
554 /*
555 * If we can get the lock, get it. The result is much more reliable.
556 *
557 * Note! This is where all clock source devices branch off because they
558 * will be owning the lock already. The 'else' is taken by code
559 * which is less picky or hasn't been adjusted yet
560 */
561 if (PDMCritSectTryEnter(&pVM->tm.s.VirtualSyncLock) == VINF_SUCCESS)
562 return tmVirtualSyncGetLocked(pVM, u64, pcNsToDeadline);
563
564 /*
565 * When the clock is ticking, not doing catch ups and not running into an
566 * expired time, we can get away without locking. Try this first.
567 */
568 uint64_t off;
569 if (ASMAtomicUoReadBool(&pVM->tm.s.fVirtualSyncTicking))
570 {
571 if (!ASMAtomicUoReadBool(&pVM->tm.s.fVirtualSyncCatchUp))
572 {
573 off = ASMAtomicReadU64(&pVM->tm.s.offVirtualSync);
574 if (RT_LIKELY( ASMAtomicUoReadBool(&pVM->tm.s.fVirtualSyncTicking)
575 && !ASMAtomicUoReadBool(&pVM->tm.s.fVirtualSyncCatchUp)
576 && off == ASMAtomicReadU64(&pVM->tm.s.offVirtualSync)))
577 {
578 off = u64 - off;
579 uint64_t const u64Expire = ASMAtomicReadU64(&pVM->tm.s.CTX_SUFF(paTimerQueues)[TMCLOCK_VIRTUAL_SYNC].u64Expire);
580 if (off < u64Expire)
581 {
582 if (pcNsToDeadline)
583 *pcNsToDeadline = tmVirtualVirtToNsDeadline(pVM, u64Expire - off);
584 STAM_COUNTER_INC(&pVM->tm.s.StatVirtualSyncGetLockless);
585 Log6(("tmVirtualSyncGetEx -> %'RU64 [lockless]\n", off));
586 DBGFTRACE_U64_TAG(pVM, off, "tmVirtualSyncGetEx-lockless");
587 return off;
588 }
589 }
590 }
591 }
592 else
593 {
594 off = ASMAtomicReadU64(&pVM->tm.s.u64VirtualSync);
595 if (RT_LIKELY(!ASMAtomicReadBool(&pVM->tm.s.fVirtualSyncTicking)))
596 {
597 if (pcNsToDeadline)
598 *pcNsToDeadline = 0;
599 STAM_COUNTER_INC(&pVM->tm.s.StatVirtualSyncGetLockless);
600 Log6(("tmVirtualSyncGetEx -> %'RU64 [lockless/stopped]\n", off));
601 DBGFTRACE_U64_TAG(pVM, off, "tmVirtualSyncGetEx-stopped2");
602 return off;
603 }
604 }
605
606 /*
607 * Read the offset and adjust if we're playing catch-up.
608 *
609 * The catch-up adjusting work by us decrementing the offset by a percentage of
610 * the time elapsed since the previous TMVirtualGetSync call.
611 *
612 * It's possible to get a very long or even negative interval between two read
613 * for the following reasons:
614 * - Someone might have suspended the process execution, frequently the case when
615 * debugging the process.
616 * - We might be on a different CPU which TSC isn't quite in sync with the
617 * other CPUs in the system.
618 * - Another thread is racing us and we might have been preempted while inside
619 * this function.
620 *
621 * Assuming nano second virtual time, we can simply ignore any intervals which has
622 * any of the upper 32 bits set.
623 */
624 AssertCompile(TMCLOCK_FREQ_VIRTUAL == 1000000000);
625 int cOuterTries = 42;
626 for (;; cOuterTries--)
627 {
628 /* Try grab the lock, things get simpler when owning the lock. */
629 int rcLock = PDMCritSectTryEnter(&pVM->tm.s.VirtualSyncLock);
630 if (RT_SUCCESS_NP(rcLock))
631 return tmVirtualSyncGetLocked(pVM, u64, pcNsToDeadline);
632
633 /* Re-check the ticking flag. */
634 if (!ASMAtomicReadBool(&pVM->tm.s.fVirtualSyncTicking))
635 {
636 off = ASMAtomicReadU64(&pVM->tm.s.u64VirtualSync);
637 if ( ASMAtomicReadBool(&pVM->tm.s.fVirtualSyncTicking)
638 && cOuterTries > 0)
639 continue;
640 if (pcNsToDeadline)
641 *pcNsToDeadline = 0;
642 Log6(("tmVirtualSyncGetEx -> %'RU64 [stopped]\n", off));
643 DBGFTRACE_U64_TAG(pVM, off, "tmVirtualSyncGetEx-stopped3");
644 return off;
645 }
646
647 off = ASMAtomicReadU64(&pVM->tm.s.offVirtualSync);
648 if (ASMAtomicReadBool(&pVM->tm.s.fVirtualSyncCatchUp))
649 {
650 /* No changes allowed, try get a consistent set of parameters. */
651 uint64_t const u64Prev = ASMAtomicReadU64(&pVM->tm.s.u64VirtualSyncCatchUpPrev);
652 uint64_t const offGivenUp = ASMAtomicReadU64(&pVM->tm.s.offVirtualSyncGivenUp);
653 uint32_t const u32Pct = ASMAtomicReadU32(&pVM->tm.s.u32VirtualSyncCatchUpPercentage);
654 if ( ( u64Prev == ASMAtomicReadU64(&pVM->tm.s.u64VirtualSyncCatchUpPrev)
655 && offGivenUp == ASMAtomicReadU64(&pVM->tm.s.offVirtualSyncGivenUp)
656 && u32Pct == ASMAtomicReadU32(&pVM->tm.s.u32VirtualSyncCatchUpPercentage)
657 && ASMAtomicReadBool(&pVM->tm.s.fVirtualSyncCatchUp))
658 || cOuterTries <= 0)
659 {
660 uint64_t u64Delta = u64 - u64Prev;
661 if (RT_LIKELY(!(u64Delta >> 32)))
662 {
663 uint64_t u64Sub = ASMMultU64ByU32DivByU32(u64Delta, u32Pct, 100);
664 if (off > u64Sub + offGivenUp)
665 {
666 off -= u64Sub;
667 Log4(("TM: %'RU64/-%'8RU64: sub %RU32 [NoLock]\n", u64 - off, pVM->tm.s.offVirtualSync - offGivenUp, u64Sub));
668 }
669 else
670 {
671 /* we've completely caught up. */
672 STAM_PROFILE_ADV_STOP(&pVM->tm.s.StatVirtualSyncCatchup, c);
673 off = offGivenUp;
674 Log4(("TM: %'RU64/0: caught up [NoLock]\n", u64));
675 }
676 }
677 else
678 /* More than 4 seconds since last time (or negative), ignore it. */
679 Log(("TMVirtualGetSync: u64Delta=%RX64 (NoLock)\n", u64Delta));
680
681 /* Check that we're still running and in catch up. */
682 if ( ASMAtomicUoReadBool(&pVM->tm.s.fVirtualSyncTicking)
683 && ASMAtomicReadBool(&pVM->tm.s.fVirtualSyncCatchUp))
684 break;
685 if (cOuterTries <= 0)
686 break; /* enough */
687 }
688 }
689 else if ( off == ASMAtomicReadU64(&pVM->tm.s.offVirtualSync)
690 && !ASMAtomicReadBool(&pVM->tm.s.fVirtualSyncCatchUp))
691 break; /* Got an consistent offset */
692 else if (cOuterTries <= 0)
693 break; /* enough */
694 }
695 if (cOuterTries <= 0)
696 STAM_COUNTER_INC(&pVM->tm.s.StatVirtualSyncGetELoop);
697
698 /*
699 * Complete the calculation of the current TMCLOCK_VIRTUAL_SYNC time. The current
700 * approach is to never pass the head timer. So, when we do stop the clock and
701 * set the timer pending flag.
702 */
703 u64 -= off;
704/** @todo u64VirtualSyncLast */
705 uint64_t u64Expire = ASMAtomicReadU64(&pVM->tm.s.CTX_SUFF(paTimerQueues)[TMCLOCK_VIRTUAL_SYNC].u64Expire);
706 if (u64 >= u64Expire)
707 {
708 PVMCPU pVCpuDst = &pVM->aCpus[pVM->tm.s.idTimerCpu];
709 if (!VMCPU_FF_IS_SET(pVCpuDst, VMCPU_FF_TIMER))
710 {
711 Log5(("TMAllVirtual(%u): FF: %d -> 1 (NoLock)\n", __LINE__, VMCPU_FF_IS_PENDING(pVCpuDst, VMCPU_FF_TIMER)));
712 VM_FF_SET(pVM, VM_FF_TM_VIRTUAL_SYNC); /* Hmm? */
713 VMCPU_FF_SET(pVCpuDst, VMCPU_FF_TIMER);
714#ifdef IN_RING3
715# ifdef VBOX_WITH_REM
716 REMR3NotifyTimerPending(pVM, pVCpuDst);
717# endif
718 VMR3NotifyCpuFFU(pVCpuDst->pUVCpu, VMNOTIFYFF_FLAGS_DONE_REM);
719#endif
720 STAM_COUNTER_INC(&pVM->tm.s.StatVirtualSyncGetSetFF);
721 Log4(("TM: %'RU64/-%'8RU64: exp tmr=>ff [NoLock]\n", u64, pVM->tm.s.offVirtualSync - pVM->tm.s.offVirtualSyncGivenUp));
722 }
723 else
724 Log4(("TM: %'RU64/-%'8RU64: exp tmr [NoLock]\n", u64, pVM->tm.s.offVirtualSync - pVM->tm.s.offVirtualSyncGivenUp));
725 if (pcNsToDeadline)
726 *pcNsToDeadline = 0;
727 STAM_COUNTER_INC(&pVM->tm.s.StatVirtualSyncGetExpired);
728 }
729 else if (pcNsToDeadline)
730 {
731 uint64_t cNsToDeadline = u64Expire - u64;
732 if (ASMAtomicReadBool(&pVM->tm.s.fVirtualSyncCatchUp))
733 cNsToDeadline = ASMMultU64ByU32DivByU32(cNsToDeadline, 100,
734 ASMAtomicReadU32(&pVM->tm.s.u32VirtualSyncCatchUpPercentage) + 100);
735 *pcNsToDeadline = tmVirtualVirtToNsDeadline(pVM, cNsToDeadline);
736 }
737
738 Log6(("tmVirtualSyncGetEx -> %'RU64\n", u64));
739 DBGFTRACE_U64_TAG(pVM, u64, "tmVirtualSyncGetEx-nolock");
740 return u64;
741}
742
743
744/**
745 * Gets the current TMCLOCK_VIRTUAL_SYNC time.
746 *
747 * @returns The timestamp.
748 * @param pVM The cross context VM structure.
749 * @thread EMT.
750 * @remarks May set the timer and virtual sync FFs.
751 */
752VMM_INT_DECL(uint64_t) TMVirtualSyncGet(PVM pVM)
753{
754 return tmVirtualSyncGetEx(pVM, true /*fCheckTimers*/, NULL /*pcNsToDeadline*/);
755}
756
757
758/**
759 * Gets the current TMCLOCK_VIRTUAL_SYNC time without checking timers running on
760 * TMCLOCK_VIRTUAL.
761 *
762 * @returns The timestamp.
763 * @param pVM The cross context VM structure.
764 * @thread EMT.
765 * @remarks May set the timer and virtual sync FFs.
766 */
767VMM_INT_DECL(uint64_t) TMVirtualSyncGetNoCheck(PVM pVM)
768{
769 return tmVirtualSyncGetEx(pVM, false /*fCheckTimers*/, NULL /*pcNsToDeadline*/);
770}
771
772
773/**
774 * Gets the current TMCLOCK_VIRTUAL_SYNC time.
775 *
776 * @returns The timestamp.
777 * @param pVM The cross context VM structure.
778 * @param fCheckTimers Check timers on the virtual clock or not.
779 * @thread EMT.
780 * @remarks May set the timer and virtual sync FFs.
781 */
782VMM_INT_DECL(uint64_t) TMVirtualSyncGetEx(PVM pVM, bool fCheckTimers)
783{
784 return tmVirtualSyncGetEx(pVM, fCheckTimers, NULL /*pcNsToDeadline*/);
785}
786
787
788/**
789 * Gets the current TMCLOCK_VIRTUAL_SYNC time and ticks to the next deadline
790 * without checking timers running on TMCLOCK_VIRTUAL.
791 *
792 * @returns The timestamp.
793 * @param pVM The cross context VM structure.
794 * @param pcNsToDeadline Where to return the number of nano seconds to
795 * the next virtual sync timer deadline.
796 * @thread EMT.
797 * @remarks May set the timer and virtual sync FFs.
798 */
799VMM_INT_DECL(uint64_t) TMVirtualSyncGetWithDeadlineNoCheck(PVM pVM, uint64_t *pcNsToDeadline)
800{
801 uint64_t cNsToDeadlineTmp; /* try convince the compiler to skip the if tests. */
802 uint64_t u64Now = tmVirtualSyncGetEx(pVM, false /*fCheckTimers*/, &cNsToDeadlineTmp);
803 *pcNsToDeadline = cNsToDeadlineTmp;
804 return u64Now;
805}
806
807
808/**
809 * Gets the number of nano seconds to the next virtual sync deadline.
810 *
811 * @returns The number of TMCLOCK_VIRTUAL ticks.
812 * @param pVM The cross context VM structure.
813 * @thread EMT.
814 * @remarks May set the timer and virtual sync FFs.
815 */
816VMMDECL(uint64_t) TMVirtualSyncGetNsToDeadline(PVM pVM)
817{
818 uint64_t cNsToDeadline;
819 tmVirtualSyncGetEx(pVM, false /*fCheckTimers*/, &cNsToDeadline);
820 return cNsToDeadline;
821}
822
823
824/**
825 * Gets the current lag of the synchronous virtual clock (relative to the virtual clock).
826 *
827 * @return The current lag.
828 * @param pVM The cross context VM structure.
829 */
830VMM_INT_DECL(uint64_t) TMVirtualSyncGetLag(PVM pVM)
831{
832 return pVM->tm.s.offVirtualSync - pVM->tm.s.offVirtualSyncGivenUp;
833}
834
835
836/**
837 * Get the current catch-up percent.
838 *
839 * @return The current catch0up percent. 0 means running at the same speed as the virtual clock.
840 * @param pVM The cross context VM structure.
841 */
842VMM_INT_DECL(uint32_t) TMVirtualSyncGetCatchUpPct(PVM pVM)
843{
844 if (pVM->tm.s.fVirtualSyncCatchUp)
845 return pVM->tm.s.u32VirtualSyncCatchUpPercentage;
846 return 0;
847}
848
849
850/**
851 * Gets the current TMCLOCK_VIRTUAL frequency.
852 *
853 * @returns The frequency.
854 * @param pVM The cross context VM structure.
855 */
856VMM_INT_DECL(uint64_t) TMVirtualGetFreq(PVM pVM)
857{
858 NOREF(pVM);
859 return TMCLOCK_FREQ_VIRTUAL;
860}
861
862
863/**
864 * Worker for TMR3PauseClocks.
865 *
866 * @returns VINF_SUCCESS or VERR_TM_VIRTUAL_TICKING_IPE (asserted).
867 * @param pVM The cross context VM structure.
868 */
869int tmVirtualPauseLocked(PVM pVM)
870{
871 uint32_t c = ASMAtomicDecU32(&pVM->tm.s.cVirtualTicking);
872 AssertMsgReturn(c < pVM->cCpus, ("%u vs %u\n", c, pVM->cCpus), VERR_TM_VIRTUAL_TICKING_IPE);
873 if (c == 0)
874 {
875 STAM_COUNTER_INC(&pVM->tm.s.StatVirtualPause);
876 pVM->tm.s.u64Virtual = tmVirtualGetRaw(pVM);
877 ASMAtomicWriteBool(&pVM->tm.s.fVirtualSyncTicking, false);
878 }
879 return VINF_SUCCESS;
880}
881
882
883/**
884 * Worker for TMR3ResumeClocks.
885 *
886 * @returns VINF_SUCCESS or VERR_TM_VIRTUAL_TICKING_IPE (asserted).
887 * @param pVM The cross context VM structure.
888 */
889int tmVirtualResumeLocked(PVM pVM)
890{
891 uint32_t c = ASMAtomicIncU32(&pVM->tm.s.cVirtualTicking);
892 AssertMsgReturn(c <= pVM->cCpus, ("%u vs %u\n", c, pVM->cCpus), VERR_TM_VIRTUAL_TICKING_IPE);
893 if (c == 1)
894 {
895 STAM_COUNTER_INC(&pVM->tm.s.StatVirtualResume);
896 pVM->tm.s.u64VirtualRawPrev = 0;
897 pVM->tm.s.u64VirtualWarpDriveStart = tmVirtualGetRawNanoTS(pVM);
898 pVM->tm.s.u64VirtualOffset = pVM->tm.s.u64VirtualWarpDriveStart - pVM->tm.s.u64Virtual;
899 ASMAtomicWriteBool(&pVM->tm.s.fVirtualSyncTicking, true);
900 }
901 return VINF_SUCCESS;
902}
903
904
905/**
906 * Converts from virtual ticks to nanoseconds.
907 *
908 * @returns nanoseconds.
909 * @param pVM The cross context VM structure.
910 * @param u64VirtualTicks The virtual ticks to convert.
911 * @remark There could be rounding errors here. We just do a simple integer divide
912 * without any adjustments.
913 */
914VMM_INT_DECL(uint64_t) TMVirtualToNano(PVM pVM, uint64_t u64VirtualTicks)
915{
916 NOREF(pVM);
917 AssertCompile(TMCLOCK_FREQ_VIRTUAL == 1000000000);
918 return u64VirtualTicks;
919}
920
921
922/**
923 * Converts from virtual ticks to microseconds.
924 *
925 * @returns microseconds.
926 * @param pVM The cross context VM structure.
927 * @param u64VirtualTicks The virtual ticks to convert.
928 * @remark There could be rounding errors here. We just do a simple integer divide
929 * without any adjustments.
930 */
931VMM_INT_DECL(uint64_t) TMVirtualToMicro(PVM pVM, uint64_t u64VirtualTicks)
932{
933 NOREF(pVM);
934 AssertCompile(TMCLOCK_FREQ_VIRTUAL == 1000000000);
935 return u64VirtualTicks / 1000;
936}
937
938
939/**
940 * Converts from virtual ticks to milliseconds.
941 *
942 * @returns milliseconds.
943 * @param pVM The cross context VM structure.
944 * @param u64VirtualTicks The virtual ticks to convert.
945 * @remark There could be rounding errors here. We just do a simple integer divide
946 * without any adjustments.
947 */
948VMM_INT_DECL(uint64_t) TMVirtualToMilli(PVM pVM, uint64_t u64VirtualTicks)
949{
950 NOREF(pVM);
951 AssertCompile(TMCLOCK_FREQ_VIRTUAL == 1000000000);
952 return u64VirtualTicks / 1000000;
953}
954
955
956/**
957 * Converts from nanoseconds to virtual ticks.
958 *
959 * @returns virtual ticks.
960 * @param pVM The cross context VM structure.
961 * @param u64NanoTS The nanosecond value ticks to convert.
962 * @remark There could be rounding and overflow errors here.
963 */
964VMM_INT_DECL(uint64_t) TMVirtualFromNano(PVM pVM, uint64_t u64NanoTS)
965{
966 NOREF(pVM);
967 AssertCompile(TMCLOCK_FREQ_VIRTUAL == 1000000000);
968 return u64NanoTS;
969}
970
971
972/**
973 * Converts from microseconds to virtual ticks.
974 *
975 * @returns virtual ticks.
976 * @param pVM The cross context VM structure.
977 * @param u64MicroTS The microsecond value ticks to convert.
978 * @remark There could be rounding and overflow errors here.
979 */
980VMM_INT_DECL(uint64_t) TMVirtualFromMicro(PVM pVM, uint64_t u64MicroTS)
981{
982 NOREF(pVM);
983 AssertCompile(TMCLOCK_FREQ_VIRTUAL == 1000000000);
984 return u64MicroTS * 1000;
985}
986
987
988/**
989 * Converts from milliseconds to virtual ticks.
990 *
991 * @returns virtual ticks.
992 * @param pVM The cross context VM structure.
993 * @param u64MilliTS The millisecond value ticks to convert.
994 * @remark There could be rounding and overflow errors here.
995 */
996VMM_INT_DECL(uint64_t) TMVirtualFromMilli(PVM pVM, uint64_t u64MilliTS)
997{
998 NOREF(pVM);
999 AssertCompile(TMCLOCK_FREQ_VIRTUAL == 1000000000);
1000 return u64MilliTS * 1000000;
1001}
1002
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