summaryrefslogtreecommitdiffstats
path: root/libntp/adjtime.c
blob: a8e65808bf2915e2afce400def4a0f106ccfb05d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#ifdef MPE 
/*
 * MPE lacks adjtime(), so we define our own.  But note that time slewing has
 * a sub-second accuracy bug documented in SR 5003462838 which prevents ntpd
 * from being able to maintain clock synch.  Because of the bug, this adjtime()
 * implementation as used by ntpd has a side-effect of screwing up the hardware
 * PDC clock, which will need to be reset with a reboot.
 *
 * This problem affects all versions of MPE at the time of this writing (when
 * MPE/iX 7.0 is the most current).  It only causes bad things to happen when
 * doing continuous clock synchronization with ntpd; note that you CAN run ntpd
 * with "disable ntp" in ntp.conf if you wish to provide a time server.
 *
 * The one-time clock adjustment functionality of ntpdate and ntp_timeset can
 * be used without screwing up the PDC clock.
 * 
 */
#include <time.h>

int adjtime(struct timeval *delta, struct timeval *olddelta);

int adjtime(struct timeval *delta, struct timeval *olddelta)

{
/* Documented, supported MPE system intrinsics. */

extern void GETPRIVMODE(void);
extern void GETUSERMODE(void);

/* Undocumented, unsupported MPE internal functions. */

extern long long current_correction_usecs(void);
extern long long get_time(void);
extern void get_time_change_info(long long *, char *, char *);
extern long long pdc_time(int *);
extern void set_time_correction(long long, int, int);
extern long long ticks_to_micro(long long);

long long big_sec, big_usec, new_correction = 0LL;
long long prev_correction;

if (delta != NULL) {
  /* Adjustment required.  Convert delta to 64-bit microseconds. */
  big_sec = (long)delta->tv_sec;
  big_usec = delta->tv_usec;
  new_correction = (big_sec * 1000000LL) + big_usec;
}

GETPRIVMODE();

/* Determine how much of a previous correction (if any) we're interrupting. */
prev_correction = current_correction_usecs();

if (delta != NULL) {
  /* Adjustment required. */

#if 0
  /* Speculative code disabled until bug SR 5003462838 is fixed.  This bug
     prevents accurate time slewing, and indeed renders ntpd inoperable. */

  if (prev_correction != 0LL) {
    /* A previous adjustment did not complete.  Since the PDC UTC clock was
    immediately jumped at the start of the previous adjustment, we must
    explicitly reset it to the value of the MPE local time clock minus the
    time zone offset. */

    char pwf_since_boot, recover_pwf_time;
    long long offset_ticks, offset_usecs, pdc_usecs_current, pdc_usecs_wanted;
    int hpe_status;

    get_time_change_info(&offset_ticks, &pwf_since_boot, &recover_pwf_time);
    offset_usecs = ticks_to_micro(offset_ticks);
    pdc_usecs_wanted = get_time() - offset_usecs;
    pdc_usecs_current = pdc_time(&hpe_status);
    if (hpe_status == 0) 
      /* Force new PDC time by starting an extra correction. */
      set_time_correction(pdc_usecs_wanted - pdc_usecs_current,0,1);
  }
#endif /* 0 */
    
  /* Immediately jump the PDC time to the new value, and then initiate a 
     gradual MPE time correction slew. */
  set_time_correction(new_correction,0,1);
}

GETUSERMODE();

if (olddelta != NULL) {
  /* Caller wants to know remaining amount of previous correction. */
  (long)olddelta->tv_sec = prev_correction / 1000000LL;
  olddelta->tv_usec = prev_correction % 1000000LL;
}

return 0;
}
#endif /* MPE */

#ifdef NEED_HPUX_ADJTIME
/*************************************************************************/
/* (c) Copyright Tai Jin, 1988.  All Rights Reserved.                    */
/*     Hewlett-Packard Laboratories.                                     */
/*                                                                       */
/* Permission is hereby granted for unlimited modification, use, and     */
/* distribution.  This software is made available with no warranty of    */
/* any kind, express or implied.  This copyright notice must remain      */
/* intact in all versions of this software.                              */
/*                                                                       */
/* The author would appreciate it if any bug fixes and enhancements were */
/* to be sent back to him for incorporation into future versions of this */
/* software.  Please send changes to tai@iag.hp.com or ken@sdd.hp.com.   */
/*************************************************************************/

/*
 * Revision history
 *
 * 9 Jul 94	David L. Mills, Unibergity of Delabunch
 *		Implemented variable threshold to limit age of
 *		corrections; reformatted code for readability.
 */

#ifndef lint
static char RCSid[] = "adjtime.c,v 3.1 1993/07/06 01:04:42 jbj Exp";
#endif

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <time.h>
#include <signal.h>
#include "adjtime.h"

#define abs(x)  ((x) < 0 ? -(x) : (x))

/*
 * The following paramters are appropriate for an NTP adjustment
 * interval of one second.
 */
#define ADJ_THRESH 200		/* initial threshold */
#define ADJ_DELTA 4		/* threshold decrement */

static long adjthresh;		/* adjustment threshold */
static long saveup;		/* corrections accumulator */

/*
 * clear_adjtime - reset accumulator and threshold variables
 */
void
_clear_adjtime(void)
{
	saveup = 0;
	adjthresh = ADJ_THRESH;
}

/*
 * adjtime - hp-ux copout of the standard Unix adjtime() system call
 */
int
adjtime(
	register struct timeval *delta,
	register struct timeval *olddelta
	)
{
	struct timeval newdelta;

	/*
	 * Corrections greater than one second are done immediately.
	 */
	if (delta->tv_sec) {
		adjthresh = ADJ_THRESH;
		saveup = 0;
		return(_adjtime(delta, olddelta));
	}

	/*
	 * Corrections less than one second are accumulated until
	 * tripping a threshold, which is initially set at ADJ_THESH and
	 * reduced in ADJ_DELTA steps to zero. The idea here is to
	 * introduce large corrections quickly, while making sure that
	 * small corrections are introduced without excessive delay. The
	 * idea comes from the ARPAnet routing update algorithm.
	 */
	saveup += delta->tv_usec;
	if (abs(saveup) >= adjthresh) {
		adjthresh = ADJ_THRESH;
		newdelta.tv_sec = 0;
		newdelta.tv_usec = saveup;
		saveup = 0;
		return(_adjtime(&newdelta, olddelta));
	} else {
		adjthresh -= ADJ_DELTA;
	}

	/*
	 * While nobody uses it, return the residual before correction,
	 * as per Unix convention.
	 */
	if (olddelta)
	    olddelta->tv_sec = olddelta->tv_usec = 0;
	return(0);
}

/*
 * _adjtime - does the actual work
 */
int
_adjtime(
	register struct timeval *delta,
	register struct timeval *olddelta
	)
{
	register int mqid;
	MsgBuf msg;
	register MsgBuf *msgp = &msg;

	/*
	 * Get the key to the adjtime message queue (note that we must
	 * get it every time because the queue might have been removed
	 * and recreated)
	 */
	if ((mqid = msgget(KEY, 0)) == -1)
	    return (-1);
	msgp->msgb.mtype = CLIENT;
	msgp->msgb.tv = *delta;
	if (olddelta)
	    msgp->msgb.code = DELTA2;
	else
	    msgp->msgb.code = DELTA1;

	/*
	 * Tickle adjtimed and snatch residual, if indicated. Lots of
	 * fanatic error checking here.
	 */
	if (msgsnd(mqid, &msgp->msgp, MSGSIZE, 0) == -1)
	    return (-1);
	if (olddelta) {
		if (msgrcv(mqid, &msgp->msgp, MSGSIZE, SERVER, 0) == -1)
		    return (-1);
		*olddelta = msgp->msgb.tv;
	}
	return (0);
}

#else
# if NEED_QNX_ADJTIME
/*
 * Emulate adjtime() using QNX ClockAdjust().
 * Chris Burghart <burghart@atd.ucar.edu>, 11/2001
 * Miroslaw Pabich <miroslaw_pabich@o2.pl>, 09/2005
 *
 * This is an implementation of adjtime() for QNX.  
 * ClockAdjust() is used to tweak the system clock for about
 * 1 second period until the desired delta is achieved.
 * Time correction slew is limited to reasonable value.
 * Internal rounding and relative errors are reduced.
 */
# include <sys/neutrino.h>
# include <sys/time.h>

# include <ntp_stdlib.h>

/*
 * Time correction slew limit. QNX is a hard real-time system,
 * so don't adjust system clock too fast.
 */
#define CORR_SLEW_LIMIT     0.02  /* [s/s] */

/*
 * Period of system clock adjustment. It should be equal to adjtime
 * execution period (1s). If slightly less than 1s (0.95-0.99), then olddelta
 * residual error (introduced by execution period jitter) will be reduced.
 */
#define ADJUST_PERIOD       0.97  /* [s] */

int 
adjtime (struct timeval *delta, struct timeval *olddelta)
{
    double delta_nsec;
    double delta_nsec_old;
    struct _clockadjust adj;
    struct _clockadjust oldadj;

    /*
     * How many nanoseconds are we adjusting?
     */
    if (delta != NULL)
	delta_nsec = 1e9 * (long)delta->tv_sec + 1e3 * delta->tv_usec;
    else
	delta_nsec = 0;

    /*
     * Build the adjust structure and call ClockAdjust()
     */
    if (delta_nsec != 0)
    {
	struct _clockperiod period;
	long count;
	long increment;
	long increment_limit;
	int isneg = 0;

	/*
	 * Convert to absolute value for future processing
	 */
	if (delta_nsec < 0)
	{
	    isneg = 1;
	    delta_nsec = -delta_nsec;
	}

	/*
	 * Get the current clock period (nanoseconds)
	 */
	if (ClockPeriod (CLOCK_REALTIME, 0, &period, 0) < 0)
	    return -1;

	/*
	 * Compute count and nanoseconds increment
	 */
	count = 1e9 * ADJUST_PERIOD / period.nsec;
	increment = delta_nsec / count + .5;
	/* Reduce relative error */
	if (count > increment + 1)
	{
	    increment = 1 + (long)((delta_nsec - 1) / count);
	    count = delta_nsec / increment + .5;
	}

	/*
	 * Limit the adjust increment to appropriate value
	 */
	increment_limit = CORR_SLEW_LIMIT * period.nsec;
	if (increment > increment_limit)
	{
	    increment = increment_limit;
	    count = delta_nsec / increment + .5;
	    /* Reduce relative error */
	    if (increment > count + 1)
	    {
		count =  1 + (long)((delta_nsec - 1) / increment);
		increment = delta_nsec / count + .5;
	    }
	}

	adj.tick_nsec_inc = isneg ? -increment : increment;
	adj.tick_count = count;
    }
    else
    {
	adj.tick_nsec_inc = 0;
	adj.tick_count = 0;
    }

    if (ClockAdjust (CLOCK_REALTIME, &adj, &oldadj) < 0)
	return -1;

    /*
     * Build olddelta
     */
    delta_nsec_old = (double)oldadj.tick_count * oldadj.tick_nsec_inc;
    if (olddelta != NULL)
    {
	if (delta_nsec_old != 0)
	{
	    /* Reduce rounding error */
	    delta_nsec_old += (delta_nsec_old < 0) ? -500 : 500;
	    olddelta->tv_sec = delta_nsec_old / 1e9;
	    olddelta->tv_usec = (long)(delta_nsec_old - 1e9
				 * (long)olddelta->tv_sec) / 1000;
	}
	else
	{
	    olddelta->tv_sec = 0;
	    olddelta->tv_usec = 0;
	}
    }

    return 0;
}
# else /* no special adjtime() needed */
int adjtime_bs;
# endif
#endif
OpenPOWER on IntegriCloud