PageRenderTime 50ms CodeModel.GetById 31ms app.highlight 15ms RepoModel.GetById 1ms app.codeStats 0ms

/contrib/ntp/ntpd/refclock_atom.c

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