svn commit: r297039 - head/sys/x86/x86

Konstantin Belousov kostikbel at gmail.com
Sat Mar 26 17:42:17 UTC 2016


On Sat, Mar 26, 2016 at 03:17:43AM +1100, Bruce Evans wrote:
> This uses the i8254.
Well, this is the part which I do not like most.  It is ridiculous
to calibrate relatively high-quality CPU oscillator with 8245 timer,
which often fed from separate, low-quality crystal which currently
selected by cost and exists only for legacy purposes.  At least
some hypervisors start offering modes where old ISA peripherals
are not emulated, and I suspect that hardware would start do the
same.  Quite possible, some variants of SoCs already do this.

More, we do not have any hooks to recalibrate our thought about
TSC frequency during normal runtime.  Often, system does have very
precise measurement of the timecounter drift due to ntp/ptp.

> xdel() is a specialized version of i8254 DELAY()
> with getit() inline.  It returns the initial and final values of the
> i8254 counter.  It doesn't handle interrupts or any other source of
> large clock jitter.  What it measures more precisely is the measurement
> overhead.  This is normally 2-5 usec.  With a timer frequency of about
> 1 MHz, a 5 usec error is about 5 ppm.  Compensating for this reduces
> the error to below 1 ppm if there are no interrupts,
> 
> tsc_calibrate() calls xdel() twice to determine the measurement overhead.
> It should be called one more time to warm up the cache.
> 
> In other kernels, I use the following version using DELAY() which is
> good enough if DELAY() works and is not delayed by interrupts
> 
> X diff -c2 ./x86/x86/tsc.c~ ./x86/x86/tsc.c
> X *** ./x86/x86/tsc.c~	Sun Feb 14 21:56:28 2016
> X --- ./x86/x86/tsc.c	Sun Feb 14 22:01:46 2016
> X ***************
> X *** 240,244 ****
> X   {
> X   	u_int regs[4];
> X ! 	uint64_t tsc1, tsc2;
> X 
> X   	if (cpu_high >= 6) {
> X --- 240,244 ----
> X   {
> X   	u_int regs[4];
> X ! 	uint64_t tsc1, tsc2, tsc3;
> X 
> X   	if (cpu_high >= 6) {
> X ***************
> X *** 306,313 ****
> X   	if (bootverbose)
> X   	        printf("Calibrating TSC clock ... ");
> X   	tsc1 = rdtsc();
> X ! 	DELAY(1000000);
> X   	tsc2 = rdtsc();
> X ! 	tsc_freq = tsc2 - tsc1;
> X   	if (bootverbose)
> X   		printf("TSC clock: %ju Hz\n", (intmax_t)tsc_freq);
> X --- 306,316 ----
> X   	if (bootverbose)
> X   	        printf("Calibrating TSC clock ... ");
> X + 	DELAY(1000);
> X   	tsc1 = rdtsc();
> X ! 	DELAY(1000);
> X   	tsc2 = rdtsc();
> X ! 	DELAY(1000000);
> X ! 	tsc3 = rdtsc();
> X ! 	tsc_freq = tsc3 - tsc2 - (tsc2 - tsc1);
> X   	if (bootverbose)
> X   		printf("TSC clock: %ju Hz\n", (intmax_t)tsc_freq);
> 
> See also kern_tc.c:cpu_tick_calibrate().  This is quite accurate after
> fixing its bugs.  It gets accuracy by timing over 16 seconds instead of
> 1 and by using a timecounter which is assumed to be accurate.
> 
> See also tsccalib/tsccalib.c in my home directory on freefall.  This
> is a refined version of the above.  It uses the time returned by
> clock_gettime() as a reference.  It compensates for interrupts and
> runs for long enough to get the specified accuracy.  If you only want
> a low accuracy like 1 ppm, this takes 1.8 msec on freefall (this depends
> a lot on the speed of clock_gettime(2) -- 1.8 msec is with the fast
> TSC timecounter in libc).
> 
> The worst case for all of these methods is if the i8254 is the only
> timer.  Then tsccalib takes much longer to get an accurate calibration
> because the error reading the timer is about its access time which is
> very large for the i8254.  The i8254 otherwise works perfectly for
> calibration provided its wrapping is always detected.
> 
> > Below is the patch to implement calibration of the ipi_wait() busy loop.
> > On my sandybridge 3.4Ghz, I get the message
> > LAPIC: ipi_wait() us multiplier 37 (r 128652089678 tsc 3392383992)
> 
> This seems OK, but it might belong closer to DELAY().
> 
> > ...
> > +	counter = lapic_ipi_wait_mult * delay;
> > +	for (i = 0; i < counter; i++) {
> > 		if ((lapic_read_icr_lo() & APIC_DELSTAT_MASK) ==
> > 		    APIC_DELSTAT_IDLE)
> > 			return (1);
> > -		DELAY(1);
> > +		ia32_pause();
> > 	}
> 
> This part is basically DELAY() implemented as a simple loop with a
> callback in the loop.  I don't like callbacks and prefer direct code
> like the above.  The direct code is basically DELAY() implemented as
> a simple loop and cloned to add a simple check on very iteration.
I thought about callback interface to DELAY().  We already have
something quite close imported from Linux, see
sys/dev/drm2/i915/intel_drv.h:_intel_wait_for and _wait_for
macros.  But this is for different change as well.

I will wait some time for John opinion on the patch.

> 
> If an error factor of 10 or so is acceptable, then the simple loop
> is good enough for DELAY() too.  Or DELAY() can do:
> 
>  	while (n > 1000)
>  		recalibrate_every_millisecond_while_reducing_n();
>  	/*
>  	 * We can't reasonably get better accuracy than a factor of 1000
>  	 * for short delays, so don't try hard.  A single register read
>  	 * can take over 100 cycles waiting for DMA and buses, and we
>  	 * don't want to disable interrupts so the general case can
>  	 * reasonably be delayed by several milliseconds for interrupt
>  	 * handling.  We hope that the worst case in normal operation is
>  	 * 1 quanta, giving an error factor of 100000 for DELAY(1).
>  	 */
>  	simple_loop();
> 
> Bruce


More information about the svn-src-head mailing list