/*
 * Copyright 2000, International Business Machines Corporation and others.
 * All Rights Reserved.
 * 
 * This software has been released under the terms of the IBM Public
 * License.  For details, see the LICENSE file in the top-level source
 * directory or online at http://www.openafs.org/dl/license10.html
 */

#include <afsconfig.h>
#include <afs/param.h>

RCSID("$Header: /cvs/openafs/src/ntp/read_psti.c,v 1.5 2001/08/08 00:03:53 shadow Exp $");

#if	defined(REFCLOCK) && defined(PSTI)
#define	ERR_RATE	60	/* Repeat errors once an hour */

/*
 * read_psti.c
 *     January 1988 -- orignal
 *     January 1989 -- QU version
 *
 *   This module facilitates reading a Precision Time Standard, Inc.
 *   WWV radio clock. We assume that clock is configured for 9600 baud,
 *   no parity. Output is accepted in either 24 or 12 hour format.
 *   Time is requested and processed in GMT.
 *
 *   This version is designed to make use of the QU command due to
 *   additional information it provides (like date and flags).
 *   Select is used to prevent hanging in a read when there are
 *   no characters to read.  The line is run in cooked mode to
 *   reduce overhead.
 *
 *   This requires a PSTI ROM revision later 4.01.000 or later.
 *
 *   Routines defined:
 *	init_clock_psti(): Open the tty associated with the clock and
 *			   set its tty mode bits. Returns fd on success
 *			   and -1 on failure.
 *	read_clock_psti(): Reads the clock and returns either 0 on success
 *			   or non-zero on error.  On success, pointers are
 *			   provided to the reference and local time.
 */
#include <stdio.h>
#include <syslog.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#if defined(AFS_SUN_ENV)
#include <termio.h>
#endif

#ifdef	DEBUG
extern int debug;
#endif /* DEBUG */

static int nerrors = 0;
static char clockdata[32];
#define	MIN_READ 13		/* for Rev 4.01.001 */

static double reltime();

#ifdef STANDALONE

#ifndef CLOCKDEV
#define CLOCKDEV "/dev/radioclock"
#endif

#define DEBUG	1
int debug = 1;

#include "AFS_component_version_number.c"

main(argc, argv)
int argc;
char **argv;
{
	struct timeval *tvp, *otvp;

	debug = argc;
	if (openclock(CLOCKDEV))
		do {
			(void)readclock(&tvp, &otvp);
			sleep(1);
		} while (debug>1);
	exit(0);
}
#endif /* STANDALONE */


init_clock_psti(timesource)
char *timesource;
{
	int cfd;
#ifdef TCSETA
	struct termio tty;
#else
	struct sgttyb tty;
#endif

	if ((cfd = open(timesource, 2)) < 0) {
#ifdef DEBUG
		if (debug) perror(timesource); else
#endif /* DEBUG */
		syslog(LOG_ERR, "can't open %s: %m", timesource);
		return(-1);
	}

	if (ioctl(cfd, TIOCEXCL, 0) < 0) {
#ifdef DEBUG
		if (debug) perror("TIOCEXCL on radioclock failed"); else
#endif /* DEBUG */
		syslog(LOG_ERR, "TIOCEXCL on %s failed: %m", timesource);
		return(-1);
	}

#ifdef TCSETA
	if (ioctl(cfd, TCGETA, &tty) < 0) {
#ifdef DEBUG
		if (debug) perror("ioctl on radioclock failed"); else
#endif /* DEBUG */
		syslog(LOG_ERR, "ioctl on %s failed: %m", timesource);
		return(-1);
	}
	tty.c_cflag = (B9600<<16)|B9600|CS8|CLOCAL|CREAD;
	tty.c_iflag = ICRNL;
	tty.c_oflag = 0;
	tty.c_lflag = 0;
	memset((char *)tty.c_cc, 0, sizeof tty.c_cc);
	tty.c_cc[VMIN] = MIN_READ;
	tty.c_cc[VTIME] = 0;
	if (ioctl(cfd, TCSETA, &tty) < 0) {
#else /* TCSETA	 Use older Berkeley style IOCTL's */
	memset((char *)&tty, 0, sizeof tty);
	tty.sg_ispeed = tty.sg_ospeed = B9600;
	tty.sg_flags = ANYP|CRMOD;
	tty.sg_erase = tty.sg_kill = '\0';
	if (ioctl(cfd, TIOCSETP, &tty) < 0) {
#endif /* TCSETA */
#ifdef DEBUG
		if (debug) perror("ioctl on radioclock failed"); else
#endif /* DEBUG */
		syslog(LOG_ERR, "ioctl on %s failed: %m", timesource);
		return(-1);
	}
	if (write(cfd, "xxxxxxsn\r", 9) != 9) {
#ifdef DEBUG
		if (debug) perror("init write to radioclock failed"); else
#endif /* DEBUG */
		syslog(LOG_ERR, "init write to %s failed: %m", timesource);
		return(-1);
	}
	return(cfd);			/* Succeeded in opening the clock */
}

/*
 * read_clock_psti() -- Read the PSTI Radio Clock.
 */
read_clock_psti(cfd, tvpp, otvpp)
int cfd;
struct timeval **tvpp, **otvpp;
{
	static struct timeval radiotime;
	static struct timeval mytime;
	struct timeval timeout;
	struct tm *mtm;
	struct tm radio_tm, *rtm = &radio_tm;
	register int i;
	register int millis;
	register double diff;
	int stat1, stat2;
	fd_set readfds;
	char message[256];
#ifndef TCSETA
	register char *cp;
	int  need;
#endif /* TCSETA */

	FD_ZERO(&readfds);
	FD_SET(cfd, &readfds);
	timeout.tv_sec = 2;
	timeout.tv_usec = 0;

	(void) ioctl(cfd, TIOCFLUSH, 0);	/* scrap the I/O queues */

	/* BEGIN TIME CRITICAL CODE SECTION!!!!!! */
	/* EVERY CYCLE FROM THIS POINT OUT ADDS TO THE INACCURACY OF
	     THE READ CLOCK VALUE!!!!! */
	if (write(cfd, "\003qu0000", 7) != 7) {
#ifdef DEBUG
		if (debug) printf("radioclock write failed\n"); else
#endif /* DEBUG */
		if ((nerrors++%ERR_RATE) == 0)
			syslog(LOG_ERR, "write to radioclock failed: %m");
		return(1);
	}
	if(select(cfd+1, &readfds, 0, 0, &timeout) != 1) {
#ifdef DEBUG
		if (debug) printf("radioclock poll timed out\n"); else
#endif /* DEBUG */
		if ((nerrors++%ERR_RATE) == 0)
			syslog(LOG_ERR, "poll of radioclock failed: %m");
		return(1);
	}
	if ((i = read(cfd, clockdata, sizeof clockdata)) < MIN_READ) {
#ifdef DEBUG
		if (debug) printf("radioclock read error (%d)\n", i); else
#endif /* DEBUG */
		if ((nerrors++%ERR_RATE) == 0)
			syslog(LOG_ERR, "radioclock read error (%d!=13): %m", i);
		return(1);
	}

	(void) gettimeofday(&mytime, (struct timezone *)0);
	/* END OF TIME CRITICAL CODE SECTION!!!! */

	if (clockdata[i-1] != '\n') {
#ifdef DEBUG
		if (debug) printf("radioclock format error1 (%.12s)(0x%x)\n",
			clockdata, clockdata[12]); else
#endif /* DEBUG */
		if ((nerrors++%ERR_RATE) == 0)
			syslog(LOG_ERR, "radioclock format error1 (%.12s)(0x%x)",
				clockdata, clockdata[12]);
		return(1);
	}
	for (i = 0; i < 12; i++) {
		if (clockdata[i] < '0' || clockdata[i] > 'o') {
#ifdef DEBUG
			if (debug) printf("radioclock format error2\n"); else
#endif /* DEBUG */
			if ((nerrors++%ERR_RATE) == 0)
				syslog(LOG_ERR, "radioclock format error2\n");
			return(1);
		}
	}
	stat1 = clockdata[0]-'0';
	stat2 = clockdata[1]-'0';
	millis = ((clockdata[2]-'0')*64)+(clockdata[3]-'0');
	rtm->tm_sec = (clockdata[4]-'0');
	rtm->tm_min = (clockdata[5]-'0');
	rtm->tm_hour = (clockdata[6]-'0');
	rtm->tm_yday = ((clockdata[7]-'0')*64)+(clockdata[8]-'0')-1;
	rtm->tm_year = 86+(clockdata[9]-'0');
	/* byte 10 and 11 reserved */

	/*
	 * Correct "hours" based on whether or not AM/PM mode is enabled.
	 * If clock is in 24 hour (military) mode then no correction is
	 * needed.
	 */
	if(stat2&0x10) {	    /* Map AM/PM time to Military */
		if (stat2&0x8) {
			if (rtm->tm_hour != 12) rtm->tm_hour += 12;
		} else {
			if (rtm->tm_hour == 12) rtm->tm_hour = 0;
		}
	}

	if (stat1 != 0x4 && (nerrors++%ERR_RATE)==0) {
#ifdef DEBUG
		if (debug) printf("radioclock fault #%d 0x%x:%s%s%s%s%s%s\n",
			nerrors, stat1,
			stat1&0x20?" Out of Spec,":"",
			stat1&0x10?" Hardware Fault,":"",
			stat1&0x8?" Signal Fault,":"",
			stat1&0x4?" Time Avail,":"",
			stat1&0x2?" Year Mismatch,":"",
			stat1&0x1?" Clock Reset,":"");
		else {
#endif /* DEBUG */
			sprintf(message, "radioclock fault #%d 0x%x:%s%s%s%s%s%s\n",
				nerrors, stat1,
				stat1&0x20?" Out of Spec,":"",
				stat1&0x10?" Hardware Fault,":"",
				stat1&0x8?" Signal Fault,":"",
				stat1&0x4?" Time Avail,":"",
				stat1&0x2?" Year Mismatch,":"",
				stat1&0x1?" Clock Reset,":"");
			syslog(LOG_ERR, message);
		}
	}
	if (stat1&0x38) /* Out of Spec, Hardware Fault, Signal Fault */
		return(1);
	if ((millis > 999 || rtm->tm_sec > 60 || rtm->tm_min > 60 ||
	     rtm->tm_hour > 23 || rtm->tm_yday > 365) && (nerrors++%ERR_RATE)==0) {
#ifdef DEBUG
		if (debug) printf("radioclock bogon #%d: %dd %dh %dm %ds %dms\n",
			nerrors, rtm->tm_yday, rtm->tm_hour,
			rtm->tm_min, rtm->tm_sec, millis);
		else
#endif /* DEBUG */
		sprintf(message, "radioclock bogon #%d: %dd %dh %dm %ds %dms\n",
			nerrors, rtm->tm_yday, rtm->tm_hour,
			rtm->tm_min, rtm->tm_sec, millis);
		syslog(LOG_ERR, message);
		return(1);
	}

	mtm = gmtime(&mytime.tv_sec);
	diff =  reltime(rtm, millis*1000) - reltime(mtm, mytime.tv_usec);
#ifdef DEBUG
	if (debug > 1) {
            char buf[256];
	    strftime (buf, 256, "Clock time:  %Y day %j %T", rtm);
	    strcat (buf, ".%03d diff %.3f\n", millis, diff);
            printf (buf);
	}
#endif /* DEBUG */
	
	if (diff > (90*24*60*60.0) && (nerrors++%ERR_RATE)==0) {
	    char buf1[16], buf2[16];
	    strftime (buf1, 16, "%Y/%j", mtm);
	    strftime (buf2, 16, "%Y/%j", rtm);
#ifdef DEBUG
		if (debug) {
		    printf("offset excessive (system %s, clock %s)\n",
			buf1, buf2);
		}
		else
#endif /* DEBUG */
		syslog(LOG_ERR, "offset excessive (system %s, clock %s)\n",
			buf1, buf2);
		return(1);
	}

	diff += (double)mytime.tv_sec + ((double)mytime.tv_usec/1000000.0);
	radiotime.tv_sec = diff;
	radiotime.tv_usec = (diff - (double)radiotime.tv_sec) * 1000000;
#ifdef DEBUG
	if (debug > 1) {
		char buf[256];
		strftime (buf, 256, "%Y day %j %T", mtm);
		printf("System time: %s.%03d\n", buf, mytime.tv_usec/1000);
		printf("stat1 0%o, stat2 0%o: ", stat1, stat2);
		if (stat1 || stat2)
			printf("%s%s%s%s%s%s%s%s%s%s%s%s",
				stat1&0x20?" Out of Spec,":"",
				stat1&0x10?" Hardware Fault,":"",
				stat1&0x8?" Signal Fault,":"",
				stat1&0x4?" Time Avail,":"",
				stat1&0x2?" Year Mismatch,":"",
				stat1&0x1?" Clock Reset,":"",
				stat2&0x20?" DST on,":"",
				stat2&0x10?" 12hr mode,":"",
				stat2&0x8?" PM,":"",
				stat2&0x4?" Spare?,":"",
				stat2&0x2?" DST??? +1,":"",
				stat2&0x1?" DST??? -1,":"");
		printf("\n");
	}
#endif /* DEBUG */
	/* If necessary, acknowledge "Clock Reset" flag bit */
	if (stat1 & 0x1) {
		if (write(cfd, "si0", 3) != 3) {
#ifdef DEBUG
			if (debug) printf("radioclock reset write failed\n"); else
#endif /* DEBUG */
			syslog(LOG_ERR, "reset write to radioclock failed: %m");
			return(1);
		}
	}
	if (nerrors && stat1==0x4) {
		syslog(LOG_ERR, "radioclock OK (after %d errors)", nerrors);
		nerrors = 0;
	}
	*tvpp = &radiotime;
	*otvpp = &mytime;
	return(0);
}

static double
reltime(tm, usec)
register struct tm *tm;
register int usec;
{
	return(tm->tm_year*(366.0*24.0*60.0*60.0) +
	       tm->tm_yday*(24.0*60.0*60.0) +
	       tm->tm_hour*(60.0*60.0) +
	       tm->tm_min*(60.0) +
	       tm->tm_sec +
	       usec/1000000.0);
}
#endif
