[LWN Logo]
[LWN.net]
From:	 "Ulrich Windl" <Ulrich.Windl@rz.uni-regensburg.de>
To:	 linux-kernel@vger.kernel.org
Subject: patch-proposal: extended adjtime()
Date:	 Tue, 24 Apr 2001 14:48:39 +0200

Hello,

someone found out that in Linux adjtime()'s correction is limited to 
something like 2000s (signed 32bit microseconds for i386). This is not 
a true problem, but for those who desperately need/want it, I have a 
patch proposal (incomplete, but essential) to implement the full range 
(maybe even more). The patch tries to keep binary compatibility, too.

Opinions?

Regards,
Ulrich



--- kernel/243time.c	Mon Apr 16 20:14:27 2001
+++ kernel/xxxtime.c	Mon Apr 16 20:41:15 2001
@@ -100,7 +100,8 @@
 	write_lock_irq(&xtime_lock);
 	xtime.tv_sec = value;
 	xtime.tv_usec = 0;
-	time_adjust = 0;	/* stop active adjtime() */
+	time_adjust.tv_sec = time_adjust.tv_usec = 0;
+	/* stop active adjtime() */
 	time_status |= STA_UNSYNC;
 	time_maxerror = NTP_PHASE_LIMIT;
 	time_esterror = NTP_PHASE_LIMIT;
@@ -225,7 +226,8 @@
  */
 int do_adjtimex(struct timex *txc)
 {
-        long ltemp, mtemp, save_adjust;
+        long ltemp, mtemp;
+	struct timeval save_adjust;
 	int result;
 
 	/* In order to modify anything, you gotta be super-user! */
@@ -295,7 +297,31 @@
 	    if (txc->modes & ADJ_OFFSET) {	/* values checked earlier */
 		if (txc->modes == ADJ_OFFSET_SINGLESHOT) {
 		    /* adjtime() is independent from ntp_adjtime() */
-		    time_adjust = txc->offset;
+
+		    /* Try to extend the range for plain old adjtime()
+		     * to multiple seconds without breaking binary
+		     * compatibility. A perfect solution is not
+		     * possible, but this one has a high probability
+		     * for success. The true solution is a syscall of
+		     * its own.
+		     * The offset for ADJ_OFFSET_SINGLESHOT is stored in
+		     * txc->time (struct timeval) now. To avoid using
+		     * garbage vaues, it's required to copy
+		     * `txc->time.tv_usec' also into `txc->offset'. Just
+		     * to be sure, we also require the magic word
+		     * EXTENDED_ADJTIME_MAGIC to be written to `txc->status'
+		     * (it's a value not possible before, and it's
+		     * overwritten after each call).
+		     */
+#define EXTENDED_ADJTIME_MAGIC	(0x0000ffff + ('U' << 24) + ('W' << 16))
+		    /* old compatible interface */
+		    time_adjust.tv_usec = txc->offset;
+
+		    if (txc->offset == txc->time.tv_usec &&
+			txc->status == EXTENDED_ADJTIME_MAGIC) {
+			/* extended part */
+			time_adjust.tv_sec = txc->time.tv_sec;
+		    }
 		}
 		else if ( time_status & (STA_PLL | STA_PPSTIME) ) {
 		    ltemp = (time_status & (STA_PPSTIME | STA_PPSSIGNAL)) ==
@@ -375,9 +401,11 @@
 	    /* p. 24, (d) */
 		result = TIME_ERROR;
 	
-	if ((txc->modes & ADJ_OFFSET_SINGLESHOT) == ADJ_OFFSET_SINGLESHOT)
-	    txc->offset	   = save_adjust;
-	else {
+	if ((txc->modes & ADJ_OFFSET_SINGLESHOT) == ADJ_OFFSET_SINGLESHOT) {
+	    txc->offset	   = save_adjust.tv_usec;
+	    if (txc->status == EXTENDED_ADJTIME_MAGIC)
+		txc->time = save_adjust;
+	} else {
 	    if (time_offset < 0)
 		txc->offset = -(-time_offset >> SHIFT_UPDATE);
 	    else
--- kernel/243timer.c	Mon Apr 16 20:34:29 2001
+++ kernel/xxxtimer.c	Mon Apr 16 21:01:29 2001
@@ -58,8 +58,7 @@
 long time_adj;				/* tick adjust (scaled 1 / HZ)	*/
 long time_reftime;			/* time at last adjustment (s)	*/
 
-long time_adjust;
-long time_adjust_step;
+struct timeval time_adjust;		/* remaining time adjustment */
 
 unsigned long event;
 
@@ -461,8 +460,26 @@
 /* in the NTP reference this is called "hardclock()" */
 static void update_wall_time_one_tick(void)
 {
-	if ( (time_adjust_step = time_adjust) != 0 ) {
-	    /* We are doing an adjtime thing. 
+	long time_adjust_step;
+
+	if ((time_adjust.tv_sec | time_adjust.tv_usec) != 0) {
+	    time_adjust_step = time_adjust.tv_usec;
+	    if (time_adjust_step > 0) {
+		/* if we run out of microseconds, but have more seconds,
+		 * borrow another second
+		 */
+		if (time_adjust_step < tickadj && time_adjust.tv_sec > 0) {
+		    time_adjust_step = time_adjust.tv_usec += 1000000;
+		    --time_adjust.tv_sec;
+		}
+	    } else {
+		if (time_adjust_step > -tickadj && time_adjust.tv_sec < 0) {
+		    time_adjust_step = time_adjust.tv_usec -= 1000000;
+		    ++time_adjust.tv_sec;
+		}
+	    }
+		    
+	    /* We gave to complete the adjtime() thing. 
 	     *
 	     * Prepare time_adjust_step to be within bounds.
 	     * Note that a positive time_adjust means we want the clock
@@ -471,15 +488,16 @@
 	     * Limit the amount of the step to be in the range
 	     * -tickadj .. +tickadj
 	     */
-	     if (time_adjust > tickadj)
+	     if (time_adjust_step > tickadj)
 		time_adjust_step = tickadj;
-	     else if (time_adjust < -tickadj)
+	     else if (time_adjust_step < -tickadj)
 		time_adjust_step = -tickadj;
 	     
-	    /* Reduce by this step the amount of time left  */
-	    time_adjust -= time_adjust_step;
+	    /* Reduce remaining correction by this step. Can't overflow */
+	    time_adjust.tv_usec -= time_adjust_step;
+	    xtime.tv_usec += time_adjust_step;
 	}
-	xtime.tv_usec += tick + time_adjust_step;
+	xtime.tv_usec += tick;
 	/*
 	 * Advance the phase, once it gets to one microsecond, then
 	 * advance the tick more.