1 /*
2  * tclWinTime.c --
3  *
4  *	Contains Windows specific versions of Tcl functions that
5  *	obtain time values from the operating system.
6  *
7  * Copyright 1995-1998 by Sun Microsystems, Inc.
8  *
9  * See the file "license.terms" for information on usage and redistribution
10  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
11  *
12  * RCS: @(#) $Id: tclWinTime.c,v 1.14.2.2 2003/04/15 21:06:36 kennykb Exp $
13  */
14 
15 #include <windows.h>
16 #include "Lang.h"
17 #include <sys/timeb.h>
18 #include "tkWin.h"
19 
20 #if defined(TCL_EVENT_IMPLEMENT)
21 
22 #define SECSPERDAY (60L * 60L * 24L)
23 #define SECSPERYEAR (SECSPERDAY * 365L)
24 #define SECSPER4YEAR (SECSPERYEAR * 4L + SECSPERDAY)
25 
26 /*
27  * Number of samples over which to estimate the performance counter
28  */
29 #define SAMPLES 64
30 
31 /*
32  * The following arrays contain the day of year for the last day of
33  * each month, where index 1 is January.
34  */
35 
36 static int normalDays[] = {
37     -1, 30, 58, 89, 119, 150, 180, 211, 242, 272, 303, 333, 364
38 };
39 
40 static int leapDays[] = {
41     -1, 30, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365
42 };
43 
44 typedef struct ThreadSpecificData {
45     char tzName[64];		/* Time zone name */
46     struct tm tm;		/* time information */
47 } ThreadSpecificData;
48 static Tcl_ThreadDataKey dataKey;
49 
50 /*
51  * Data for managing high-resolution timers.
52  */
53 
54 typedef struct TimeInfo {
55 
56     CRITICAL_SECTION cs;	/* Mutex guarding this structure */
57 
58     int initialized;		/* Flag == 1 if this structure is
59 				 * initialized. */
60 
61     int perfCounterAvailable;	/* Flag == 1 if the hardware has a
62 				 * performance counter */
63 
64     HANDLE calibrationThread;	/* Handle to the thread that keeps the
65 				 * virtual clock calibrated. */
66 
67     HANDLE readyEvent;		/* System event used to
68 				 * trigger the requesting thread
69 				 * when the clock calibration procedure
70 				 * is initialized for the first time */
71 
72     HANDLE exitEvent; 		/* Event to signal out of an exit handler
73 				 * to tell the calibration loop to
74 				 * terminate */
75 
76     LARGE_INTEGER nominalFreq;	/* Nominal frequency of the system
77 				 * performance counter, that is, the value
78 				 * returned from QueryPerformanceFrequency. */
79 
80     /*
81      * The following values are used for calculating virtual time.
82      * Virtual time is always equal to:
83      *    lastFileTime + (current perf counter - lastCounter)
84      *				* 10000000 / curCounterFreq
85      * and lastFileTime and lastCounter are updated any time that
86      * virtual time is returned to a caller.
87      */
88 
89     ULARGE_INTEGER fileTimeLastCall;
90     LARGE_INTEGER perfCounterLastCall;
91     LARGE_INTEGER curCounterFreq;
92 
93     /*
94      * Data used in developing the estimate of performance counter
95      * frequency
96      */
97     Tcl_WideUInt fileTimeSample[SAMPLES];
98 				/* Last 64 samples of system time */
99     Tcl_WideInt perfCounterSample[SAMPLES];
100 				/* Last 64 samples of performance counter */
101     int sampleNo;		/* Current sample number */
102 
103 
104 } TimeInfo;
105 
106 static TimeInfo timeInfo = {
107     { NULL },
108     0,
109     0,
110     (HANDLE) NULL,
111     (HANDLE) NULL,
112     (HANDLE) NULL,
113 #ifdef HAVE_CAST_TO_UNION
114     (LARGE_INTEGER) (Tcl_WideInt) 0,
115     (ULARGE_INTEGER) (DWORDLONG) 0,
116     (LARGE_INTEGER) (Tcl_WideInt) 0,
117     (LARGE_INTEGER) (Tcl_WideInt) 0,
118 #else
119     0,
120     0,
121     0,
122     0,
123 #endif
124     { 0 },
125     { 0 },
126     0
127 };
128 
129 CONST static FILETIME posixEpoch = { 0xD53E8000, 0x019DB1DE };
130 
131 /*
132  * Declarations for functions defined later in this file.
133  */
134 
135 static struct tm *	ComputeGMT _ANSI_ARGS_((const time_t *tp));
136 static void		StopCalibration _ANSI_ARGS_(( ClientData ));
137 static DWORD WINAPI     CalibrationThread _ANSI_ARGS_(( LPVOID arg ));
138 static void 		UpdateTimeEachSecond _ANSI_ARGS_(( void ));
139 static void		ResetCounterSamples _ANSI_ARGS_((
140 			    Tcl_WideUInt fileTime,
141                             Tcl_WideInt perfCounter,
142 			    Tcl_WideInt perfFreq
143 			));
144 static Tcl_WideInt		AccumulateSample _ANSI_ARGS_((
145 			    Tcl_WideInt perfCounter,
146 			    Tcl_WideUInt fileTime
147 			));
148 
149 /*
150  *----------------------------------------------------------------------
151  *
152  * TclpGetSeconds --
153  *
154  *	This procedure returns the number of seconds from the epoch.
155  *	On most Unix systems the epoch is Midnight Jan 1, 1970 GMT.
156  *
157  * Results:
158  *	Number of seconds from the epoch.
159  *
160  * Side effects:
161  *	None.
162  *
163  *----------------------------------------------------------------------
164  */
165 
166 unsigned long
TclpGetSeconds()167 TclpGetSeconds()
168 {
169     Tcl_Time t;
170     Tcl_GetTime( &t );
171     return t.sec;
172 }
173 
174 /*
175  *----------------------------------------------------------------------
176  *
177  * TclpGetClicks --
178  *
179  *	This procedure returns a value that represents the highest
180  *	resolution clock available on the system.  There are no
181  *	guarantees on what the resolution will be.  In Tcl we will
182  *	call this value a "click".  The start time is also system
183  *	dependant.
184  *
185  * Results:
186  *	Number of clicks from some start time.
187  *
188  * Side effects:
189  *	None.
190  *
191  *----------------------------------------------------------------------
192  */
193 
194 unsigned long
TclpGetClicks()195 TclpGetClicks()
196 {
197     /*
198      * Use the Tcl_GetTime abstraction to get the time in microseconds,
199      * as nearly as we can, and return it.
200      */
201 
202     Tcl_Time now;		/* Current Tcl time */
203     unsigned long retval;	/* Value to return */
204 
205     Tcl_GetTime( &now );
206     retval = ( now.sec * 1000000 ) + now.usec;
207     return retval;
208 
209 }
210 
211 #ifndef _LANG
212 /*
213  *----------------------------------------------------------------------
214  *
215  * TclpGetTimeZone --
216  *
217  *	Determines the current timezone.  The method varies wildly
218  *	between different Platform implementations, so its hidden in
219  *	this function.
220  *
221  * Results:
222  *	Minutes west of GMT.
223  *
224  * Side effects:
225  *	None.
226  *
227  *----------------------------------------------------------------------
228  */
229 
230 int
TclpGetTimeZone(currentTime)231 TclpGetTimeZone (currentTime)
232     unsigned long  currentTime;
233 {
234     int timeZone;
235 
236     tzset();
237     timeZone = _timezone / 60;
238 
239     return timeZone;
240 }
241 
242 #endif /* _LANG */
243 /*
244  *----------------------------------------------------------------------
245  *
246  * Tcl_GetTime --
247  *
248  *	Gets the current system time in seconds and microseconds
249  *	since the beginning of the epoch: 00:00 UCT, January 1, 1970.
250  *
251  * Results:
252  *	Returns the current time in timePtr.
253  *
254  * Side effects:
255  *	On the first call, initializes a set of static variables to
256  *	keep track of the base value of the performance counter, the
257  *	corresponding wall clock (obtained through ftime) and the
258  *	frequency of the performance counter.  Also spins a thread
259  *	whose function is to wake up periodically and monitor these
260  *	values, adjusting them as necessary to correct for drift
261  *	in the performance counter's oscillator.
262  *
263  *----------------------------------------------------------------------
264  */
265 
266 void
Tcl_GetTime(timePtr)267 Tcl_GetTime(timePtr)
268     Tcl_Time *timePtr;		/* Location to store time information. */
269 {
270 
271     struct timeb t;
272 
273     int useFtime = 1;		/* Flag == TRUE if we need to fall back
274 				 * on ftime rather than using the perf
275 				 * counter */
276 
277     /* Initialize static storage on the first trip through. */
278 
279     /*
280      * Note: Outer check for 'initialized' is a performance win
281      * since it avoids an extra mutex lock in the common case.
282      */
283 
284     if ( !timeInfo.initialized ) {
285 	TclpInitLock();
286 	if ( !timeInfo.initialized ) {
287 	    timeInfo.perfCounterAvailable
288 		= QueryPerformanceFrequency( &timeInfo.nominalFreq );
289 
290 	    /*
291 	     * Some hardware abstraction layers use the CPU clock
292 	     * in place of the real-time clock as a performance counter
293 	     * reference.  This results in:
294 	     *    - inconsistent results among the processors on
295 	     *      multi-processor systems.
296 	     *    - unpredictable changes in performance counter frequency
297 	     *      on "gearshift" processors such as Transmeta and
298 	     *      SpeedStep.
299 	     *
300 	     * There seems to be no way to test whether the performance
301 	     * counter is reliable, but a useful heuristic is that
302 	     * if its frequency is 1.193182 MHz or 3.579545 MHz, it's
303 	     * derived from a colorburst crystal and is therefore
304 	     * the RTC rather than the TSC.
305 	     *
306 	     * A sloppier but serviceable heuristic is that the RTC crystal
307 	     * is normally less than 15 MHz while the TSC crystal is
308 	     * virtually assured to be greater than 100 MHz.  Since Win98SE
309 	     * appears to fiddle with the definition of the perf counter
310 	     * frequency (perhaps in an attempt to calibrate the clock?)
311 	     * we use the latter rule rather than an exact match.
312 	     */
313 
314 	    if ( timeInfo.perfCounterAvailable
315 		 /* The following lines would do an exact match on
316 		  * crystal frequency:
317 		  * && timeInfo.nominalFreq.QuadPart != (Tcl_WideInt) 1193182
318 		  * && timeInfo.nominalFreq.QuadPart != (Tcl_WideInt) 3579545
319 		  */
320 		 && timeInfo.nominalFreq.QuadPart > (Tcl_WideInt) 15000000 ) {
321 		timeInfo.perfCounterAvailable = FALSE;
322 	    }
323 
324 	    /*
325 	     * If the performance counter is available, start a thread to
326 	     * calibrate it.
327 	     */
328 
329 	    if ( timeInfo.perfCounterAvailable ) {
330 		DWORD id;
331 		InitializeCriticalSection( &timeInfo.cs );
332 		timeInfo.readyEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
333 		timeInfo.exitEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
334 		timeInfo.calibrationThread = CreateThread( NULL,
335 							   256,
336 							   CalibrationThread,
337 							   (LPVOID) NULL,
338 							   0,
339 							   &id );
340 		SetThreadPriority( timeInfo.calibrationThread,
341 				   THREAD_PRIORITY_HIGHEST );
342 
343 		/*
344 		 * Wait for the thread just launched to start running,
345 		 * and create an exit handler that kills it so that it
346 		 * doesn't outlive unloading tclXX.dll
347 		 */
348 
349 		WaitForSingleObject( timeInfo.readyEvent, INFINITE );
350 		CloseHandle( timeInfo.readyEvent );
351 		Tcl_CreateExitHandler( StopCalibration, (ClientData) NULL );
352 	    }
353 	    timeInfo.initialized = TRUE;
354 	}
355 	TclpInitUnlock();
356     }
357 
358     if ( timeInfo.perfCounterAvailable ) {
359 
360 	/*
361 	 * Query the performance counter and use it to calculate the
362 	 * current time.
363 	 */
364 
365 	LARGE_INTEGER curCounter;
366 				/* Current performance counter */
367 
368 	Tcl_WideInt curFileTime;
369 				/* Current estimated time, expressed
370 				 * as 100-ns ticks since the Windows epoch */
371 
372 	static LARGE_INTEGER posixEpoch;
373 				/* Posix epoch expressed as 100-ns ticks
374 				 * since the windows epoch */
375 
376 	Tcl_WideInt usecSincePosixEpoch;
377 				/* Current microseconds since Posix epoch */
378 
379 	posixEpoch.LowPart = 0xD53E8000;
380 	posixEpoch.HighPart = 0x019DB1DE;
381 
382 	EnterCriticalSection( &timeInfo.cs );
383 
384 	QueryPerformanceCounter( &curCounter );
385 
386 	/*
387 	 * If it appears to be more than 1.1 seconds since the last trip
388 	 * through the calibration loop, the performance counter may
389 	 * have jumped forward. (See MSDN Knowledge Base article
390 	 * Q274323 for a description of the hardware problem that makes
391 	 * this test necessary.) If the counter jumps, we don't want
392 	 * to use it directly. Instead, we must return system time.
393 	 * Eventually, the calibration loop should recover.
394 	 */
395 	if ( curCounter.QuadPart - timeInfo.perfCounterLastCall.QuadPart
396 	     < 11 * timeInfo.curCounterFreq.QuadPart / 10 ) {
397 
398 	    curFileTime = timeInfo.fileTimeLastCall.QuadPart
399 		+ ( ( curCounter.QuadPart - timeInfo.perfCounterLastCall.QuadPart )
400 		    * 10000000 / timeInfo.curCounterFreq.QuadPart );
401 	    timeInfo.fileTimeLastCall.QuadPart = curFileTime;
402 	    timeInfo.perfCounterLastCall.QuadPart = curCounter.QuadPart;
403 	    usecSincePosixEpoch = ( curFileTime - posixEpoch.QuadPart ) / 10;
404 	    timePtr->sec = (time_t) ( usecSincePosixEpoch / 1000000 );
405 	    timePtr->usec = (unsigned long ) ( usecSincePosixEpoch % 1000000 );
406 	    useFtime = 0;
407 	}
408 
409 	LeaveCriticalSection( &timeInfo.cs );
410     }
411 
412     if ( useFtime ) {
413 
414 	/* High resolution timer is not available.  Just use ftime */
415 
416 	ftime(&t);
417 	timePtr->sec = t.time;
418 	timePtr->usec = t.millitm * 1000;
419     }
420 }
421 
422 /*
423  *----------------------------------------------------------------------
424  *
425  * StopCalibration --
426  *
427  *	Turns off the calibration thread in preparation for exiting the
428  *	process.
429  *
430  * Results:
431  *	None.
432  *
433  * Side effects:
434  *	Sets the 'exitEvent' event in the 'timeInfo' structure to ask
435  *	the thread in question to exit, and waits for it to do so.
436  *
437  *----------------------------------------------------------------------
438  */
439 
440 static void
StopCalibration(ClientData unused)441 StopCalibration( ClientData unused )
442 				/* Client data is unused */
443 {
444     SetEvent( timeInfo.exitEvent );
445     WaitForSingleObject( timeInfo.calibrationThread, INFINITE );
446     CloseHandle( timeInfo.exitEvent );
447     CloseHandle( timeInfo.calibrationThread );
448 }
449 
450 /*
451  *----------------------------------------------------------------------
452  *
453  * TclpGetTZName --
454  *
455  *	Gets the current timezone string.
456  *
457  * Results:
458  *	Returns a pointer to a static string, or NULL on failure.
459  *
460  * Side effects:
461  *	None.
462  *
463  *----------------------------------------------------------------------
464  */
465 
466 #ifndef _LANG
467 char *
TclpGetTZName(int dst)468 TclpGetTZName(int dst)
469 {
470     int len;
471     char *zone, *p;
472     TIME_ZONE_INFORMATION tz;
473     Tcl_Encoding encoding;
474     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
475     char *name = tsdPtr->tzName;
476 
477     /*
478      * tzset() under Borland doesn't seem to set up tzname[] at all.
479      * tzset() under MSVC has the following weird observed behavior:
480      *	 First time we call "clock format [clock seconds] -format %Z -gmt 1"
481      *	 we get "GMT", but on all subsequent calls we get the current time
482      *	 zone string, even though env(TZ) is GMT and the variable _timezone
483      *   is 0.
484      */
485 
486     name[0] = '\0';
487 
488     zone = getenv("TZ");
489     if (zone != NULL) {
490 	/*
491 	 * TZ is of form "NST-4:30NDT", where "NST" would be the
492 	 * name of the standard time zone for this area, "-4:30" is
493 	 * the offset from GMT in hours, and "NDT is the name of
494 	 * the daylight savings time zone in this area.  The offset
495 	 * and DST strings are optional.
496 	 */
497 
498 	len = strlen(zone);
499 	if (len > 3) {
500 	    len = 3;
501 	}
502 	if (dst != 0) {
503 	    /*
504 	     * Skip the offset string and get the DST string.
505 	     */
506 
507 	    p = zone + len;
508 	    p += strspn(p, "+-:0123456789");
509 	    if (*p != '\0') {
510 		zone = p;
511 		len = strlen(zone);
512 		if (len > 3) {
513 		    len = 3;
514 		}
515 	    }
516 	}
517 	Tcl_ExternalToUtf(NULL, NULL, zone, len, 0, NULL, name,
518 		sizeof(tsdPtr->tzName), NULL, NULL, NULL);
519     }
520     if (name[0] == '\0') {
521 	if (GetTimeZoneInformation(&tz) == TIME_ZONE_ID_UNKNOWN) {
522 	    /*
523 	     * MSDN: On NT this is returned if DST is not used in
524 	     * the current TZ
525 	     */
526 	    dst = 0;
527 	}
528 	encoding = Tcl_GetEncoding(NULL, "unicode");
529 	Tcl_ExternalToUtf(NULL, encoding,
530 		(char *) ((dst) ? tz.DaylightName : tz.StandardName), -1,
531 		0, NULL, name, sizeof(tsdPtr->tzName), NULL, NULL, NULL);
532 	Tcl_FreeEncoding(encoding);
533     }
534     return name;
535 }
536 #endif
537 
538 
539 #ifndef _LANG
540 /*
541  *----------------------------------------------------------------------
542  *
543  * TclpGetDate --
544  *
545  *	This function converts between seconds and struct tm.  If
546  *	useGMT is true, then the returned date will be in Greenwich
547  *	Mean Time (GMT).  Otherwise, it will be in the local time zone.
548  *
549  * Results:
550  *	Returns a static tm structure.
551  *
552  * Side effects:
553  *	None.
554  *
555  *----------------------------------------------------------------------
556  */
557 
558 struct tm *
TclpGetDate(t,useGMT)559 TclpGetDate(t, useGMT)
560     TclpTime_t t;
561     int useGMT;
562 {
563     const time_t *tp = (const time_t *) t;
564     struct tm *tmPtr;
565     long time;
566 
567     if (!useGMT) {
568 	tzset();
569 
570 	/*
571 	 * If we are in the valid range, let the C run-time library
572 	 * handle it.  Otherwise we need to fake it.  Note that this
573 	 * algorithm ignores daylight savings time before the epoch.
574 	 */
575 
576 	if (*tp >= 0) {
577 	    return localtime(tp);
578 	}
579 
580 	time = *tp - _timezone;
581 
582 	/*
583 	 * If we aren't near to overflowing the long, just add the bias and
584 	 * use the normal calculation.  Otherwise we will need to adjust
585 	 * the result at the end.
586 	 */
587 
588 	if (*tp < (LONG_MAX - 2 * SECSPERDAY)
589 		&& *tp > (LONG_MIN + 2 * SECSPERDAY)) {
590 	    tmPtr = ComputeGMT(&time);
591 	} else {
592 	    tmPtr = ComputeGMT(tp);
593 
594 	    tzset();
595 
596 	    /*
597 	     * Add the bias directly to the tm structure to avoid overflow.
598 	     * Propagate seconds overflow into minutes, hours and days.
599 	     */
600 
601 	    time = tmPtr->tm_sec - _timezone;
602 	    tmPtr->tm_sec = (int)(time % 60);
603 	    if (tmPtr->tm_sec < 0) {
604 		tmPtr->tm_sec += 60;
605 		time -= 60;
606 	    }
607 
608 	    time = tmPtr->tm_min + time/60;
609 	    tmPtr->tm_min = (int)(time % 60);
610 	    if (tmPtr->tm_min < 0) {
611 		tmPtr->tm_min += 60;
612 		time -= 60;
613 	    }
614 
615 	    time = tmPtr->tm_hour + time/60;
616 	    tmPtr->tm_hour = (int)(time % 24);
617 	    if (tmPtr->tm_hour < 0) {
618 		tmPtr->tm_hour += 24;
619 		time -= 24;
620 	    }
621 
622 	    time /= 24;
623 	    tmPtr->tm_mday += time;
624 	    tmPtr->tm_yday += time;
625 	    tmPtr->tm_wday = (tmPtr->tm_wday + time) % 7;
626 	}
627     } else {
628 	tmPtr = ComputeGMT(tp);
629     }
630     return tmPtr;
631 }
632 
633 #endif
634 /*
635  *----------------------------------------------------------------------
636  *
637  * ComputeGMT --
638  *
639  *	This function computes GMT given the number of seconds since
640  *	the epoch (midnight Jan 1 1970).
641  *
642  * Results:
643  *	Returns a (per thread) statically allocated struct tm.
644  *
645  * Side effects:
646  *	Updates the values of the static struct tm.
647  *
648  *----------------------------------------------------------------------
649  */
650 
651 static struct tm *
ComputeGMT(tp)652 ComputeGMT(tp)
653     const time_t *tp;
654 {
655     struct tm *tmPtr;
656     long tmp, rem;
657     int isLeap;
658     int *days;
659     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
660 
661     tmPtr = &tsdPtr->tm;
662 
663     /*
664      * Compute the 4 year span containing the specified time.
665      */
666 
667     tmp = *tp / SECSPER4YEAR;
668     rem = *tp % SECSPER4YEAR;
669 
670     /*
671      * Correct for weird mod semantics so the remainder is always positive.
672      */
673 
674     if (rem < 0) {
675 	tmp--;
676 	rem += SECSPER4YEAR;
677     }
678 
679     /*
680      * Compute the year after 1900 by taking the 4 year span and adjusting
681      * for the remainder.  This works because 2000 is a leap year, and
682      * 1900/2100 are out of the range.
683      */
684 
685     tmp = (tmp * 4) + 70;
686     isLeap = 0;
687     if (rem >= SECSPERYEAR) {			  /* 1971, etc. */
688 	tmp++;
689 	rem -= SECSPERYEAR;
690 	if (rem >= SECSPERYEAR) {		  /* 1972, etc. */
691 	    tmp++;
692 	    rem -= SECSPERYEAR;
693 	    if (rem >= SECSPERYEAR + SECSPERDAY) { /* 1973, etc. */
694 		tmp++;
695 		rem -= SECSPERYEAR + SECSPERDAY;
696 	    } else {
697 		isLeap = 1;
698 	    }
699 	}
700     }
701     tmPtr->tm_year = tmp;
702 
703     /*
704      * Compute the day of year and leave the seconds in the current day in
705      * the remainder.
706      */
707 
708     tmPtr->tm_yday = rem / SECSPERDAY;
709     rem %= SECSPERDAY;
710 
711     /*
712      * Compute the time of day.
713      */
714 
715     tmPtr->tm_hour = rem / 3600;
716     rem %= 3600;
717     tmPtr->tm_min = rem / 60;
718     tmPtr->tm_sec = rem % 60;
719 
720     /*
721      * Compute the month and day of month.
722      */
723 
724     days = (isLeap) ? leapDays : normalDays;
725     for (tmp = 1; days[tmp] < tmPtr->tm_yday; tmp++) {
726     }
727     tmPtr->tm_mon = --tmp;
728     tmPtr->tm_mday = tmPtr->tm_yday - days[tmp];
729 
730     /*
731      * Compute day of week.  Epoch started on a Thursday.
732      */
733 
734     tmPtr->tm_wday = (*tp / SECSPERDAY) + 4;
735     if ((*tp % SECSPERDAY) < 0) {
736 	tmPtr->tm_wday--;
737     }
738     tmPtr->tm_wday %= 7;
739     if (tmPtr->tm_wday < 0) {
740 	tmPtr->tm_wday += 7;
741     }
742 
743     return tmPtr;
744 }
745 
746 /*
747  *----------------------------------------------------------------------
748  *
749  * CalibrationThread --
750  *
751  *	Thread that manages calibration of the hi-resolution time
752  *	derived from the performance counter, to keep it synchronized
753  *	with the system clock.
754  *
755  * Parameters:
756  *	arg -- Client data from the CreateThread call.  This parameter
757  *             points to the static TimeInfo structure.
758  *
759  * Return value:
760  *	None.  This thread embeds an infinite loop.
761  *
762  * Side effects:
763  *	At an interval of 1 s, this thread performs virtual time discipline.
764  *
765  * Note: When this thread is entered, TclpInitLock has been called
766  * to safeguard the static storage.  There is therefore no synchronization
767  * in the body of this procedure.
768  *
769  *----------------------------------------------------------------------
770  */
771 
772 static DWORD WINAPI
CalibrationThread(LPVOID arg)773 CalibrationThread( LPVOID arg )
774 {
775     FILETIME curFileTime;
776     DWORD waitResult;
777 
778     /* Get initial system time and performance counter */
779 
780     GetSystemTimeAsFileTime( &curFileTime );
781     QueryPerformanceCounter( &timeInfo.perfCounterLastCall );
782     QueryPerformanceFrequency( &timeInfo.curCounterFreq );
783     timeInfo.fileTimeLastCall.LowPart = curFileTime.dwLowDateTime;
784     timeInfo.fileTimeLastCall.HighPart = curFileTime.dwHighDateTime;
785 
786     ResetCounterSamples( timeInfo.fileTimeLastCall.QuadPart,
787 			 timeInfo.perfCounterLastCall.QuadPart,
788 			 timeInfo.curCounterFreq.QuadPart );
789 
790     /*
791      * Wake up the calling thread.  When it wakes up, it will release the
792      * initialization lock.
793      */
794 
795     SetEvent( timeInfo.readyEvent );
796 
797     /* Run the calibration once a second */
798 
799     for ( ; ; ) {
800 
801 	/* If the exitEvent is set, break out of the loop. */
802 
803 	waitResult = WaitForSingleObjectEx(timeInfo.exitEvent, 1000, FALSE);
804 	if ( waitResult == WAIT_OBJECT_0 ) {
805 	    break;
806 	}
807 	UpdateTimeEachSecond();
808     }
809 
810     /* lint */
811     return (DWORD) 0;
812 }
813 
814 /*
815  *----------------------------------------------------------------------
816  *
817  * UpdateTimeEachSecond --
818  *
819  *	Callback from the waitable timer in the clock calibration thread
820  *	that updates system time.
821  *
822  * Parameters:
823  *	info -- Pointer to the static TimeInfo structure
824  *
825  * Results:
826  *	None.
827  *
828  * Side effects:
829  *	Performs virtual time calibration discipline.
830  *
831  *----------------------------------------------------------------------
832  */
833 
834 static void
UpdateTimeEachSecond()835 UpdateTimeEachSecond()
836 {
837 
838     LARGE_INTEGER curPerfCounter;
839 				/* Current value returned from
840 				 * QueryPerformanceCounter */
841 
842     FILETIME curSysTime;	/* Current system time */
843 
844     LARGE_INTEGER curFileTime;	/* File time at the time this callback
845 				 * was scheduled. */
846 
847     Tcl_WideInt estFreq;	/* Estimated perf counter frequency */
848 
849     Tcl_WideInt vt0;		/* Tcl time right now */
850     Tcl_WideInt vt1;		/* Tcl time one second from now */
851 
852     Tcl_WideInt tdiff;		/* Difference between system clock and
853 				 * Tcl time. */
854 
855     Tcl_WideInt driftFreq;	/* Frequency needed to drift virtual time
856 				 * into step over 1 second */
857 
858     /*
859      * Sample performance counter and system time.
860      */
861 
862     QueryPerformanceCounter( &curPerfCounter );
863     GetSystemTimeAsFileTime( &curSysTime );
864     curFileTime.LowPart = curSysTime.dwLowDateTime;
865     curFileTime.HighPart = curSysTime.dwHighDateTime;
866 
867     EnterCriticalSection( &timeInfo.cs );
868 
869     /*
870      * Several things may have gone wrong here that have to
871      * be checked for.
872      * (1) The performance counter may have jumped.
873      * (2) The system clock may have been reset.
874      *
875      * In either case, we'll need to reinitialize the circular buffer
876      * with samples relative to the current system time and the NOMINAL
877      * performance frequency (not the actual, because the actual has
878      * probably run slow in the first case). Our estimated frequency
879      * will be the nominal frequency.
880      */
881 
882     /*
883      * Store the current sample into the circular buffer of samples,
884      * and estimate the performance counter frequency.
885      */
886 
887     estFreq = AccumulateSample( curPerfCounter.QuadPart,
888 				(Tcl_WideUInt) curFileTime.QuadPart );
889 
890     /*
891      * We want to adjust things so that time appears to be continuous.
892      * Virtual file time, right now, is
893      *
894      * vt0 = 10000000 * ( curPerfCounter - perfCounterLastCall )
895      *       / curCounterFreq
896      *       + fileTimeLastCall
897      *
898      * Ideally, we would like to drift the clock into place over a
899      * period of 2 sec, so that virtual time 2 sec from now will be
900      *
901      * vt1 = 20000000 + curFileTime
902      *
903      * The frequency that we need to use to drift the counter back into
904      * place is estFreq * 20000000 / ( vt1 - vt0 )
905      */
906 
907     vt0 = 10000000 * ( curPerfCounter.QuadPart
908 		       - timeInfo.perfCounterLastCall.QuadPart )
909 	/ timeInfo.curCounterFreq.QuadPart
910 	+ timeInfo.fileTimeLastCall.QuadPart;
911     vt1 = 20000000 + curFileTime.QuadPart;
912 
913     /*
914      * If we've gotten more than a second away from system time,
915      * then drifting the clock is going to be pretty hopeless.
916      * Just let it jump. Otherwise, compute the drift frequency and
917      * fill in everything.
918      */
919 
920     tdiff = vt0 - curFileTime.QuadPart;
921     if ( tdiff > 10000000 || tdiff < -10000000 ) {
922 	timeInfo.fileTimeLastCall.QuadPart = curFileTime.QuadPart;
923 	timeInfo.curCounterFreq.QuadPart = estFreq;
924     } else {
925 	driftFreq = estFreq * 20000000 / ( vt1 - vt0 );
926 	if ( driftFreq > 1003 * estFreq / 1000 ) {
927 	    driftFreq = 1003 * estFreq / 1000;
928 	}
929 	if ( driftFreq < 997 * estFreq / 1000 ) {
930 	    driftFreq = 997 * estFreq / 1000;
931 	}
932 	timeInfo.fileTimeLastCall.QuadPart = vt0;
933 	timeInfo.curCounterFreq.QuadPart = driftFreq;
934     }
935 
936     timeInfo.perfCounterLastCall.QuadPart = curPerfCounter.QuadPart;
937 
938     LeaveCriticalSection( &timeInfo.cs );
939 
940 }
941 
942 /*
943  *----------------------------------------------------------------------
944  *
945  * ResetCounterSamples --
946  *
947  *	Fills the sample arrays in 'timeInfo' with dummy values that will
948  *	yield the current performance counter and frequency.
949  *
950  * Results:
951  *	None.
952  *
953  * Side effects:
954  *	The array of samples is filled in so that it appears that there
955  *	are SAMPLES samples at one-second intervals, separated by precisely
956  *	the given frequency.
957  *
958  *----------------------------------------------------------------------
959  */
960 
961 static void
ResetCounterSamples(Tcl_WideUInt fileTime,Tcl_WideInt perfCounter,Tcl_WideInt perfFreq)962 ResetCounterSamples( Tcl_WideUInt fileTime,
963 				/* Current file time */
964 		     Tcl_WideInt perfCounter,
965 				/* Current performance counter */
966 		     Tcl_WideInt perfFreq )
967 				/* Target performance frequency */
968 {
969     int i;
970     for ( i = SAMPLES-1; i >= 0; --i ) {
971 	timeInfo.perfCounterSample[i] = perfCounter;
972 	timeInfo.fileTimeSample[i] = fileTime;
973 	perfCounter -= perfFreq;
974 	fileTime -= 10000000;
975     }
976     timeInfo.sampleNo = 0;
977 }
978 
979 /*
980  *----------------------------------------------------------------------
981  *
982  * AccumulateSample --
983  *
984  *	Updates the circular buffer of performance counter and system
985  *	time samples with a new data point.
986  *
987  * Results:
988  *	None.
989  *
990  * Side effects:
991  *	The new data point replaces the oldest point in the circular
992  *	buffer, and the descriptive statistics are updated to accumulate
993  *	the new point.
994  *
995  * Several things may have gone wrong here that have to
996  * be checked for.
997  * (1) The performance counter may have jumped.
998  * (2) The system clock may have been reset.
999  *
1000  * In either case, we'll need to reinitialize the circular buffer
1001  * with samples relative to the current system time and the NOMINAL
1002  * performance frequency (not the actual, because the actual has
1003  * probably run slow in the first case).
1004  */
1005 
1006 static Tcl_WideInt
AccumulateSample(Tcl_WideInt perfCounter,Tcl_WideUInt fileTime)1007 AccumulateSample( Tcl_WideInt perfCounter,
1008 		  Tcl_WideUInt fileTime )
1009 {
1010     Tcl_WideUInt workFTSample;	/* File time sample being removed
1011 				 * from or added to the circular buffer */
1012 
1013     Tcl_WideInt workPCSample;	/* Performance counter sample being
1014 				 * removed from or added to the circular
1015 				 * buffer */
1016 
1017     Tcl_WideUInt lastFTSample;	/* Last file time sample recorded */
1018 
1019     Tcl_WideInt lastPCSample;	/* Last performance counter sample recorded */
1020 
1021     Tcl_WideInt FTdiff;		/* Difference between last FT and current */
1022 
1023     Tcl_WideInt PCdiff;		/* Difference between last PC and current */
1024 
1025     Tcl_WideInt estFreq;	/* Estimated performance counter frequency */
1026 
1027     /* Test for jumps and reset the samples if we have one. */
1028 
1029     if ( timeInfo.sampleNo == 0 ) {
1030 	lastPCSample = timeInfo.perfCounterSample[ timeInfo.sampleNo
1031 						   + SAMPLES - 1 ];
1032 	lastFTSample = timeInfo.fileTimeSample[ timeInfo.sampleNo
1033 						+ SAMPLES - 1 ];
1034     } else {
1035 	lastPCSample = timeInfo.perfCounterSample[ timeInfo.sampleNo - 1 ];
1036 	lastFTSample = timeInfo.fileTimeSample[ timeInfo.sampleNo - 1 ];
1037     }
1038     PCdiff = perfCounter - lastPCSample;
1039     FTdiff = fileTime - lastFTSample;
1040     if ( PCdiff < timeInfo.nominalFreq.QuadPart * 9 / 10
1041 	 || PCdiff > timeInfo.nominalFreq.QuadPart * 11 / 10
1042 	 || FTdiff < 9000000
1043 	 || FTdiff > 11000000 ) {
1044 	ResetCounterSamples( fileTime, perfCounter,
1045 			     timeInfo.nominalFreq.QuadPart );
1046 	return timeInfo.nominalFreq.QuadPart;
1047 
1048     } else {
1049 
1050 	/* Estimate the frequency */
1051 
1052 	workPCSample = timeInfo.perfCounterSample[ timeInfo.sampleNo ];
1053 	workFTSample = timeInfo.fileTimeSample[ timeInfo.sampleNo ];
1054 	estFreq = 10000000 * ( perfCounter - workPCSample )
1055 	    / ( fileTime - workFTSample );
1056 	timeInfo.perfCounterSample[ timeInfo.sampleNo ] = perfCounter;
1057 	timeInfo.fileTimeSample[ timeInfo.sampleNo ] = (Tcl_WideInt) fileTime;
1058 
1059 	/* Advance the sample number */
1060 
1061 	if ( ++timeInfo.sampleNo >= SAMPLES ) {
1062 	    timeInfo.sampleNo = 0;
1063 	}
1064 
1065 	return estFreq;
1066     }
1067 }
1068 
1069 #endif
1070 
1071