From 6edc4f5f8bf6e5742b6d5d84b9172146f21fe9c7 Mon Sep 17 00:00:00 2001 From: Andrew Morton Date: Tue, 21 Oct 2003 18:19:33 -0700 Subject: [PATCH] [PATCH] Time precision, adjtime(x) vs. gettimeofday From: Stephen Hemminger The following will prevent adjtime from causing time regression. It delays starting the adjtime mechanism for one tick, and keeps gettimeofday inside the window. Only fixes i386, but changes to other arch would be similar. Running a simple clock test program and playing with adjtime demonstrates that this fixes the problem (and 2.6.0-test6 is broken). But given the fragile nature of the timer code, it should go through some more testing before inclusion. --- arch/i386/kernel/time.c | 9 +++++++++ include/linux/timex.h | 1 + kernel/time.c | 5 +++-- kernel/timer.c | 7 +++++++ 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/arch/i386/kernel/time.c b/arch/i386/kernel/time.c index a1a583b6bc043..61b7b24f8915a 100644 --- a/arch/i386/kernel/time.c +++ b/arch/i386/kernel/time.c @@ -104,6 +104,15 @@ void do_gettimeofday(struct timeval *tv) lost = jiffies - wall_jiffies; if (lost) usec += lost * (1000000 / HZ); + + /* + * If time_adjust is negative then NTP is slowing the clock + * so make sure not to go into next possible interval. + * Better to lose some accuracy than have time go backwards.. + */ + if (unlikely(time_adjust < 0) && usec > tickadj) + usec = tickadj; + sec = xtime.tv_sec; usec += (xtime.tv_nsec / 1000); } while (read_seqretry(&xtime_lock, seq)); diff --git a/include/linux/timex.h b/include/linux/timex.h index 2b1665d57ad08..c9e92e016524e 100644 --- a/include/linux/timex.h +++ b/include/linux/timex.h @@ -302,6 +302,7 @@ extern long time_adj; /* tick adjust (scaled 1 / HZ) */ extern long time_reftime; /* time at last adjustment (s) */ extern long time_adjust; /* The amount of adjtime left */ +extern long time_next_adjust; /* Value for time_adjust at next tick */ /* interface variables pps->timer interrupt */ extern long pps_offset; /* pps time offset (us) */ diff --git a/kernel/time.c b/kernel/time.c index 6279ee846ed55..33a6fe0863046 100644 --- a/kernel/time.c +++ b/kernel/time.c @@ -236,7 +236,7 @@ int do_adjtimex(struct timex *txc) result = time_state; /* mostly `TIME_OK' */ /* Save for later - semantics of adjtime is to return old value */ - save_adjust = time_adjust; + save_adjust = time_next_adjust ? time_next_adjust : time_adjust; #if 0 /* STA_CLOCKERR is never set yet */ time_status &= ~STA_CLOCKERR; /* reset STA_CLOCKERR */ @@ -283,7 +283,8 @@ int do_adjtimex(struct timex *txc) if (txc->modes & ADJ_OFFSET) { /* values checked earlier */ if (txc->modes == ADJ_OFFSET_SINGLESHOT) { /* adjtime() is independent from ntp_adjtime() */ - time_adjust = txc->offset; + if ((time_next_adjust = txc->offset) == 0) + time_adjust = 0; } else if ( time_status & (STA_PLL | STA_PPSTIME) ) { ltemp = (time_status & (STA_PPSTIME | STA_PPSSIGNAL)) == diff --git a/kernel/timer.c b/kernel/timer.c index 1c513bd0b1e0e..a8442b38659b9 100644 --- a/kernel/timer.c +++ b/kernel/timer.c @@ -474,6 +474,7 @@ long time_freq = (((NSEC_PER_SEC + HZ/2) % HZ - HZ/2) << SHIFT_USEC) / NSEC_PER_ long time_adj; /* tick adjust (scaled 1 / HZ) */ long time_reftime; /* time at last adjustment (s) */ long time_adjust; +long time_next_adjust; /* * this routine handles the overflow of the microsecond field @@ -654,6 +655,12 @@ static void update_wall_time_one_tick(void) } xtime.tv_nsec += delta_nsec; time_interpolator_update(delta_nsec); + + /* Changes by adjtime() do not take effect till next tick. */ + if (time_next_adjust != 0) { + time_adjust = time_next_adjust; + time_next_adjust = 0; + } } /* -- 2.53.0