summaryrefslogtreecommitdiffstats
path: root/contrib/ntp/ntpd/refclock_atom.c
blob: 51153aeb5f1a2d1418180e56dced2336a9cc7442 (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
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504

/*
 * refclock_atom - clock driver for 1-pps signals
 */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <ctype.h>

#include "ntpd.h"
#include "ntp_io.h"
#include "ntp_unixtime.h"
#include "ntp_refclock.h"
#include "ntp_stdlib.h"

#if defined(REFCLOCK) && defined(CLOCK_ATOM)

#ifdef HAVE_PPSAPI
# ifdef HAVE_TIMEPPS_H
#  include <timepps.h>
# else
#  ifdef HAVE_SYS_TIMEPPS_H
#   include <sys/timepps.h>
#  endif
# endif
#endif /* HAVE_PPSAPI */

/*
 * This driver furnishes an interface for pulse-per-second (PPS) signals
 * produced by a cesium clock, timing receiver or related equipment. It
 * can be used to remove accumulated jitter and retime a secondary
 * server when synchronized to a primary server over a congested, wide-
 * area network and before redistributing the time to local clients.
 *
 * Before this driver becomes active, the local clock must be set to
 * within +-500 ms by another means, such as a radio clock or NTP
 * itself. There are two ways to connect the PPS signal, normally at TTL
 * levels, to the computer. One is to shift to EIA levels and connect to
 * pin 8 (DCD) of a serial port. This requires a level converter and
 * may require a one-shot flipflop to lengthen the pulse. The other is
 * to connect the PPS signal directly to pin 10 (ACK) of a PC paralell
 * port. These methods are architecture dependent.
 *
 * Both methods require a modified device driver and kernel interface
 * compatible with the Pulse-per-Second API for Unix-like Operating
 * Systems, Version 1.0, RFC-2783 (PPSAPI). Implementations are
 * available for FreeBSD, Linux, SunOS, Solaris and Alpha. However, at
 * present only the Alpha implementation provides the full generality of
 * the API with multiple PPS drivers and multiple handles per driver.
 *
 * In many configurations a single port is used for the radio timecode
 * and PPS signal. In order to provide for this configuration and others
 * involving dedicated multiple serial/parallel ports, the driver first
 * attempts to open the device /dev/pps%d, where %d is the unit number.
 * If this fails, the driver attempts to open the device specified by
 * the pps configuration command. If a port is to be shared, the pps
 * command must be placed before the radio device(s) and the radio
 * device(s) must be placed before the PPS driver(s) in the
 * configuration file.
 *
 * This driver normally uses the PLL/FLL clock discipline implemented in
 * the ntpd code. If kernel support is available, the kernel PLL/FLL
 * clock discipline is used instead. The default configuration is not to
 * use the kernel PPS discipline, if present. The kernel PPS discipline
 * can be enabled using the pps command.
 *
 * Fudge Factors
 *
 * There are no special fudge factors other than the generic. The fudge
 * time1 parameter can be used to compensate for miscellaneous device
 * driver and OS delays.
 */
/*
 * Interface definitions
 */
#ifdef HAVE_PPSAPI
#define DEVICE		"/dev/pps%d" /* device name and unit */
#endif /* HAVE_PPSAPI */

#define	PRECISION	(-20)	/* precision assumed (about 1 us) */
#define	REFID		"PPS\0"	/* reference ID */
#define	DESCRIPTION	"PPS Clock Discipline" /* WRU */
#define NANOSECOND	1000000000 /* one second (ns) */
#define RANGEGATE	500000	/* range gate (ns) */
#define ASTAGE		8	/* filter stages */

static struct peer *pps_peer;	/* atom driver for PPS sources */

#ifdef HAVE_PPSAPI
/*
 * PPS unit control structure
 */
struct ppsunit {
	struct timespec ts;	/* last timestamp */
	int fddev;		/* pps device descriptor */
	pps_params_t pps_params; /* pps parameters */
	pps_info_t pps_info;	/* last pps data */
	pps_handle_t handle;	/* pps handlebars */
};
#endif /* HAVE_PPSAPI */

/*
 * Function prototypes
 */
static	int	atom_start	P((int, struct peer *));
static	void	atom_poll	P((int, struct peer *));
#ifdef HAVE_PPSAPI
static	void	atom_shutdown	P((int, struct peer *));
static	void	atom_control	P((int, struct refclockstat *, struct
				    refclockstat *, struct peer *));
static	int	atom_pps	P((struct peer *));
static	int	atom_ppsapi	P((struct peer *, int, int));
#endif /* HAVE_PPSAPI */

/*
 * Transfer vector
 */
struct	refclock refclock_atom = {
	atom_start,		/* start up driver */
#ifdef HAVE_PPSAPI
	atom_shutdown,		/* shut down driver */
#else
	noentry,		/* shut down driver */
#endif /* HAVE_PPSAPI */
	atom_poll,		/* transmit poll message */
#ifdef HAVE_PPSAPI
	atom_control,		/* fudge control */
#else
	noentry,		/* fudge control */
#endif /* HAVE_PPSAPI */
	noentry,		/* initialize driver */
	noentry,		/* not used (old atom_buginfo) */
	NOFLAGS			/* not used */
};


/*
 * atom_start - initialize data for processing
 */
static int
atom_start(
	int unit,		/* unit number (not used) */
	struct peer *peer	/* peer structure pointer */
	)
{
	struct refclockproc *pp;
#ifdef HAVE_PPSAPI
	register struct ppsunit *up;
	char device[80];
#endif /* HAVE_PPSAPI */

	/*
	 * Allocate and initialize unit structure
	 */
	pps_peer = peer;
	pp = peer->procptr;
	peer->precision = PRECISION;
	pp->clockdesc = DESCRIPTION;
	pp->stratum = STRATUM_UNSPEC;
	memcpy((char *)&pp->refid, REFID, 4);
	peer->burst = ASTAGE;
#ifdef HAVE_PPSAPI
	up = emalloc(sizeof(struct ppsunit));
	memset(up, 0, sizeof(struct ppsunit));
	pp->unitptr = (caddr_t)up;

	/*
	 * Open PPS device. If this fails and some driver has already
	 * opened the associated radio device, fdpps has the file
	 * descriptor for it.
	 */
	sprintf(device, DEVICE, unit);
	up->fddev = open(device, O_RDWR, 0777);
	if (up->fddev <= 0 && fdpps > 0) {
		strcpy(device, pps_device);
		up->fddev = fdpps;
	}
	if (up->fddev <= 0) {
		msyslog(LOG_ERR,
		    "refclock_atom: %s: %m", device);
		return (0);
	}

	/*
	 * Light off the PPSAPI interface. If this PPS device is shared
	 * with the radio device, take the default options from the pps
	 * command. This is for legacy purposes.
	 */
	if (time_pps_create(up->fddev, &up->handle) < 0) {
		msyslog(LOG_ERR,
		    "refclock_atom: time_pps_create failed: %m");
		return (0);
	}
	return (atom_ppsapi(peer, 0, 0));
#else /* HAVE_PPSAPI */
	return (1);
#endif /* HAVE_PPSAPI */
}


#ifdef HAVE_PPSAPI
/*
 * atom_control - fudge control
 */
static void
atom_control(
	int unit,		/* unit (not used */
	struct refclockstat *in, /* input parameters (not uded) */
	struct refclockstat *out, /* output parameters (not used) */
	struct peer *peer	/* peer structure pointer */
	)
{
	struct refclockproc *pp;

	pp = peer->procptr;
	atom_ppsapi(peer, pp->sloppyclockflag & CLK_FLAG2,
	    pp->sloppyclockflag & CLK_FLAG3);
}


/*
 * Initialize PPSAPI
 */
int
atom_ppsapi(
	struct peer *peer,	/* peer structure pointer */
	int enb_clear,		/* clear enable */
	int enb_hardpps		/* hardpps enable */
	)
{
	struct refclockproc *pp;
	register struct ppsunit *up;
	int capability;

	pp = peer->procptr;
	up = (struct ppsunit *)pp->unitptr;
	if (time_pps_getcap(up->handle, &capability) < 0) {
		msyslog(LOG_ERR,
		    "refclock_atom: time_pps_getcap failed: %m");
		return (0);
	}
	memset(&up->pps_params, 0, sizeof(pps_params_t));
	if (enb_clear)
		up->pps_params.mode = capability & PPS_CAPTURECLEAR;
	else
		up->pps_params.mode = capability & PPS_CAPTUREASSERT;
	if (!up->pps_params.mode) {
		msyslog(LOG_ERR,
		    "refclock_atom: invalid capture edge %d",
		    enb_clear);
		return (0);
	}
	up->pps_params.mode |= PPS_TSFMT_TSPEC;
	if (time_pps_setparams(up->handle, &up->pps_params) < 0) {
		msyslog(LOG_ERR,
		    "refclock_atom: time_pps_setparams failed: %m");
		return (0);
	}
	if (enb_hardpps) {
		if (time_pps_kcbind(up->handle, PPS_KC_HARDPPS,
		    up->pps_params.mode & ~PPS_TSFMT_TSPEC,
		    PPS_TSFMT_TSPEC) < 0) {
			msyslog(LOG_ERR,
			    "refclock_atom: time_pps_kcbind failed: %m");
			return (0);
		}
		pps_enable = 1;
	}
#if DEBUG
	if (debug) {
		time_pps_getparams(up->handle, &up->pps_params);
		printf(
		    "refclock_ppsapi: fd %d capability 0x%x version %d mode 0x%x kern %d\n",
		    up->fddev, capability, up->pps_params.api_version,
		    up->pps_params.mode, enb_hardpps);
	}
#endif
	return (1);
}


/*
 * atom_shutdown - shut down the clock
 */
static void
atom_shutdown(
	int unit,		/* unit number (not used) */
	struct peer *peer	/* peer structure pointer */
	)
{
	struct refclockproc *pp;
	register struct ppsunit *up;

	pp = peer->procptr;
	up = (struct ppsunit *)pp->unitptr;
	if (up->fddev > 0)
		close(up->fddev);
	if (up->handle != 0)
		time_pps_destroy(up->handle);
	if (pps_peer == peer)
		pps_peer = 0;
	free(up);
}


/*
 * atom_pps - receive data from the PPSAPI interface
 *
 * This routine is called once per second when the PPSAPI interface is
 * present. It snatches the PPS timestamp from the kernel and saves the
 * sign-extended fraction in a circular buffer for processing at the
 * next poll event.
 */
static int
atom_pps(
	struct peer *peer	/* peer structure pointer */
	)
{
	register struct ppsunit *up;
	struct refclockproc *pp;
	pps_info_t pps_info;
	struct timespec timeout, ts;
	double dtemp;

	/*
	 * Convert the timespec nanoseconds field to signed double and
	 * save in the median filter. for billboards. No harm is done if
	 * previous data are overwritten. If the discipline comes bum or
	 * the data grow stale, just forget it. A range gate rejects new
	 * samples if less than a jiggle time from the next second.
	 */ 
	pp = peer->procptr;
	up = (struct ppsunit *)pp->unitptr;
	if (up->handle == 0)
		return (-1);
	timeout.tv_sec = 0;
	timeout.tv_nsec = 0;
	memcpy(&pps_info, &up->pps_info, sizeof(pps_info_t));
	if (time_pps_fetch(up->handle, PPS_TSFMT_TSPEC, &up->pps_info,
	    &timeout) < 0)
		return (-1);
	if (up->pps_params.mode & PPS_CAPTUREASSERT) {
		if (pps_info.assert_sequence ==
		    up->pps_info.assert_sequence)
			return (1);
		ts = up->pps_info.assert_timestamp;
	} else if (up->pps_params.mode & PPS_CAPTURECLEAR) {
		if (pps_info.clear_sequence ==
		    up->pps_info.clear_sequence)
			return (1);
		ts = up->pps_info.clear_timestamp;
	} else {
		return (-1);
	}
	if (!((ts.tv_sec == up->ts.tv_sec && ts.tv_nsec -
	    up->ts.tv_nsec > NANOSECOND - RANGEGATE) ||
	    (ts.tv_sec - up->ts.tv_sec == 1 && ts.tv_nsec -
	    up->ts.tv_nsec < RANGEGATE))) {
		up->ts = ts;
		return (1);
	}
	up->ts = ts;
	pp->lastrec.l_ui = ts.tv_sec + JAN_1970;
	dtemp = ts.tv_nsec * FRAC / 1e9;
	if (dtemp >= FRAC)
		pp->lastrec.l_ui++;
	pp->lastrec.l_uf = (u_int32)dtemp;
	if (ts.tv_nsec > NANOSECOND / 2)
		ts.tv_nsec -= NANOSECOND;
	dtemp = -(double)ts.tv_nsec / NANOSECOND;
	SAMPLE(dtemp + pp->fudgetime1);
#ifdef DEBUG
	if (debug > 1)
		printf("atom_pps %f %f\n", dtemp, pp->fudgetime1);
#endif
	return (0);
}
#endif /* HAVE_PPSAPI */


/*
 * pps_sample - receive PPS data from some other clock driver
 *
 * This routine is called once per second when the external clock driver
 * processes PPS information. It processes the PPS timestamp and saves
 * the sign-extended fraction in a circular buffer for processing at the
 * next poll event. This works only for a single PPS device.
 */
int
pps_sample(
	   l_fp *offset		/* PPS offset */
	   )
{
	register struct peer *peer;
	struct refclockproc *pp;
	l_fp lftmp;
	double doffset;

	peer = pps_peer;
	if (peer == 0)		/* nobody home */
		return (1);
	pp = peer->procptr;

	/*
	 * Convert the timeval to l_fp and save for billboards. Sign-
	 * extend the fraction and stash in the buffer. No harm is done
	 * if previous data are overwritten. If the discipline comes bum
	 * or the data grow stale, just forget it.
	 */ 
	pp->lastrec = *offset;
	L_CLR(&lftmp);
	L_ADDF(&lftmp, pp->lastrec.l_f);
	LFPTOD(&lftmp, doffset);
	SAMPLE(-doffset + pp->fudgetime1);
	return (0);
}

/*
 * atom_poll - called by the transmit procedure
 *
 * This routine is called once per second when in burst mode to save PPS
 * sample offsets in the median filter. At the end of the burst period
 * the samples are processed as a heap and the clock filter updated.
 */
static void
atom_poll(
	int unit,		/* unit number (not used) */
	struct peer *peer	/* peer structure pointer */
	)
{
	struct refclockproc *pp;
#ifdef HAVE_PPSAPI
	int err;
#endif /* HAVE_PPSAPI */

	/*
	 * Accumulate samples in the median filter. If a noise sample,
	 * return with no prejudice; if a protocol error, get mean;
	 * otherwise, cool. At the end of each poll interval, do a
	 * little bookeeping and process the surviving samples.
	 */
	pp = peer->procptr;
	pp->polls++;
#ifdef HAVE_PPSAPI
	err = atom_pps(peer);
	if (err < 0) {
		refclock_report(peer, CEVNT_FAULT);
		return;
	}
#endif /* HAVE_PPSAPI */

	/*
	 * Valid time is returned only if the prefer peer has survived
	 * the intersection algorithm and within clock_max of local time
	 * and not too long ago. This ensures the PPS time is within
	 * +-0.5 s of the local time and the seconds numbering is
	 * unambiguous. Note that the leap bits are set no-warning on
	 * the first valid update and the stratum is set at the prefer
	 * peer, unless overriden by a fudge command.
	 */
	if (peer->burst > 0)
		return;
	peer->leap = LEAP_NOTINSYNC;
	if (pp->codeproc == pp->coderecv) {
		refclock_report(peer, CEVNT_TIMEOUT);
		peer->burst = ASTAGE;
		return;

	} else if (sys_prefer == NULL) {
		pp->codeproc = pp->coderecv;
		peer->burst = ASTAGE;
		return;

	} else if (fabs(sys_prefer->offset) > clock_max) {
		pp->codeproc = pp->coderecv;
		peer->burst = ASTAGE;
		return;
	}
	pp->leap = LEAP_NOWARNING;
	if (pp->stratum >= STRATUM_UNSPEC)
		peer->stratum = sys_prefer->stratum;
	else
		peer->stratum = pp->stratum;
	if (peer->stratum == STRATUM_REFCLOCK || peer->stratum ==
	    STRATUM_UNSPEC)
		peer->refid = pp->refid;
	else
		peer->refid = addr2refid(&sys_prefer->srcadr);
	pp->lastref = pp->lastrec;
	refclock_receive(peer);
	peer->burst = ASTAGE;
}
#else
int refclock_atom_bs;
int
pps_sample(
	   l_fp *offset		/* PPS offset */
	   )
{
	return 1;
}
#endif /* REFCLOCK */
OpenPOWER on IntegriCloud