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