/* $Id: gen_pps.c,v 1.2 1998/02/10 10:55:50 windl Exp $
 *	Test program to create a PPS signal on RTS of serial port.
 *	Used to test NTP PPS code when no other PPS source is available.
 *	Linux 2.0 RTC driver is needed.
 *
 *	Copyright (C) 1996, Paul Gortmaker.
 *	Copyright (C) 1996, 1997, 1998 by Ulrich Windl.
 *
 *	Released under the GNU General Public License, version 2,
 *	included herein by reference.
 *
 */

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <sched.h>
#include <sys/mman.h>
#include <linux/mc146818rtc.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>

static	const char	id[] = "$Id: gen_pps.c,v 1.2 1998/02/10 10:55:50 windl Exp $";

static	int	jitter		= 0;	/* amount of jitter */
static	int	polarity	= 1;	/* polarity */
static	int	reliability	= 0;	/* reliability emulation */
static	int	stability	= 0;	/* stability emulation */
static	int	wander		= 0;	/* wander emulation */

/* create a pulse of possibly poor quality */
static	void	pulse(int port)
{
	int	       	modem_bits	= TIOCM_RTS; /* RTS output */
	int		on, off;
	struct timespec	ns_delay	= {0, 0};
	static int	w_phase		= 0;	/* phase of wander */
	int		retval;

	if ( reliability > 0 && (random() % reliability) == 0 )
		return;
	on = polarity ? TIOCMBIC : TIOCMBIS;
	off = polarity ? TIOCMBIS : TIOCMBIC;
	if ( jitter > 0 )
		ns_delay.tv_nsec += random() % (1000 * jitter);
	if ( stability > 0 && (random() % (stability + 1)) == 0 )
		ns_delay.tv_nsec += random() % (1000 * stability);
	if ( wander != 0 )
	{
		w_phase = (w_phase + 1000 * wander) % 1000000000;
		ns_delay.tv_nsec += w_phase;
	}
	nanosleep(&ns_delay, &ns_delay);
	/* create a short pulse */
	retval = ioctl(port, on, &modem_bits);
	ns_delay.tv_sec = 0, ns_delay.tv_nsec = 500;
	/* hopefully 500ns are long enough */
	nanosleep(&ns_delay, &ns_delay);
	retval |= ioctl(port, off, &modem_bits);
	if ( retval == -1 )
		perror("ioctl(TIOCMBI[SC])");
}

/* parse options and generate pulses */
int main(int argc, char *argv[])
{

	int			fd, retval;
	struct sched_param	sched;
	unsigned long		data;
	int			div	= 2;
	int			out_p;
	int			ch;

	while ( (ch = getopt(argc, argv, "j:p:r:s:w:")) != -1 )
	{
		switch ( ch )
		{
		case 'j':
			jitter = atoi(optarg); break;
		case 'p':
			polarity = atoi(optarg); break;
		case 'r':
			reliability = atoi(optarg); break;
		case 's':
			stability = atoi(optarg); break;
		case 'w':
			wander = atoi(optarg); break;
		case '?':
			fprintf (stderr, "Unknown option `-%c'.\n", optopt);
			exit(1);
		}
	}
	if ( optind >= argc )
	{
		fprintf(stderr, "Output port must be specified!\n");
		exit(1);
	}
	if ( (out_p = open(argv[optind], O_RDWR)) < 0 )
	{
		perror("open()");
		fprintf(stderr, "failed to open %s\n", argv[optind]);
		exit(1);
	}

	fd = open("/dev/rtc", O_RDONLY);
	if ( fd == -1 )
	{
		perror("open(/dev/rtc)");
		exit(1);
	}

	fprintf(stderr, "RTC PPS generator for Linux 2.0\n");
	fprintf(stderr, "jitter = %d\n",  jitter);
	fprintf(stderr, "reliability = %d\n",  reliability);
	fprintf(stderr, "stability = %d\n",  stability);
	fprintf(stderr, "wander = %d\n",  wander);

	/* Turn on periodic interrupts (2 Hz) */
	retval = ioctl(fd, RTC_IRQP_SET, 2);
	if ( retval == -1 )
	{
		perror("ioctl(RTC_IRQP_SET)");
		exit(1);
	}

	retval = ioctl(fd, RTC_PIE_ON, 0);
	if ( retval == -1 )
	{
		perror("ioctl(RTC_PIE_ON)");
		exit(1);
	}

	read(fd, &data, sizeof(long));	/* wait for RTC interrupt */

	/* become a low priority real-time process */
	sched.sched_priority = sched_get_priority_min(SCHED_FIFO);
	if ( sched_setscheduler(0, SCHED_FIFO, &sched) == -1 )
	{
		perror("sched_setscheduler()");
		exit(1);
	}
	if ( mlockall(MCL_FUTURE|MCL_CURRENT) == -1 )
	{
		perror("mlockall()");
		exit(1);
	}
	for ( ;; )
	{
		if ( read(fd, &data, sizeof(long)) == sizeof(long) )
		{
			if ( !--div )
			{
				pulse(out_p);
				div = 2;
			}
			if ( (data >> 16) != 0 )
				printf("Missed interrupt (%ld)\n", data >> 16);
		}
	}

	/* Turn off update interrupts */
	retval = ioctl(fd, RTC_PIE_OFF, 0);
	if ( retval == -1 )
	{
		perror("ioctl(RTC_PIE_OFF)");
		exit(errno);
	}
   
	close(fd);
} /* end main */
