Re: 2.6.14-rt21: slow-running clock

From: john stultz
Date: Mon Dec 12 2005 - 20:55:55 EST


On Fri, 2005-12-09 at 12:49 +1030, Jonathan Woithe wrote:
> > Ok, I went digging further and found the c3tsc selection is correct on
> > your hardware. I'm just too used to my own laptop where the TSC varies
> > with cpu speed and we lower the rating value. So that should be ok.
>
> Ok, good. That leaves the c3tsc slowdown as the only outstanding issue at
> this stage.
>
> > I'm now working on why we mis-compensate the c3tsc clocksource in the
> > -RT tree.
>
> No problem. Let me know when you have something to test or need further
> info.

Hey Jonathan,

Attached is a test patch to see if it doesn't resolve the issue for
you. I get a maximum change in drift of 30ppm when idling between C3
states by being more careful with the C3 TSC compensation and I also
force timekeeping updates when cpufreq events occur.

I'm not sure if this is the right fix yet, but it might help narrow down
the problem.


Also attached is a python script that will spit out the offset value
from an ntp server and calculate the drift. This also will give us a
better idea of what is going on. If you could run it with and without
the patch, that would be great!


First shutdown NTPd then run:
./drift-test.py [-s] <timeserver> <polling interval>

Where -s will sync the clock before running.


thanks
-john



diff --git a/arch/i386/kernel/tsc.c b/arch/i386/kernel/tsc.c
--- a/arch/i386/kernel/tsc.c
+++ b/arch/i386/kernel/tsc.c
@@ -66,13 +66,13 @@ void mark_tsc_unstable(void)
/* Code to compensate for C3 stalls */
static u64 tsc_c3_offset;

-void tsc_c3_compensate(unsigned long nsecs)
+void tsc_c3_compensate(unsigned long nsecs, u64 tsc_offset)
{
/* this could def be optimized */
u64 cycles = ((u64)nsecs * tsc_khz);

do_div(cycles, 1000000);
- tsc_c3_offset += cycles;
+ tsc_c3_offset += cycles - tsc_offset;
}

EXPORT_SYMBOL_GPL(tsc_c3_compensate);
@@ -263,6 +263,7 @@ static int
time_cpufreq_notifier(struct notifier_block *nb, unsigned long val, void *data)
{
struct cpufreq_freqs *freq = data;
+ unsigned long old_tsc_khz = tsc_khz;

if (val != CPUFREQ_RESUMECHANGE)
write_seqlock_irq(&xtime_lock);
@@ -299,6 +300,9 @@ time_cpufreq_notifier(struct notifier_bl
if (val != CPUFREQ_RESUMECHANGE)
write_sequnlock_irq(&xtime_lock);

+ if(old_tsc_khz != tsc_khz)
+ timeofday_force_update();
+
return 0;
}

diff --git a/drivers/acpi/processor_idle.c b/drivers/acpi/processor_idle.c
--- a/drivers/acpi/processor_idle.c
+++ b/drivers/acpi/processor_idle.c
@@ -177,7 +177,7 @@ static void acpi_safe_halt(void)
}

static atomic_t c3_cpu_count;
-extern void tsc_c3_compensate(unsigned long nsecs);
+extern void tsc_c3_compensate(unsigned long nsecs, u64 tsc_offset);

static void acpi_processor_idle(void)
{
@@ -186,6 +186,7 @@ static void acpi_processor_idle(void)
struct acpi_processor_cx *next_state = NULL;
int sleep_ticks = 0;
u32 t1, t2 = 0;
+ u64 tsc1, tsc2, tsc3;

pr = processors[smp_processor_id()];
if (!pr)
@@ -359,12 +360,15 @@ static void acpi_processor_idle(void)

/* Get start time (ticks) */
t1 = inl(acpi_fadt.xpm_tmr_blk.address);
+ rdtscll(tsc1);
/* Invoke C3 */
inb(cx->address);
/* Dummy op - must do something useless after P_LVL3 read */
t2 = inl(acpi_fadt.xpm_tmr_blk.address);
/* Get end time (ticks) */
+ rdtscll(tsc2);
t2 = inl(acpi_fadt.xpm_tmr_blk.address);
+ rdtscll(tsc3);
if (pr->flags.bm_check) {
/* Enable bus master arbitration */
atomic_dec(&c3_cpu_count);
@@ -374,7 +378,7 @@ static void acpi_processor_idle(void)

#ifdef CONFIG_GENERIC_TIME
/* compensate for TSC pause */
- tsc_c3_compensate((u32)(((u64)((t2-t1)&0xFFFFFF)*286070)>>10));
+ tsc_c3_compensate((u32)(((u64)((t2-t1)&0xFFFFFF)*286070)>>10), tsc3-tsc1);
#endif

/* Re-enable interrupts */
diff --git a/include/asm-i386/tsc.h b/include/asm-i386/tsc.h
--- a/include/asm-i386/tsc.h
+++ b/include/asm-i386/tsc.h
@@ -44,7 +44,7 @@ static inline cycles_t get_cycles(void)
}

extern void tsc_init(void);
-extern void tsc_c3_compensate(unsigned long usecs);
+extern void tsc_c3_compensate(unsigned long usecs, unsigned long long tsc_offset);
extern void mark_tsc_unstable(void);

#endif
diff --git a/include/linux/timeofday.h b/include/linux/timeofday.h
--- a/include/linux/timeofday.h
+++ b/include/linux/timeofday.h
@@ -30,6 +30,7 @@ extern int do_settimeofday(struct timesp

/* Internal functions */
extern int timeofday_is_continuous(void);
+extern void timeofday_force_update(void);
extern void timeofday_init(void);

#ifndef CONFIG_IS_TICK_BASED
diff --git a/kernel/time/timeofday.c b/kernel/time/timeofday.c
--- a/kernel/time/timeofday.c
+++ b/kernel/time/timeofday.c
@@ -745,6 +745,12 @@ static void timeofday_periodic_hook(unsi
jiffies + 1 + msecs_to_jiffies(PERIODIC_INTERVAL_MS));
}

+
+void timeofday_force_update(void)
+{
+ timeofday_periodic_hook(0);
+}
+
/**
* timeofday_is_continuous - check to see if timekeeping is free running
*/
#!/usr/bin/python

# Time Drift Script
# Periodically checks and displays time drift
# by john stultz (jstultz@xxxxxxxxxx)

import commands
import sys
import string
import time

server_default = "yourserverhere"
sleep_time_default = 60

server = ""
sleep_time = 0
set_time = 0

#parse args
for arg in sys.argv[1:]:
if arg == "-s":
set_time = 1
elif server == "":
server = arg
elif sleep_time == 0:
sleep_time = string.atoi(arg)

if server == "":
server = server_default
if sleep_time == 0:
sleep_time = sleep_time_default

#set time
if (set_time == 1):
cmd = commands.getoutput('/usr/sbin/ntpdate -ub ' + server)

cmd = commands.getoutput('/usr/sbin/ntpdate -uq ' + server)
line = string.split(cmd)

#parse original offset
start_offset = string.atof(line[-2]);
#parse original time
start_time = time.localtime(time.time())
datestr = time.strftime("%d %b %Y %H:%M:%S", start_time)

time.sleep(1)
while 1:
cmd = commands.getoutput('/usr/sbin/ntpdate -uq ' + server)
line = string.split(cmd)

#parse offset
now_offset = string.atof(line[-2]);

#parse time
now_time = time.localtime(time.time())
datestr = time.strftime("%d %b %Y %H:%M:%S", now_time)

# calculate drift
delta_time = time.mktime(now_time) - time.mktime(start_time)
delta_offset = now_offset - start_offset
drift = delta_offset / delta_time * 1000000

#print output
print time.strftime("%d %b %H:%M:%S",now_time),
print " offset:", now_offset ,
print " drift:", drift ,"ppm"
sys.stdout.flush()

#sleep
time.sleep(sleep_time)