xref: /netbsd-src/external/bsd/ntp/dist/libntp/adjtime.c (revision eabc0478de71e4e011a5b4e0392741e01d491794)
1*eabc0478Schristos /*	$NetBSD: adjtime.c,v 1.7 2024/08/18 20:47:13 christos Exp $	*/
2abb0f93cSkardel 
3abb0f93cSkardel #ifdef HAVE_CONFIG_H
4abb0f93cSkardel # include <config.h>
5abb0f93cSkardel #endif
6abb0f93cSkardel 
7abb0f93cSkardel #ifdef MPE
8abb0f93cSkardel /*
9abb0f93cSkardel  * MPE lacks adjtime(), so we define our own.  But note that time slewing has
10abb0f93cSkardel  * a sub-second accuracy bug documented in SR 5003462838 which prevents ntpd
11abb0f93cSkardel  * from being able to maintain clock synch.  Because of the bug, this adjtime()
12abb0f93cSkardel  * implementation as used by ntpd has a side-effect of screwing up the hardware
13abb0f93cSkardel  * PDC clock, which will need to be reset with a reboot.
14abb0f93cSkardel  *
15abb0f93cSkardel  * This problem affects all versions of MPE at the time of this writing (when
16abb0f93cSkardel  * MPE/iX 7.0 is the most current).  It only causes bad things to happen when
17abb0f93cSkardel  * doing continuous clock synchronization with ntpd; note that you CAN run ntpd
18abb0f93cSkardel  * with "disable ntp" in ntp.conf if you wish to provide a time server.
19abb0f93cSkardel  *
20abb0f93cSkardel  * The one-time clock adjustment functionality of ntpdate and ntp_timeset can
21abb0f93cSkardel  * be used without screwing up the PDC clock.
22abb0f93cSkardel  *
23abb0f93cSkardel  */
24abb0f93cSkardel #include <time.h>
25abb0f93cSkardel 
26abb0f93cSkardel int adjtime(struct timeval *delta, struct timeval *olddelta);
27abb0f93cSkardel 
28abb0f93cSkardel int adjtime(struct timeval *delta, struct timeval *olddelta)
29abb0f93cSkardel 
30abb0f93cSkardel {
31abb0f93cSkardel /* Documented, supported MPE system intrinsics. */
32abb0f93cSkardel 
33abb0f93cSkardel extern void GETPRIVMODE(void);
34abb0f93cSkardel extern void GETUSERMODE(void);
35abb0f93cSkardel 
36abb0f93cSkardel /* Undocumented, unsupported MPE internal functions. */
37abb0f93cSkardel 
38abb0f93cSkardel extern long long current_correction_usecs(void);
39abb0f93cSkardel extern long long get_time(void);
40abb0f93cSkardel extern void get_time_change_info(long long *, char *, char *);
41abb0f93cSkardel extern long long pdc_time(int *);
42abb0f93cSkardel extern void set_time_correction(long long, int, int);
43abb0f93cSkardel extern long long ticks_to_micro(long long);
44abb0f93cSkardel 
45abb0f93cSkardel long long big_sec, big_usec, new_correction = 0LL;
46abb0f93cSkardel long long prev_correction;
47abb0f93cSkardel 
48abb0f93cSkardel if (delta != NULL) {
49abb0f93cSkardel   /* Adjustment required.  Convert delta to 64-bit microseconds. */
50abb0f93cSkardel   big_sec = (long)delta->tv_sec;
51abb0f93cSkardel   big_usec = delta->tv_usec;
52abb0f93cSkardel   new_correction = (big_sec * 1000000LL) + big_usec;
53abb0f93cSkardel }
54abb0f93cSkardel 
55abb0f93cSkardel GETPRIVMODE();
56abb0f93cSkardel 
57abb0f93cSkardel /* Determine how much of a previous correction (if any) we're interrupting. */
58abb0f93cSkardel prev_correction = current_correction_usecs();
59abb0f93cSkardel 
60abb0f93cSkardel if (delta != NULL) {
61abb0f93cSkardel   /* Adjustment required. */
62abb0f93cSkardel 
63abb0f93cSkardel #if 0
64abb0f93cSkardel   /* Speculative code disabled until bug SR 5003462838 is fixed.  This bug
65abb0f93cSkardel      prevents accurate time slewing, and indeed renders ntpd inoperable. */
66abb0f93cSkardel 
67abb0f93cSkardel   if (prev_correction != 0LL) {
68abb0f93cSkardel     /* A previous adjustment did not complete.  Since the PDC UTC clock was
69abb0f93cSkardel     immediately jumped at the start of the previous adjustment, we must
70abb0f93cSkardel     explicitly reset it to the value of the MPE local time clock minus the
71abb0f93cSkardel     time zone offset. */
72abb0f93cSkardel 
73abb0f93cSkardel     char pwf_since_boot, recover_pwf_time;
74abb0f93cSkardel     long long offset_ticks, offset_usecs, pdc_usecs_current, pdc_usecs_wanted;
75abb0f93cSkardel     int hpe_status;
76abb0f93cSkardel 
77abb0f93cSkardel     get_time_change_info(&offset_ticks, &pwf_since_boot, &recover_pwf_time);
78abb0f93cSkardel     offset_usecs = ticks_to_micro(offset_ticks);
79abb0f93cSkardel     pdc_usecs_wanted = get_time() - offset_usecs;
80abb0f93cSkardel     pdc_usecs_current = pdc_time(&hpe_status);
81abb0f93cSkardel     if (hpe_status == 0)
82abb0f93cSkardel       /* Force new PDC time by starting an extra correction. */
83abb0f93cSkardel       set_time_correction(pdc_usecs_wanted - pdc_usecs_current,0,1);
84abb0f93cSkardel   }
85abb0f93cSkardel #endif /* 0 */
86abb0f93cSkardel 
87abb0f93cSkardel   /* Immediately jump the PDC time to the new value, and then initiate a
88abb0f93cSkardel      gradual MPE time correction slew. */
89abb0f93cSkardel   set_time_correction(new_correction,0,1);
90abb0f93cSkardel }
91abb0f93cSkardel 
92abb0f93cSkardel GETUSERMODE();
93abb0f93cSkardel 
94abb0f93cSkardel if (olddelta != NULL) {
95abb0f93cSkardel   /* Caller wants to know remaining amount of previous correction. */
96abb0f93cSkardel   (long)olddelta->tv_sec = prev_correction / 1000000LL;
97abb0f93cSkardel   olddelta->tv_usec = prev_correction % 1000000LL;
98abb0f93cSkardel }
99abb0f93cSkardel 
100abb0f93cSkardel return 0;
101abb0f93cSkardel }
102abb0f93cSkardel #endif /* MPE */
103abb0f93cSkardel 
104abb0f93cSkardel #ifdef NEED_HPUX_ADJTIME
105abb0f93cSkardel /*************************************************************************/
106abb0f93cSkardel /* (c) Copyright Tai Jin, 1988.  All Rights Reserved.                    */
107abb0f93cSkardel /*     Hewlett-Packard Laboratories.                                     */
108abb0f93cSkardel /*                                                                       */
109abb0f93cSkardel /* Permission is hereby granted for unlimited modification, use, and     */
110abb0f93cSkardel /* distribution.  This software is made available with no warranty of    */
111abb0f93cSkardel /* any kind, express or implied.  This copyright notice must remain      */
112abb0f93cSkardel /* intact in all versions of this software.                              */
113abb0f93cSkardel /*                                                                       */
114abb0f93cSkardel /* The author would appreciate it if any bug fixes and enhancements were */
115abb0f93cSkardel /* to be sent back to him for incorporation into future versions of this */
116abb0f93cSkardel /* software.  Please send changes to tai@iag.hp.com or ken@sdd.hp.com.   */
117abb0f93cSkardel /*************************************************************************/
118abb0f93cSkardel 
119abb0f93cSkardel /*
120abb0f93cSkardel  * Revision history
121abb0f93cSkardel  *
122abb0f93cSkardel  * 9 Jul 94	David L. Mills, Unibergity of Delabunch
123abb0f93cSkardel  *		Implemented variable threshold to limit age of
124abb0f93cSkardel  *		corrections; reformatted code for readability.
125abb0f93cSkardel  */
126abb0f93cSkardel 
127abb0f93cSkardel #ifndef lint
128abb0f93cSkardel static char RCSid[] = "adjtime.c,v 3.1 1993/07/06 01:04:42 jbj Exp";
129abb0f93cSkardel #endif
130abb0f93cSkardel 
131abb0f93cSkardel #include <sys/types.h>
132abb0f93cSkardel #include <sys/ipc.h>
133abb0f93cSkardel #include <sys/msg.h>
134abb0f93cSkardel #include <time.h>
135abb0f93cSkardel #include <signal.h>
136abb0f93cSkardel #include "adjtime.h"
137abb0f93cSkardel 
138abb0f93cSkardel #define abs(x)  ((x) < 0 ? -(x) : (x))
139abb0f93cSkardel 
140abb0f93cSkardel /*
141abb0f93cSkardel  * The following paramters are appropriate for an NTP adjustment
142abb0f93cSkardel  * interval of one second.
143abb0f93cSkardel  */
144abb0f93cSkardel #define ADJ_THRESH 200		/* initial threshold */
145abb0f93cSkardel #define ADJ_DELTA 4		/* threshold decrement */
146abb0f93cSkardel 
147abb0f93cSkardel static long adjthresh;		/* adjustment threshold */
148abb0f93cSkardel static long saveup;		/* corrections accumulator */
149abb0f93cSkardel 
150abb0f93cSkardel /*
151abb0f93cSkardel  * clear_adjtime - reset accumulator and threshold variables
152abb0f93cSkardel  */
153abb0f93cSkardel void
154abb0f93cSkardel _clear_adjtime(void)
155abb0f93cSkardel {
156abb0f93cSkardel 	saveup = 0;
157abb0f93cSkardel 	adjthresh = ADJ_THRESH;
158abb0f93cSkardel }
159abb0f93cSkardel 
160abb0f93cSkardel /*
161abb0f93cSkardel  * adjtime - hp-ux copout of the standard Unix adjtime() system call
162abb0f93cSkardel  */
163abb0f93cSkardel int
164abb0f93cSkardel adjtime(
165abb0f93cSkardel 	register struct timeval *delta,
166abb0f93cSkardel 	register struct timeval *olddelta
167abb0f93cSkardel 	)
168abb0f93cSkardel {
169abb0f93cSkardel 	struct timeval newdelta;
170abb0f93cSkardel 
171abb0f93cSkardel 	/*
172abb0f93cSkardel 	 * Corrections greater than one second are done immediately.
173abb0f93cSkardel 	 */
174abb0f93cSkardel 	if (delta->tv_sec) {
175abb0f93cSkardel 		adjthresh = ADJ_THRESH;
176abb0f93cSkardel 		saveup = 0;
177abb0f93cSkardel 		return(_adjtime(delta, olddelta));
178abb0f93cSkardel 	}
179abb0f93cSkardel 
180abb0f93cSkardel 	/*
181abb0f93cSkardel 	 * Corrections less than one second are accumulated until
182abb0f93cSkardel 	 * tripping a threshold, which is initially set at ADJ_THESH and
183abb0f93cSkardel 	 * reduced in ADJ_DELTA steps to zero. The idea here is to
184abb0f93cSkardel 	 * introduce large corrections quickly, while making sure that
185abb0f93cSkardel 	 * small corrections are introduced without excessive delay. The
186abb0f93cSkardel 	 * idea comes from the ARPAnet routing update algorithm.
187abb0f93cSkardel 	 */
188abb0f93cSkardel 	saveup += delta->tv_usec;
189abb0f93cSkardel 	if (abs(saveup) >= adjthresh) {
190abb0f93cSkardel 		adjthresh = ADJ_THRESH;
191abb0f93cSkardel 		newdelta.tv_sec = 0;
192abb0f93cSkardel 		newdelta.tv_usec = saveup;
193abb0f93cSkardel 		saveup = 0;
194abb0f93cSkardel 		return(_adjtime(&newdelta, olddelta));
195abb0f93cSkardel 	} else {
196abb0f93cSkardel 		adjthresh -= ADJ_DELTA;
197abb0f93cSkardel 	}
198abb0f93cSkardel 
199abb0f93cSkardel 	/*
200abb0f93cSkardel 	 * While nobody uses it, return the residual before correction,
201abb0f93cSkardel 	 * as per Unix convention.
202abb0f93cSkardel 	 */
203abb0f93cSkardel 	if (olddelta)
204abb0f93cSkardel 	    olddelta->tv_sec = olddelta->tv_usec = 0;
205abb0f93cSkardel 	return(0);
206abb0f93cSkardel }
207abb0f93cSkardel 
208abb0f93cSkardel /*
209abb0f93cSkardel  * _adjtime - does the actual work
210abb0f93cSkardel  */
211abb0f93cSkardel int
212abb0f93cSkardel _adjtime(
213abb0f93cSkardel 	register struct timeval *delta,
214abb0f93cSkardel 	register struct timeval *olddelta
215abb0f93cSkardel 	)
216abb0f93cSkardel {
217abb0f93cSkardel 	register int mqid;
218abb0f93cSkardel 	MsgBuf msg;
219abb0f93cSkardel 	register MsgBuf *msgp = &msg;
220abb0f93cSkardel 
221abb0f93cSkardel 	/*
222abb0f93cSkardel 	 * Get the key to the adjtime message queue (note that we must
223abb0f93cSkardel 	 * get it every time because the queue might have been removed
224abb0f93cSkardel 	 * and recreated)
225abb0f93cSkardel 	 */
226abb0f93cSkardel 	if ((mqid = msgget(KEY, 0)) == -1)
227abb0f93cSkardel 	    return (-1);
228abb0f93cSkardel 	msgp->msgb.mtype = CLIENT;
229abb0f93cSkardel 	msgp->msgb.tv = *delta;
230abb0f93cSkardel 	if (olddelta)
231abb0f93cSkardel 	    msgp->msgb.code = DELTA2;
232abb0f93cSkardel 	else
233abb0f93cSkardel 	    msgp->msgb.code = DELTA1;
234abb0f93cSkardel 
235abb0f93cSkardel 	/*
236abb0f93cSkardel 	 * Tickle adjtimed and snatch residual, if indicated. Lots of
237abb0f93cSkardel 	 * fanatic error checking here.
238abb0f93cSkardel 	 */
239abb0f93cSkardel 	if (msgsnd(mqid, &msgp->msgp, MSGSIZE, 0) == -1)
240abb0f93cSkardel 	    return (-1);
241abb0f93cSkardel 	if (olddelta) {
242abb0f93cSkardel 		if (msgrcv(mqid, &msgp->msgp, MSGSIZE, SERVER, 0) == -1)
243abb0f93cSkardel 		    return (-1);
244abb0f93cSkardel 		*olddelta = msgp->msgb.tv;
245abb0f93cSkardel 	}
246abb0f93cSkardel 	return (0);
247abb0f93cSkardel }
248abb0f93cSkardel 
249abb0f93cSkardel #else
250abb0f93cSkardel # if NEED_QNX_ADJTIME
251abb0f93cSkardel /*
252abb0f93cSkardel  * Emulate adjtime() using QNX ClockAdjust().
253abb0f93cSkardel  * Chris Burghart <burghart@atd.ucar.edu>, 11/2001
254abb0f93cSkardel  * Miroslaw Pabich <miroslaw_pabich@o2.pl>, 09/2005
255abb0f93cSkardel  *
256abb0f93cSkardel  * This is an implementation of adjtime() for QNX.
257abb0f93cSkardel  * ClockAdjust() is used to tweak the system clock for about
258abb0f93cSkardel  * 1 second period until the desired delta is achieved.
259abb0f93cSkardel  * Time correction slew is limited to reasonable value.
260abb0f93cSkardel  * Internal rounding and relative errors are reduced.
261abb0f93cSkardel  */
262abb0f93cSkardel # include <sys/neutrino.h>
263abb0f93cSkardel # include <sys/time.h>
264abb0f93cSkardel 
265abb0f93cSkardel # include <ntp_stdlib.h>
266abb0f93cSkardel 
267abb0f93cSkardel /*
268abb0f93cSkardel  * Time correction slew limit. QNX is a hard real-time system,
269abb0f93cSkardel  * so don't adjust system clock too fast.
270abb0f93cSkardel  */
271abb0f93cSkardel #define CORR_SLEW_LIMIT     0.02  /* [s/s] */
272abb0f93cSkardel 
273abb0f93cSkardel /*
274abb0f93cSkardel  * Period of system clock adjustment. It should be equal to adjtime
275abb0f93cSkardel  * execution period (1s). If slightly less than 1s (0.95-0.99), then olddelta
276abb0f93cSkardel  * residual error (introduced by execution period jitter) will be reduced.
277abb0f93cSkardel  */
278abb0f93cSkardel #define ADJUST_PERIOD       0.97  /* [s] */
279abb0f93cSkardel 
280abb0f93cSkardel int
281abb0f93cSkardel adjtime (struct timeval *delta, struct timeval *olddelta)
282abb0f93cSkardel {
283abb0f93cSkardel     double delta_nsec;
284abb0f93cSkardel     double delta_nsec_old;
285abb0f93cSkardel     struct _clockadjust adj;
286abb0f93cSkardel     struct _clockadjust oldadj;
287abb0f93cSkardel 
288abb0f93cSkardel     /*
289abb0f93cSkardel      * How many nanoseconds are we adjusting?
290abb0f93cSkardel      */
291abb0f93cSkardel     if (delta != NULL)
292abb0f93cSkardel 	delta_nsec = 1e9 * (long)delta->tv_sec + 1e3 * delta->tv_usec;
293abb0f93cSkardel     else
294abb0f93cSkardel 	delta_nsec = 0;
295abb0f93cSkardel 
296abb0f93cSkardel     /*
297abb0f93cSkardel      * Build the adjust structure and call ClockAdjust()
298abb0f93cSkardel      */
299abb0f93cSkardel     if (delta_nsec != 0)
300abb0f93cSkardel     {
301abb0f93cSkardel 	struct _clockperiod period;
302abb0f93cSkardel 	long count;
303abb0f93cSkardel 	long increment;
304abb0f93cSkardel 	long increment_limit;
305abb0f93cSkardel 	int isneg = 0;
306abb0f93cSkardel 
307abb0f93cSkardel 	/*
308abb0f93cSkardel 	 * Convert to absolute value for future processing
309abb0f93cSkardel 	 */
310abb0f93cSkardel 	if (delta_nsec < 0)
311abb0f93cSkardel 	{
312abb0f93cSkardel 	    isneg = 1;
313abb0f93cSkardel 	    delta_nsec = -delta_nsec;
314abb0f93cSkardel 	}
315abb0f93cSkardel 
316abb0f93cSkardel 	/*
317abb0f93cSkardel 	 * Get the current clock period (nanoseconds)
318abb0f93cSkardel 	 */
3194eea345dSchristos 	if (ClockPeriod (CLOCK_REALTIME, 0, &period, 0) == -1)
320abb0f93cSkardel 	    return -1;
321abb0f93cSkardel 
322abb0f93cSkardel 	/*
323abb0f93cSkardel 	 * Compute count and nanoseconds increment
324abb0f93cSkardel 	 */
325abb0f93cSkardel 	count = 1e9 * ADJUST_PERIOD / period.nsec;
326abb0f93cSkardel 	increment = delta_nsec / count + .5;
327abb0f93cSkardel 	/* Reduce relative error */
328abb0f93cSkardel 	if (count > increment + 1)
329abb0f93cSkardel 	{
330abb0f93cSkardel 	    increment = 1 + (long)((delta_nsec - 1) / count);
331abb0f93cSkardel 	    count = delta_nsec / increment + .5;
332abb0f93cSkardel 	}
333abb0f93cSkardel 
334abb0f93cSkardel 	/*
335abb0f93cSkardel 	 * Limit the adjust increment to appropriate value
336abb0f93cSkardel 	 */
337abb0f93cSkardel 	increment_limit = CORR_SLEW_LIMIT * period.nsec;
338abb0f93cSkardel 	if (increment > increment_limit)
339abb0f93cSkardel 	{
340abb0f93cSkardel 	    increment = increment_limit;
341abb0f93cSkardel 	    count = delta_nsec / increment + .5;
342abb0f93cSkardel 	    /* Reduce relative error */
343abb0f93cSkardel 	    if (increment > count + 1)
344abb0f93cSkardel 	    {
345abb0f93cSkardel 		count =  1 + (long)((delta_nsec - 1) / increment);
346abb0f93cSkardel 		increment = delta_nsec / count + .5;
347abb0f93cSkardel 	    }
348abb0f93cSkardel 	}
349abb0f93cSkardel 
350abb0f93cSkardel 	adj.tick_nsec_inc = isneg ? -increment : increment;
351abb0f93cSkardel 	adj.tick_count = count;
352abb0f93cSkardel     }
353abb0f93cSkardel     else
354abb0f93cSkardel     {
355abb0f93cSkardel 	adj.tick_nsec_inc = 0;
356abb0f93cSkardel 	adj.tick_count = 0;
357abb0f93cSkardel     }
358abb0f93cSkardel 
3594eea345dSchristos     if (ClockAdjust (CLOCK_REALTIME, &adj, &oldadj) == -1)
360abb0f93cSkardel 	return -1;
361abb0f93cSkardel 
362abb0f93cSkardel     /*
363abb0f93cSkardel      * Build olddelta
364abb0f93cSkardel      */
365abb0f93cSkardel     delta_nsec_old = (double)oldadj.tick_count * oldadj.tick_nsec_inc;
366abb0f93cSkardel     if (olddelta != NULL)
367abb0f93cSkardel     {
368abb0f93cSkardel 	if (delta_nsec_old != 0)
369abb0f93cSkardel 	{
370abb0f93cSkardel 	    /* Reduce rounding error */
371abb0f93cSkardel 	    delta_nsec_old += (delta_nsec_old < 0) ? -500 : 500;
372abb0f93cSkardel 	    olddelta->tv_sec = delta_nsec_old / 1e9;
373abb0f93cSkardel 	    olddelta->tv_usec = (long)(delta_nsec_old - 1e9
374abb0f93cSkardel 				 * (long)olddelta->tv_sec) / 1000;
375abb0f93cSkardel 	}
376abb0f93cSkardel 	else
377abb0f93cSkardel 	{
378abb0f93cSkardel 	    olddelta->tv_sec = 0;
379abb0f93cSkardel 	    olddelta->tv_usec = 0;
380abb0f93cSkardel 	}
381abb0f93cSkardel     }
382abb0f93cSkardel 
383abb0f93cSkardel     return 0;
384abb0f93cSkardel }
385abb0f93cSkardel # else /* no special adjtime() needed */
386*eabc0478Schristos NONEMPTY_TRANSLATION_UNIT
387abb0f93cSkardel # endif
388abb0f93cSkardel #endif
389