/* Sample program to test the PPS-related kernel routines
 * (enable PPS detection, watch for errors, and monitor data)
 *
 * Errors and exceptional messages go to stderr, while monitoring data
 * is sent to stdout.
 *
 * A detailed description of the clock model can be found in the technical
 * memorandum "A Kernel Model for Precision Timekeeping" by Dave Mills,
 * revised 31 January 1996. That document updates RFC1589.
 *
 * This program has been updated to use the new PPS API (draft-05, 1999-08-17)
 *
 * Copyright (c) 1996 - 1999 by Ulrich Windl
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: enable_pps.c,v 1.34 1999/10/10 14:07:07 windl Exp $
 */
#include	<unistd.h>
#include	<stdlib.h>
#include	<stdio.h>
#include	<ctype.h>
#include	<string.h>
#include	<errno.h>
#include	<sys/time.h>
#include	<sys/utsname.h>

#include	<linux/version.h>
#define	NANO	/* prefer the Nanokernel if available */
#include	<sys/timex.h>

#include	<sys/ppsclock.h>

#include	<sys/ioctl.h>
#include	<sys/timepps.h>
#if defined(__GNU_LIBRARY__) && __GNU_LIBRARY__ >= 6	/* libc6 or glibc2 */
/*# warning	"Compatibility with this library has not been tested a lot!" */
#else
				/* conflict: ioctl-types.h vs. some
				 * linux file
				 */
# include	<termios.h>	/* conflict: termbits.h vs. asm/termbits.h
				 * struct termios differs in size (NCCS)
				 */
#endif

#ifdef	STA_NANO		/* check if compiling with Nanokernel */
#define n_time	timespec
#define	tv_frac	tv_nsec		/* using struct timespec */
#else
#define n_time	timeval
#define	tv_frac	tv_usec		/* using struct timeval */
#endif

#ifndef	SHIFT_USEC
#define SHIFT_USEC	16	/* compatibility define */
#define	PPS_SHIFT	4	/* compatibility define */
#define	PPS_SHIFTMAX	8	/* compatibility define */
#endif

#ifndef	ADJ_ADJTIME
#define ADJ_ADJTIME	ADJ_OFFSET_SINGLESHOT	/* compatibility define */
#endif

#define OFFSET_TOLERANCE	333333333	/* ns */
#define STABIL_TOLERANCE	(10 << SHIFT_USEC)
#define	PPS_INSANE		(STA_PPSERROR|STA_PPSWANDER|STA_PPSJITTER)
#define ALL_ERRORS		(PPS_INSANE|STA_CLOCKERR)

#define	ALL_BITS_ON(v, b)		(((v) & (b)) == (b))
#define	ALL_BITS_OFF(v, b)		(((v) & (b)) == 0)
#define	BITS_OFF(v, b)			((v) & ~(b))
#define	BITS_ON(v, b)			((v) | (b))

static	const char	id[] = "$Id: enable_pps.c,v 1.34 1999/10/10 14:07:07 windl Exp $";

#define	BIT(n)	(1UL << (n))
#define OPT_MONITOR		BIT(0)	/* perform monitor loop */
#define OPT_RESET		BIT(1)	/* reset clock state */
#define OPT_NO_OFFSET		BIT(2)	/* don't adjust offset */
#define OPT_NO_FREQ		BIT(3)	/* don't adjust frequency */
#define OPT_NO_STATUS		BIT(4)	/* don't adjust status */
#define OPT_NO_TIMECONST	BIT(5)	/* don't adjust time constant */
#define OPT_JITTER_LOOP		BIT(6)	/* monitor jitter only */
#define OPT_MOD_MICRO		BIT(7)	/* enforce MOD_MICRO */
#define OPT_MOD_NANO		BIT(8)	/* enforce MOD_NANO */

static	unsigned long	options = OPT_MONITOR | OPT_RESET;

static	char		trigger	= 'A';	/* off | assert | clear | both */

static	const	int	fd	= 0;	/* file descriptor of port (stdin) */
static	pps_handle_t	ppshandle;	/* PPS handle (stdin) */

struct clock_info {			/* information about clock and conditions */
	struct timex	tx;		/* kernel status information */
	pps_info_t	info;		/* pulse information */
	struct timespec	delta;		/* time difference between pulses */
	int		have_assert;	/* assert event detected */
	int		have_clear;	/* clear event detected */
	long		sec, nsec;	/* last time stamp */
	long		dsec, dnsec;	/* difference since last time stamp */
	long		seq, dseq;	/* sequence number and difference */
	long		nano_offset;	/* offset around zero (ns) */
	long		jitter;		/* jitter between pulses (ns) */
	int		state;		/* time state */
};

typedef struct {		/* general monitor */
	int	(*trigger)(const struct clock_info *cip);
	void	(*action)(struct clock_info *cip);
} monitor_t;

static	void	print_header(void)
{
	printf("%4.4s %9.9s %8.8s %8.8s %9.9s %1.1s "
	       "%7.7s %4.4s %4.4s %4.4s %4.4s\n",
	       "Status", "Offset [us]",
	       "Frequency",
	       "PPS Frequency",
	       "PPS Jitter", "Shift",
	       "stabil",
	       "jitcnt", "calcnt",
	       "errcnt", "stbcnt");
}

/* (partially) print current time state */
static	void	print_stats(const struct clock_info *cip)
{
	int nano = 0;
	double offset, jitter;

#ifdef STA_NANO
	nano = (cip->tx.status & STA_NANO) != 0;
#endif
	offset = nano ? cip->tx.offset / 1000.0 : cip->tx.offset;
	jitter = nano ? cip->tx.jitter / 1000.0 : cip->tx.jitter;
	printf("%04x %9.3f %8.3f %8.3f %9.3f %1d %7.3f %4ld %4ld %4ld %4ld\n",
	       cip->tx.status, offset,
	       (double) cip->tx.freq / (1L << SHIFT_USEC),
	       (double) cip->tx.ppsfreq / (1L << SHIFT_USEC),
	       jitter, cip->tx.shift,
	       (double) cip->tx.stabil / (1L << SHIFT_USEC),
	       cip->tx.jitcnt, cip->tx.calcnt,
	       cip->tx.errcnt, cip->tx.stbcnt);
	fflush(stdout);
}

enum pps_state { S_NO_PPS, S_PPS_TIME, S_PPS_FREQ, S_PPS_FLL };

static	char const	*status_str[] = {	/* from <timex.h> */
	"PLL",		/* bit 0 */
	"PPSFREQ",	/* bit 1 */
	"PPSTIME",	/* bit 2 */
	"FLL",		/* bit 3 */

	"INS",		/* bit 4 */
	"DEL",		/* bit 5 */
	"UNSYNC",	/* bit 6 */
	"FREQHOLD",	/* bit 7 */

	"PPSSIGNAL",	/* bit 8 */
	"PPSJITTER",	/* bit 9 */
	"PPSWANDER",	/* bit 10 */
	"PPSERROR",	/* bit 11 */

	"CLOCKERR",	/* bit 12 */
	"NANO",		/* bit 13 */
	"MODE",		/* bit 14 */
	"CLK",		/* bit 15 */
};

/* write strings for set status bits on ``fp'' */
static	void	write_status_bits(FILE *fp, int bits)
{
	int	b;
	char	*sep = "";
	for ( b = 0; b < 16; ++b )
	{
		if ( (bits & 1) != 0 )
			fprintf(fp, "%s%s", sep, status_str[b]), sep = "+";
		bits >>= 1;
	}
}

/* print PPS status */
static	int	pps_status(const char *tag, const int show_event,
			   const struct clock_info *cip)
{
	static	struct clock_info	last;
	int	result = -1;
	char const	*state_str = NULL;
	char const	*sep	= tag;

	if ( show_event && (cip->have_assert || cip->have_clear) )
	{
		long	sec = cip->sec;

		if ( cip->nsec < -500000000 )
			--sec;
		else if ( cip->nsec > 500000000 )
			++sec;
		fprintf(stderr, "%spulse(%c:%c) %lu at 0.%09ld",
			sep, cip->have_assert + '0', cip->have_clear + '0',
			cip->seq, cip->nsec), sep = ", ";
		if ( sec != cip->dseq )
			fprintf(stderr, "%s%ld events in %ld seconds",
				sep, cip->dseq, sec), sep = ", ";
		result = 0;
	}
	switch ( cip->state )
	{
	case TIME_INS: state_str = "TIME_INS"; break;
	case TIME_DEL: state_str = "TIME_DEL"; break;
	case TIME_OOP: state_str = "TIME_OOP"; break;
	case TIME_WAIT: state_str = "TIME_WAIT"; break;
	case TIME_ERROR: state_str = "TIME_ERROR"; break;
	}
	if ( state_str != NULL )
		fprintf(stderr, "%sstate=%s", sep, state_str), sep = ", ";
	if ( ((cip->tx.status ^ last.tx.status) & ~ALL_ERRORS) != 0 )
	{
		fprintf(stderr, "%snew status=", sep), sep = ", ";
		write_status_bits(stderr, cip->tx.status);
		last.tx = cip->tx;
	}
	else if ( (cip->tx.status & ALL_ERRORS) != 0 )
	{
		fprintf(stderr, "%serrors=", sep), sep = ", ";
		write_status_bits(stderr, cip->tx.status & ALL_ERRORS);
	}
	if ( sep != tag )
		fprintf(stderr, "\n");
	return(result);
}

/* update *cip */
static	int	update_clock_info(struct clock_info *cip)
{
	static struct clock_info	last;
	static struct timespec	sleeper = {0, 0};
	int	result = -1;

	cip->tx.modes = 0;
	cip->state = adjtimex(&cip->tx);
	if ( time_pps_fetch(ppshandle, PPS_TSFMT_TSPEC,
			    &cip->info, &sleeper) == -1 )
	{
		perror("time_pps_fetch()");
		memset(&cip->info, 0, sizeof(cip->info));
	}
	
	cip->have_assert = cip->info.assert_sequence != last.info.assert_sequence;
	cip->have_clear = cip->info.clear_sequence != last.info.clear_sequence;
	if ( cip->have_assert )
	{
		cip->sec = cip->info.assert_timestamp.tv_sec;
		cip->dsec = cip->sec - last.sec;
		cip->nsec = cip->info.assert_timestamp.tv_nsec;
		cip->dnsec = cip->nsec - last.nsec;
		cip->seq = cip->info.assert_sequence;
		cip->dseq = cip->seq - last.seq;
	}
	if ( cip->have_clear )
	{
		cip->sec = cip->info.clear_timestamp.tv_sec;
		cip->dsec = cip->sec - last.sec;
		cip->nsec = cip->info.clear_timestamp.tv_nsec;
		cip->dnsec = cip->nsec - last.nsec;
		cip->seq = cip->info.clear_sequence;
		cip->dseq = cip->seq - last.seq;
	}
	if ( cip->dseq )
	{
		cip->nano_offset = cip->nsec;
		if ( cip->nano_offset > 500000000 )
			cip->nano_offset -= 1000000000;
		cip->delta.tv_sec = cip->dsec;
		cip->delta.tv_nsec = cip->dnsec;
		if ( cip->dnsec < -500000000 )
		{
			--cip->delta.tv_sec;
			cip->delta.tv_nsec += 1000000000;
		}
		else if ( cip->dnsec > 500000000 )
		{
			++cip->delta.tv_sec;
			cip->delta.tv_nsec -= 1000000000;
		}
		cip->jitter = cip->delta.tv_sec - last.delta.tv_sec;
		cip->jitter *= 1000000000;
		cip->jitter += cip->delta.tv_nsec - last.delta.tv_nsec;
		last = *cip;
		result = 0;
	}
	return(result);
}

/* do adjtimex(), update clock_info, and check for errors */
static	int	verbose_adjtimex(const char *tag, const int show_event,
				 struct clock_info *cip)
{
	cip->state = adjtimex(&cip->tx);
	if ( cip->state < 0 )
	{
		errno = -cip->state;
		perror("adjtimex()");
	}
	return(cip->state);
}

/* update kernel status bits with status check */
static	int	update_status(struct clock_info *cip, int clear_bits,
			      int set_bits)
{
	int	old_status = cip->tx.status;
	int	change;
	char	*sep = " (";

	if ( options & OPT_NO_STATUS )
		return(0);
	cip->tx.status |= set_bits;
	cip->tx.status &= ~clear_bits;
	cip->tx.modes = MOD_STATUS;
	if ( cip->tx.status == old_status )
		return(0);
	fprintf(stderr, "update status: %04x -> %04x",
		old_status, cip->tx.status);
	if ( (change = ~old_status & (old_status ^ cip->tx.status)) != 0 )
	{
		fprintf(stderr, "%sset=[", sep), sep = ", ";
		write_status_bits(stderr, change);
		fprintf(stderr, "]");
	}
	if ( (change = old_status & (old_status ^ cip->tx.status)) != 0 )
	{
		fprintf(stderr, "%sclear=[", sep), sep = ", ";
		write_status_bits(stderr, change);
		fprintf(stderr, "]");
	}
	fprintf(stderr, "%s\n", sep[0] == ',' ? ")" : "");
	return( verbose_adjtimex("update_status(): ", 0, cip) );
}

/* reset kernel PLL */
static	void	restart(struct clock_info *cip)
{
	if ( ALL_BITS_OFF(options, OPT_RESET) )
		return;
	cip->tx.modes = MOD_ESTERROR | MOD_MAXERROR;
	/* indicate that we are trying to synchronize */
	cip->tx.esterror = cip->tx.maxerror = MAXPHASE / 1000;
	cip->tx.modes |= MOD_FREQUENCY | ADJ_TICKADJ;
	cip->tx.freq = 0;
	/* boost adjtime() times 100 (HZ*500 == 0.05s per second) */
	cip->tx.tickadj = 500;			
	cip->tx.modes |= MOD_TIMECONST;
	cip->tx.constant = 1;
#ifdef MOD_MICRO
	if ( ALL_BITS_ON(options, OPT_MOD_MICRO) )
		cip->tx.modes |= MOD_MICRO;
#endif
#ifdef MOD_NANO
	if ( ALL_BITS_ON(options, OPT_MOD_NANO) )
		cip->tx.modes |= MOD_NANO;
#endif
	verbose_adjtimex("restart(): ", 0, cip);
	update_status(cip, STA_FLL | STA_PPSFREQ | STA_PPSTIME,
		      STA_FREQHOLD | STA_PLL | STA_UNSYNC);
}

/* use current time constant from PPS discipline if it is working properly */
static	void	update_constant(struct clock_info *cip)
{
	if ( options & OPT_NO_TIMECONST )
		return;
	if ( (cip->tx.status & STA_PPSSIGNAL) != 0 &&
	     (cip->tx.status & PPS_INSANE) == 0 )
		cip->tx.constant = cip->tx.shift;	/* copy PPS PLL constant */
	else
		cip->tx.constant = 1;
	cip->tx.modes = MOD_TIMECONST;
	verbose_adjtimex("update_constant(): ", 0, cip);
}

/* do offset adjustments. expects offset within bounds */
static	void	pll_adj_offset(struct clock_info *cip)
{
	static	time_t last_adj_time;
	time_t	t = time(NULL);
	long	offset = 0;
	long	tdelta, tconst;

	if ( cip->dseq == 0 )
		return;
	if ( cip->have_assert )
		offset = cip->info.assert_timestamp.tv_nsec;
	if ( cip->have_clear )
		offset = cip->info.clear_timestamp.tv_nsec;
	if ( offset > 500000000 )
		offset -= 1000000000;	/* change direction */
	offset = -offset;		/* correct direction */
	cip->tx.modes = MOD_MAXERROR | MOD_ESTERROR;
	cip->tx.offset = offset;
	offset /= 1000;			/* convert ns to us */
	cip->tx.esterror = cip->tx.maxerror = offset >= 0 ? : -offset;
	if ( cip->tx.offset > MAXPHASE / 2 || cip->tx.offset < -MAXPHASE / 2 )
	{
		cip->tx.modes = ADJ_ADJTIME;
		cip->tx.offset = offset;	/* need us here */
	}
	else
	{
		cip->tx.modes |= MOD_OFFSET;
#ifdef STA_NANO
		if ( (cip->tx.status & STA_NANO) == 0 )
			cip->tx.offset /= 1000;
#endif
	}
	tdelta = t - last_adj_time;
	tconst = ALL_BITS_ON(cip->tx.status, STA_PPSTIME|STA_PPSSIGNAL) ?
		cip->tx.shift : cip->tx.constant;
	if ( ((cip->tx.status & (STA_FLL|STA_PPSFREQ)) != 0 &&
	      (tdelta < MINSEC || tdelta < (1L << tconst))) ||
	     ((cip->tx.status & STA_PLL) != 0 && tdelta < (1L << tconst) / 2) )
		return;			/* don't adjust too frequently */
	else
		last_adj_time = t;
	printf("%s: modes=%04x, delta=%ld, const=%ld, in=%ld, out=%ld\n",
	       "pll_adj_offset", cip->tx.modes, tdelta, tconst, offset,
	       cip->tx.offset);
	verbose_adjtimex("pll_adj_offset(): ", 0, cip);
}

/* print status message */
static	void	status(char const *str)
{
	fprintf(stderr, "[%s]\n", str);
}

/* check and adjust offset */
static	int	trg_offset(const struct clock_info *cip)
{
	if ( options & OPT_NO_OFFSET )
		return 0;
	return 1;
}

static	void	act_offset(struct clock_info *cip)
{
	long offset = cip->nano_offset;

	if ( offset < -OFFSET_TOLERANCE || offset > OFFSET_TOLERANCE )
		update_status(cip, 0, STA_FREQHOLD | STA_PLL);
	else if ( offset > -OFFSET_TOLERANCE / 2 &&
		  offset < OFFSET_TOLERANCE / 2 )
		update_status(cip, STA_FREQHOLD, STA_PLL);
	if ( (cip->tx.status & STA_PPSSIGNAL) != 0 && cip->dseq != 0 )
	{
		pll_adj_offset(cip);
	}
	update_constant(cip);
}

/* check and adjust frequency */
static	int	trg_freq(const struct clock_info *cip)
{
	if ( options & OPT_NO_FREQ )
		return 0;
	return 1;
}

static	void	act_freq(struct clock_info *cip)
{
	long offset = cip->dnsec;

	if ( offset > 500000000 )
		offset -= 1000000000;	/* round to nearest second */
	if ( offset < -OFFSET_TOLERANCE || offset > OFFSET_TOLERANCE )
		update_status(cip, STA_FLL, STA_FREQHOLD | STA_PLL);
	else if ( offset > -OFFSET_TOLERANCE / 2 &&
		  offset < OFFSET_TOLERANCE / 2 )
		update_status(cip, STA_FREQHOLD, STA_FLL | STA_PLL);
}

/* check for PPS signal suitable for offset adjustment */
static	int	trg_ppstime(const struct clock_info *cip)
{
	if ( options & OPT_NO_OFFSET )
		return 0;
	return 1;
}

static	void	act_ppstime(struct clock_info *cip)
{
	long offset = cip->nano_offset;

	if ( (cip->tx.status & STA_PPSSIGNAL) != 0
	     && (cip->tx.status & PPS_INSANE) == 0
	     && (offset > -OFFSET_TOLERANCE && offset < OFFSET_TOLERANCE) )
		update_status(cip, 0, STA_PPSTIME);
	else
		update_status(cip, STA_PPSTIME, 0);
}

/* check for PPS signal suitable for frequency adjustment */
static	int	trg_ppsfreq(const struct clock_info *cip)
{
	if ( options & OPT_NO_FREQ )
		return 0;
	return 1;
}

static	void	act_ppsfreq(struct clock_info *cip)
{
	if ( (cip->tx.status & STA_PPSSIGNAL) != 0
	     && (cip->tx.status & PPS_INSANE) == 0
	     && cip->tx.stabil <= STABIL_TOLERANCE )
		update_status(cip, 0, STA_PPSFREQ|STA_PLL);
	else
		update_status(cip, STA_PPSFREQ, STA_PLL);
}

/* print data */
static	int	trg_print(const struct clock_info *cip)
{
	static struct clock_info last;

	if ( memcmp(&last, cip, sizeof(*cip)) != 0 )
	{
		last = *cip;
		return 1;
	}
	return 0;
}

static	void	act_print(struct clock_info *cip)
{
	static struct clock_info	last;

	if ( memcmp(&last, cip, sizeof(last)) != 0 )
	{
		pps_status("monitor: ", 1, cip);
		print_stats(cip);
		last = *cip;
	}
}

/* wait for event */
static	int	trg_wait(const struct clock_info *cip)
{
	return 1;
}

static	void	act_wait(struct clock_info *cip)
{
	static struct timespec	sleeper = {5, 0};
	pps_info_t		buf;

	if ( time_pps_fetch(ppshandle, PPS_TSFMT_TSPEC, &buf, &sleeper) != 0 )
	{
		perror("time_pps_fetch()");
		memset(&buf, 0, sizeof(buf));
	}
}

static monitor_t	monitors[] = {
	{trg_wait, act_wait},
	{trg_offset, act_offset},
	{trg_freq, act_freq},
	{trg_ppstime, act_ppstime},
	{trg_ppsfreq, act_ppsfreq},
	{trg_print, act_print},
};

/* execute calibration loop */
static	void	calibrate(struct clock_info *cip)
{
	int	m;

	while ( 1 )
	{
		update_clock_info(cip);
		for ( m = 0; m < sizeof(monitors) / sizeof(monitor_t); ++m )
		{
			if ( (*monitors[m].trigger)(cip) )
				(*monitors[m].action)(cip);
		}
	}
}

/* monitor PPS jitter */
static	void	monitor_jitter(struct clock_info *cip)
{
	struct clock_info	last;
	pps_info_t		buf;

	cip->tx.modes = 0;
	while ( 1 )
	{
		if ( time_pps_fetch(ppshandle, PPS_TSFMT_TSPEC,
				    &buf, NULL) != 0 )
		{
			perror("time_pps_fetch()");
			continue;
		}
		update_clock_info(cip);
		if ( cip->seq == last.seq )
			continue;
		pps_status("monitor_jitter(): ", 0, cip);
		printf("event(%4lu:%4lu) jitter %9ld ns, offset %9ld ns",
		       cip->info.assert_sequence, cip->info.clear_sequence,
		       cip->jitter, cip->nano_offset);
		if ( cip->jitter > 1000000 || cip->jitter < -1000000 )
		{
			printf("\t%ld:%ld %ld:%ld %ld:%ld",
			       last.sec, last.nsec, cip->sec, cip->nsec,
			       cip->delta.tv_sec, cip->delta.tv_nsec);
		}
		last = *cip;
		printf("\n");
		fflush(stdout);
	}
}

/* provide a usage message */
static	void	usage(void)
{
	fprintf(stderr,
		"Known options are:\n"
		"\t-C\tdon't adjust time constant in monitoring loop\n"
		"\t-EX\tEcho - set PPS API echo for `X' events (a|c)\n"
		"\t-e\texit - do not enter monitoring loop\n"
		"\t-F\tdon't adjust frequency in monitoring loop\n"
		"\t-j\tmonitor jitter only\n"
		"\t-k\tkeep kernel flags (when entering monitoring loop)\n"
#if defined(MOD_MICRO) && defined(MOD_NANO)
		"\t-M\tenforce MOD_MICRO\n"
		"\t-N\tenforce MOD_NANO\n"
#endif
		"\t-O\tdon't adjust offset in monitoring loop\n"
		"\t-oXY\tSet PPS API offset for `X' events (a|c) to `Y'ns\n"
		"\t-S\tdon't adjust status in monitoring loop\n"
		"\t-t{0|a|c|b}[h]\ttrigger for event: off|assert|clear|both\n"
		);
}

/* input has to be redirected to the desired device! */
int	main(int argc, char *argv[])
{
	struct utsname	un;
	int		stat = TIOCM_RTS;	/* RTS MODEM control line */
	struct clock_info ci;
	pps_params_t	parm;
	int		kc_edge = 0;
	int		ch;

	fprintf(stderr,
		"WARNING: This program may invalidate your system time!\n");
	printf("(This version has been compiled on Linux %s with"
#ifndef	STA_NANO
	       "out"
#endif
	       " nanokernel support\n", UTS_RELEASE);
	printf(" using "
#if !defined(__GNU_LIBRARY__) || __GNU_LIBRARY__ < 6
	       "an old C library (not glibc-2.x)"
#else
	       "glibc-%d.%d",
	       __GLIBC__, __GLIBC_MINOR__
#endif
	       );
	uname(&un);
	printf(". Now running %s %s)\n", un.sysname, un.release);
	parm.mode = PPS_TSFMT_TSPEC;	/* set default flags */
	while ( (ch = getopt(argc, argv, "CE:eFjkMNOo:St:")) != -1 )
	{
		switch ( ch )
		{
		case 'C':	/* don't adjust time constant */
			options = BITS_ON(options, OPT_NO_TIMECONST);
			break;
		case 'E':
			switch (optarg[0])
			{
			case 'a':
				parm.mode |= PPS_ECHOASSERT;
				break;
			case 'c':
				parm.mode |= PPS_ECHOCLEAR;
				break;
			default:
				fprintf(stderr, "'-%c%s' is illegal\n",
					ch, optarg);
				usage();
				exit(1);
			}
			break;
		case 'e':	/* exit - don't enter monitoring loop */
			options = BITS_OFF(options, OPT_MONITOR);
			break;
		case 'F':	/* don't adjust frequency */
			options = BITS_ON(options, OPT_NO_FREQ);
			break;
		case 'j':	/* monitor jitter only */
			options = BITS_ON(options, OPT_JITTER_LOOP);
			break;
		case 'k':	/* keep settings - don't restart when entering
				   monitoring loop */
			options = BITS_OFF(options, OPT_RESET);
			break;
		case 'M':	/* enforce MOD_MICRO */
			options = BITS_ON(options, OPT_MOD_MICRO);
			break;
		case 'N':	/* enforce MOD_NANO */
			options = BITS_ON(options, OPT_MOD_NANO);
			break;
		case 'O':	/* don't adjust offset */
			options = BITS_ON(options, OPT_NO_OFFSET);
			break;
		case 'S':	/* don't adjust time status */
			options = BITS_ON(options, OPT_NO_STATUS);
			break;
		case 'o':
			switch (optarg[0])
			{
			case 'a':
				parm.mode |= PPS_OFFSETASSERT;
				parm.assert_offset.tv_sec = 0;
				parm.assert_offset.tv_nsec = strtol(optarg + 1,
								    NULL, 10);
				break;
			case 'c':
				parm.mode |= PPS_OFFSETCLEAR;
				parm.clear_offset.tv_sec = 0;
				parm.clear_offset.tv_nsec = strtol(optarg + 1,
								   NULL, 10);
				break;
			default:
				fprintf(stderr, "'-%c%s' is illegal\n",
					ch, optarg);
				usage();
				exit(1);
			}
			break;
		case 't':	/* select desired edge of the pulse */
			trigger = optarg[0];
			switch ( trigger )
			{
			case '0':
			case 'a':
			case 'c':
			case 'b':
				break;
			default:
				fprintf(stderr, "'-%c%s' is illegal\n",
					ch, optarg);
				usage();
				exit(1);
			}
			if ( optarg[1] == 'h' )
				trigger = toupper(trigger);
			break;
		case '?':
			fprintf (stderr, "Unknown option `-%c'.\n", optopt);
			usage();
			exit(1);
		}
	}
	if ( optind > argc )
	{
		fprintf(stderr, "Extra arguments ignored!\n");
		usage();
		exit(1);
	}

	ioctl(fd, TIOCMBIC, &stat);	/* power-on DCF77 receiver */
	if ( time_pps_create(fd, &ppshandle) )
		perror("time_pps_create()");
	switch ( trigger )
	{
	case 'A':
		kc_edge |= PPS_CAPTUREASSERT;	/* fall through */
	case 'a':
		parm.mode |= PPS_CAPTUREASSERT;
		break;
	case 'C':
		kc_edge |= PPS_CAPTURECLEAR;	/* fall through */
	case 'c':
		parm.mode |= PPS_CAPTURECLEAR;
		break;
	case 'B':
		/* parm.mode |= PPS_HARDPPSONBOTH; */	/* fall through */
	case 'b':
		parm.mode |= PPS_CAPTUREBOTH;
		break;
	}

	parm.api_version = PPS_API_VERS_1;
	if ( time_pps_setparams(ppshandle, &parm) != 0 )
	{
		perror("time_pps_setparams()");
		fprintf(stderr, "handle=%d, mode=%#04x\n",
			ppshandle, parm.mode);
		/* continue, maybe just monitoring */
	}
	else
	{
		fprintf(stderr,
			"PPS API v%d set up (handle %d, mode %#04x)\n",
			parm.api_version, ppshandle, parm.mode);
		if ( kc_edge != 0 )
		{
			if ( time_pps_kcbind(ppshandle, PPS_KC_HARDPPS,
					     kc_edge, PPS_TSFMT_TSPEC) != 0 )
			{
				perror("time_pps_kcbind()");
				fprintf(stderr, "handle=%d, edge=%#04x\n",
					ppshandle, kc_edge);
				/* continue, maybe just monitoring */
			}
			else
			{
				fprintf(stderr,
					"kernel consumer set (edge %#04x)\n",
					kc_edge);
			}
		}
	}
	memset(&ci, 0, sizeof(ci));
	update_clock_info(&ci);
	print_header();
	pps_status("init: ", 1, &ci);
	print_stats(&ci);
	if ( options & OPT_RESET )
	{
		restart(&ci);
		print_stats(&ci);
	}
	if ( options & OPT_JITTER_LOOP )
		monitor_jitter(&ci);
	if ( options & OPT_MONITOR )
		calibrate(&ci);
	return(0);
}
