refclock_atom.c revision 132451
1
2/*
3 * refclock_atom - clock driver for 1-pps signals
4 */
5#ifdef HAVE_CONFIG_H
6#include <config.h>
7#endif
8
9#include <stdio.h>
10#include <ctype.h>
11
12#include "ntpd.h"
13#include "ntp_io.h"
14#include "ntp_unixtime.h"
15#include "ntp_refclock.h"
16#include "ntp_stdlib.h"
17
18#if defined(REFCLOCK) && defined(CLOCK_ATOM)
19
20#ifdef HAVE_PPSAPI
21# ifdef HAVE_TIMEPPS_H
22#  include <timepps.h>
23# else
24#  ifdef HAVE_SYS_TIMEPPS_H
25#   include <sys/timepps.h>
26#  endif
27# endif
28#endif /* HAVE_PPSAPI */
29
30/*
31 * This driver furnishes an interface for pulse-per-second (PPS) signals
32 * produced by a cesium clock, timing receiver or related equipment. It
33 * can be used to remove accumulated jitter and retime a secondary
34 * server when synchronized to a primary server over a congested, wide-
35 * area network and before redistributing the time to local clients.
36 *
37 * Before this driver becomes active, the local clock must be set to
38 * within +-500 ms by another means, such as a radio clock or NTP
39 * itself. There are two ways to connect the PPS signal, normally at TTL
40 * levels, to the computer. One is to shift to EIA levels and connect to
41 * pin 8 (DCD) of a serial port. This requires a level converter and
42 * may require a one-shot flipflop to lengthen the pulse. The other is
43 * to connect the PPS signal directly to pin 10 (ACK) of a PC paralell
44 * port. These methods are architecture dependent.
45 *
46 * Both methods require a modified device driver and kernel interface
47 * compatible with the Pulse-per-Second API for Unix-like Operating
48 * Systems, Version 1.0, RFC-2783 (PPSAPI). Implementations are
49 * available for FreeBSD, Linux, SunOS, Solaris and Alpha. However, at
50 * present only the Alpha implementation provides the full generality of
51 * the API with multiple PPS drivers and multiple handles per driver.
52 *
53 * In many configurations a single port is used for the radio timecode
54 * and PPS signal. In order to provide for this configuration and others
55 * involving dedicated multiple serial/parallel ports, the driver first
56 * attempts to open the device /dev/pps%d, where %d is the unit number.
57 * If this fails, the driver attempts to open the device specified by
58 * the pps configuration command. If a port is to be shared, the pps
59 * command must be placed before the radio device(s) and the radio
60 * device(s) must be placed before the PPS driver(s) in the
61 * configuration file.
62 *
63 * This driver normally uses the PLL/FLL clock discipline implemented in
64 * the ntpd code. If kernel support is available, the kernel PLL/FLL
65 * clock discipline is used instead. The default configuration is not to
66 * use the kernel PPS discipline, if present. The kernel PPS discipline
67 * can be enabled using the pps command.
68 *
69 * Fudge Factors
70 *
71 * There are no special fudge factors other than the generic. The fudge
72 * time1 parameter can be used to compensate for miscellaneous device
73 * driver and OS delays.
74 */
75/*
76 * Interface definitions
77 */
78#ifdef HAVE_PPSAPI
79#define DEVICE		"/dev/pps%d" /* device name and unit */
80#endif /* HAVE_PPSAPI */
81
82#define	PRECISION	(-20)	/* precision assumed (about 1 us) */
83#define	REFID		"PPS\0"	/* reference ID */
84#define	DESCRIPTION	"PPS Clock Discipline" /* WRU */
85#define NANOSECOND	1000000000 /* one second (ns) */
86#define RANGEGATE	500000	/* range gate (ns) */
87#define ASTAGE		8	/* filter stages */
88
89static struct peer *pps_peer;	/* atom driver for PPS sources */
90
91#ifdef HAVE_PPSAPI
92/*
93 * PPS unit control structure
94 */
95struct ppsunit {
96	struct timespec ts;	/* last timestamp */
97	int fddev;		/* pps device descriptor */
98	pps_params_t pps_params; /* pps parameters */
99	pps_info_t pps_info;	/* last pps data */
100	pps_handle_t handle;	/* pps handlebars */
101};
102#endif /* HAVE_PPSAPI */
103
104/*
105 * Function prototypes
106 */
107static	int	atom_start	P((int, struct peer *));
108static	void	atom_poll	P((int, struct peer *));
109#ifdef HAVE_PPSAPI
110static	void	atom_shutdown	P((int, struct peer *));
111static	void	atom_control	P((int, struct refclockstat *, struct
112				    refclockstat *, struct peer *));
113static	int	atom_pps	P((struct peer *));
114static	int	atom_ppsapi	P((struct peer *, int, int));
115#endif /* HAVE_PPSAPI */
116
117/*
118 * Transfer vector
119 */
120struct	refclock refclock_atom = {
121	atom_start,		/* start up driver */
122#ifdef HAVE_PPSAPI
123	atom_shutdown,		/* shut down driver */
124#else
125	noentry,		/* shut down driver */
126#endif /* HAVE_PPSAPI */
127	atom_poll,		/* transmit poll message */
128#ifdef HAVE_PPSAPI
129	atom_control,		/* fudge control */
130#else
131	noentry,		/* fudge control */
132#endif /* HAVE_PPSAPI */
133	noentry,		/* initialize driver */
134	noentry,		/* not used (old atom_buginfo) */
135	NOFLAGS			/* not used */
136};
137
138
139/*
140 * atom_start - initialize data for processing
141 */
142static int
143atom_start(
144	int unit,		/* unit number (not used) */
145	struct peer *peer	/* peer structure pointer */
146	)
147{
148	struct refclockproc *pp;
149#ifdef HAVE_PPSAPI
150	register struct ppsunit *up;
151	char device[80];
152#endif /* HAVE_PPSAPI */
153
154	/*
155	 * Allocate and initialize unit structure
156	 */
157	pps_peer = peer;
158	pp = peer->procptr;
159	peer->precision = PRECISION;
160	pp->clockdesc = DESCRIPTION;
161	pp->stratum = STRATUM_UNSPEC;
162	memcpy((char *)&pp->refid, REFID, 4);
163	peer->burst = ASTAGE;
164#ifdef HAVE_PPSAPI
165	up = emalloc(sizeof(struct ppsunit));
166	memset(up, 0, sizeof(struct ppsunit));
167	pp->unitptr = (caddr_t)up;
168
169	/*
170	 * Open PPS device. If this fails and some driver has already
171	 * opened the associated radio device, fdpps has the file
172	 * descriptor for it.
173	 */
174	sprintf(device, DEVICE, unit);
175	up->fddev = open(device, O_RDWR, 0777);
176	if (up->fddev <= 0 && fdpps > 0) {
177		strcpy(device, pps_device);
178		up->fddev = fdpps;
179	}
180	if (up->fddev <= 0) {
181		msyslog(LOG_ERR,
182		    "refclock_atom: %s: %m", device);
183		return (0);
184	}
185
186	/*
187	 * Light off the PPSAPI interface. If this PPS device is shared
188	 * with the radio device, take the default options from the pps
189	 * command. This is for legacy purposes.
190	 */
191	if (time_pps_create(up->fddev, &up->handle) < 0) {
192		msyslog(LOG_ERR,
193		    "refclock_atom: time_pps_create failed: %m");
194		return (0);
195	}
196	return (atom_ppsapi(peer, 0, 0));
197#else /* HAVE_PPSAPI */
198	return (1);
199#endif /* HAVE_PPSAPI */
200}
201
202
203#ifdef HAVE_PPSAPI
204/*
205 * atom_control - fudge control
206 */
207static void
208atom_control(
209	int unit,		/* unit (not used */
210	struct refclockstat *in, /* input parameters (not uded) */
211	struct refclockstat *out, /* output parameters (not used) */
212	struct peer *peer	/* peer structure pointer */
213	)
214{
215	struct refclockproc *pp;
216
217	pp = peer->procptr;
218	atom_ppsapi(peer, pp->sloppyclockflag & CLK_FLAG2,
219	    pp->sloppyclockflag & CLK_FLAG3);
220}
221
222
223/*
224 * Initialize PPSAPI
225 */
226int
227atom_ppsapi(
228	struct peer *peer,	/* peer structure pointer */
229	int enb_clear,		/* clear enable */
230	int enb_hardpps		/* hardpps enable */
231	)
232{
233	struct refclockproc *pp;
234	register struct ppsunit *up;
235	int capability;
236
237	pp = peer->procptr;
238	up = (struct ppsunit *)pp->unitptr;
239	if (time_pps_getcap(up->handle, &capability) < 0) {
240		msyslog(LOG_ERR,
241		    "refclock_atom: time_pps_getcap failed: %m");
242		return (0);
243	}
244	memset(&up->pps_params, 0, sizeof(pps_params_t));
245	if (enb_clear)
246		up->pps_params.mode = capability & PPS_CAPTURECLEAR;
247	else
248		up->pps_params.mode = capability & PPS_CAPTUREASSERT;
249	if (!up->pps_params.mode) {
250		msyslog(LOG_ERR,
251		    "refclock_atom: invalid capture edge %d",
252		    enb_clear);
253		return (0);
254	}
255	up->pps_params.mode |= PPS_TSFMT_TSPEC;
256	if (time_pps_setparams(up->handle, &up->pps_params) < 0) {
257		msyslog(LOG_ERR,
258		    "refclock_atom: time_pps_setparams failed: %m");
259		return (0);
260	}
261	if (enb_hardpps) {
262		if (time_pps_kcbind(up->handle, PPS_KC_HARDPPS,
263		    up->pps_params.mode & ~PPS_TSFMT_TSPEC,
264		    PPS_TSFMT_TSPEC) < 0) {
265			msyslog(LOG_ERR,
266			    "refclock_atom: time_pps_kcbind failed: %m");
267			return (0);
268		}
269		pps_enable = 1;
270	}
271#if DEBUG
272	if (debug) {
273		time_pps_getparams(up->handle, &up->pps_params);
274		printf(
275		    "refclock_ppsapi: fd %d capability 0x%x version %d mode 0x%x kern %d\n",
276		    up->fddev, capability, up->pps_params.api_version,
277		    up->pps_params.mode, enb_hardpps);
278	}
279#endif
280	return (1);
281}
282
283
284/*
285 * atom_shutdown - shut down the clock
286 */
287static void
288atom_shutdown(
289	int unit,		/* unit number (not used) */
290	struct peer *peer	/* peer structure pointer */
291	)
292{
293	struct refclockproc *pp;
294	register struct ppsunit *up;
295
296	pp = peer->procptr;
297	up = (struct ppsunit *)pp->unitptr;
298	if (up->fddev > 0)
299		close(up->fddev);
300	if (up->handle != 0)
301		time_pps_destroy(up->handle);
302	if (pps_peer == peer)
303		pps_peer = 0;
304	free(up);
305}
306
307
308/*
309 * atom_pps - receive data from the PPSAPI interface
310 *
311 * This routine is called once per second when the PPSAPI interface is
312 * present. It snatches the PPS timestamp from the kernel and saves the
313 * sign-extended fraction in a circular buffer for processing at the
314 * next poll event.
315 */
316static int
317atom_pps(
318	struct peer *peer	/* peer structure pointer */
319	)
320{
321	register struct ppsunit *up;
322	struct refclockproc *pp;
323	pps_info_t pps_info;
324	struct timespec timeout, ts;
325	double dtemp;
326
327	/*
328	 * Convert the timespec nanoseconds field to signed double and
329	 * save in the median filter. for billboards. No harm is done if
330	 * previous data are overwritten. If the discipline comes bum or
331	 * the data grow stale, just forget it. A range gate rejects new
332	 * samples if less than a jiggle time from the next second.
333	 */
334	pp = peer->procptr;
335	up = (struct ppsunit *)pp->unitptr;
336	if (up->handle == 0)
337		return (-1);
338	timeout.tv_sec = 0;
339	timeout.tv_nsec = 0;
340	memcpy(&pps_info, &up->pps_info, sizeof(pps_info_t));
341	if (time_pps_fetch(up->handle, PPS_TSFMT_TSPEC, &up->pps_info,
342	    &timeout) < 0)
343		return (-1);
344	if (up->pps_params.mode & PPS_CAPTUREASSERT) {
345		if (pps_info.assert_sequence ==
346		    up->pps_info.assert_sequence)
347			return (1);
348		ts = up->pps_info.assert_timestamp;
349	} else if (up->pps_params.mode & PPS_CAPTURECLEAR) {
350		if (pps_info.clear_sequence ==
351		    up->pps_info.clear_sequence)
352			return (1);
353		ts = up->pps_info.clear_timestamp;
354	} else {
355		return (-1);
356	}
357	if (!((ts.tv_sec == up->ts.tv_sec && ts.tv_nsec -
358	    up->ts.tv_nsec > NANOSECOND - RANGEGATE) ||
359	    (ts.tv_sec - up->ts.tv_sec == 1 && ts.tv_nsec -
360	    up->ts.tv_nsec < RANGEGATE))) {
361		up->ts = ts;
362		return (1);
363	}
364	up->ts = ts;
365	pp->lastrec.l_ui = ts.tv_sec + JAN_1970;
366	dtemp = ts.tv_nsec * FRAC / 1e9;
367	if (dtemp >= FRAC)
368		pp->lastrec.l_ui++;
369	pp->lastrec.l_uf = (u_int32)dtemp;
370	if (ts.tv_nsec > NANOSECOND / 2)
371		ts.tv_nsec -= NANOSECOND;
372	dtemp = -(double)ts.tv_nsec / NANOSECOND;
373	SAMPLE(dtemp + pp->fudgetime1);
374#ifdef DEBUG
375	if (debug > 1)
376		printf("atom_pps %f %f\n", dtemp, pp->fudgetime1);
377#endif
378	return (0);
379}
380#endif /* HAVE_PPSAPI */
381
382
383/*
384 * pps_sample - receive PPS data from some other clock driver
385 *
386 * This routine is called once per second when the external clock driver
387 * processes PPS information. It processes the PPS timestamp and saves
388 * the sign-extended fraction in a circular buffer for processing at the
389 * next poll event. This works only for a single PPS device.
390 */
391int
392pps_sample(
393	   l_fp *offset		/* PPS offset */
394	   )
395{
396	register struct peer *peer;
397	struct refclockproc *pp;
398	l_fp lftmp;
399	double doffset;
400
401	peer = pps_peer;
402	if (peer == 0)		/* nobody home */
403		return (1);
404	pp = peer->procptr;
405
406	/*
407	 * Convert the timeval to l_fp and save for billboards. Sign-
408	 * extend the fraction and stash in the buffer. No harm is done
409	 * if previous data are overwritten. If the discipline comes bum
410	 * or the data grow stale, just forget it.
411	 */
412	pp->lastrec = *offset;
413	L_CLR(&lftmp);
414	L_ADDF(&lftmp, pp->lastrec.l_f);
415	LFPTOD(&lftmp, doffset);
416	SAMPLE(-doffset + pp->fudgetime1);
417	return (0);
418}
419
420/*
421 * atom_poll - called by the transmit procedure
422 *
423 * This routine is called once per second when in burst mode to save PPS
424 * sample offsets in the median filter. At the end of the burst period
425 * the samples are processed as a heap and the clock filter updated.
426 */
427static void
428atom_poll(
429	int unit,		/* unit number (not used) */
430	struct peer *peer	/* peer structure pointer */
431	)
432{
433	struct refclockproc *pp;
434#ifdef HAVE_PPSAPI
435	int err;
436#endif /* HAVE_PPSAPI */
437
438	/*
439	 * Accumulate samples in the median filter. If a noise sample,
440	 * return with no prejudice; if a protocol error, get mean;
441	 * otherwise, cool. At the end of each poll interval, do a
442	 * little bookeeping and process the surviving samples.
443	 */
444	pp = peer->procptr;
445	pp->polls++;
446#ifdef HAVE_PPSAPI
447	err = atom_pps(peer);
448	if (err < 0) {
449		refclock_report(peer, CEVNT_FAULT);
450		return;
451	}
452#endif /* HAVE_PPSAPI */
453
454	/*
455	 * Valid time is returned only if the prefer peer has survived
456	 * the intersection algorithm and within clock_max of local time
457	 * and not too long ago. This ensures the PPS time is within
458	 * +-0.5 s of the local time and the seconds numbering is
459	 * unambiguous. Note that the leap bits are set no-warning on
460	 * the first valid update and the stratum is set at the prefer
461	 * peer, unless overriden by a fudge command.
462	 */
463	if (peer->burst > 0)
464		return;
465	peer->leap = LEAP_NOTINSYNC;
466	if (pp->codeproc == pp->coderecv) {
467		refclock_report(peer, CEVNT_TIMEOUT);
468		peer->burst = ASTAGE;
469		return;
470
471	} else if (sys_prefer == NULL) {
472		pp->codeproc = pp->coderecv;
473		peer->burst = ASTAGE;
474		return;
475
476	} else if (fabs(sys_prefer->offset) > clock_max) {
477		pp->codeproc = pp->coderecv;
478		peer->burst = ASTAGE;
479		return;
480	}
481	pp->leap = LEAP_NOWARNING;
482	if (pp->stratum >= STRATUM_UNSPEC)
483		peer->stratum = sys_prefer->stratum;
484	else
485		peer->stratum = pp->stratum;
486	if (peer->stratum == STRATUM_REFCLOCK || peer->stratum ==
487	    STRATUM_UNSPEC)
488		peer->refid = pp->refid;
489	else
490		peer->refid = addr2refid(&sys_prefer->srcadr);
491	pp->lastref = pp->lastrec;
492	refclock_receive(peer);
493	peer->burst = ASTAGE;
494}
495#else
496int refclock_atom_bs;
497int
498pps_sample(
499	   l_fp *offset		/* PPS offset */
500	   )
501{
502	return 1;
503}
504#endif /* REFCLOCK */
505