/*
 * refclock_bancomm.c - clock driver for the  Datum/Bancomm
 * bc635VME Time and Frequency Processor. 
 * R. Schmidt, Time Service, US Naval Obs. May 94 
 * modelled after the TPRO NTP driver. 
 * 
 * This requires the Datum HP-UX V9.01 kernel driver and the HP-UX vme2
 * driver subsystem. It has been tested on an HP9000/747i at HP-UX 9.03. 
 * There are no restrictions on release and use of the following code.  
 * The refclock type has been defined as  16. 
 * Installation of the Datum/Bancomm HPUX driver creates  the device file
 * /dev/btfp0 
 *
 * These DEFS are included in the Makefile:
 *      DEFS= -DHAVE_TERMIOS -DSYS_HPUX=9
 *      DEFS_LOCAL=  -DREFCLOCK
 *      CLOCKDEFS=   -DBANC
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#if defined(REFCLOCK) && defined(CLOCK_BANC) 
#include <stdio.h>
#include <syslog.h>
#include <ctype.h>
#include <string.h>
#include <strings.h>
#include <sys/time.h>

#include "ntpd.h"
#include "ntp_io.h"
#include "ntp_refclock.h"
#include "ntp_unixtime.h"
#include "ntp_stdlib.h"
#include "/etc/conf/machine/vme2.h"
#include "/etc/conf/h/io.h"

/*  STUFF BY RES */
struct btfp_time                /* Structure for reading 5 time words   */
                                /* in one ioctl(2) operation.           */
{
	unsigned short btfp_time[5];  /* Time words 0,1,2,3, and 4. (16bit)*/
};

/***** Simple ioctl commands *****/

#define RUNLOCK   _IO('X',19)                   /* Release Capture Lockout */
#define RCR0      _IOR('X',22,unsigned int)     /* Read control register */
#define WCR0      _IOW('X',23,unsigned int)     /* Write control register */

/***** Compound ioctl commands *****/

/* Read all 5 time words in one call.   */
#define READTIME        _IOR('X',32,struct btfp_time)
#define VMEFD "/dev/btfp0"

struct vmedate {               /* structure returned by get_vmetime.c */
	unsigned short year;
	unsigned short doy;
	unsigned short hr;
	unsigned short mn;
	unsigned short sec;
	unsigned long frac;
	unsigned short status;
};

/* END OF STUFF FROM RES */

/*
 * Definitions
 */
#define MAXUNITS 2              /* max number of VME units */
#define BMAX  50        /* timecode buffer length */

/*
 * VME interface parameters. 
 */
#define VMEPRECISION    (-21)   /* precision assumed (1 us) */
#define USNOREFID       "USNO"  /* or whatever */
#define VMEREFID        "USNO"  /* reference id */
#define VMEDESCRIPTION  "USNO Master Clock 2" /* who we are */
#define VMEHSREFID      0x7f7f1000 /* 127.127.16.00 refid hi strata */
/* clock type 16 is used here  */
#define GMT             0       /* hour offset from Greenwich */

/*
 * VME unit control structure.
 */
struct vmeunit {
	struct peer *peer;      /* associated peer structure */
	struct refclockio io;   /* given to the I/O handler */
	struct vmedate vmedata; /* data returned from vme read */
	l_fp lastrec;           /* last local time */
	l_fp lastref;           /* last timecode time */
	char lastcode[BMAX];    /* last timecode received */
	u_short lencode;        /* length of last timecode */
	u_long lasttime;        /* last time clock heard from */
	u_short unit;           /* unit number for this guy */
	u_short status;         /* clock status */
	u_short lastevent;      /* last clock event */
	u_short year;           /* year of eternity */
	u_short day;            /* day of year */
	u_short hour;           /* hour of day */
	u_short minute;         /* minute of hour */
	u_short second;         /* seconds of minute */
	u_long usec;            /* microsecond of second */
	u_long yearstart;       /* start of current year */
	u_short leap;           /* leap indicators */
	/*
	 * Status tallies
	 */
	u_long polls;           /* polls sent */
	u_long noreply;         /* no replies to polls */
	u_long coderecv;        /* timecodes received */
	u_long badformat;       /* bad format */
	u_long baddata;         /* bad data */
	u_long timestarted;     /* time we started this */
};

/*
 * Data space for the unit structures.  Note that we allocate these on
 * the fly, but never give them back.
 */
static struct vmeunit *vmeunits[MAXUNITS];
static u_char unitinuse[MAXUNITS];

/*
 * Keep the fudge factors separately so they can be set even
 * when no clock is configured.
 */
static l_fp fudgefactor[MAXUNITS];
static u_char stratumtouse[MAXUNITS];
static u_char sloppyclockflag[MAXUNITS];

/*
 * Function prototypes
 */
static  void    vme_init        (void);
static  int     vme_start       (u_int, struct peer *);
static  void    vme_shutdown    (int);
static  void    vme_report_event        (struct vmeunit *, int);
static  void    vme_receive     (struct recvbuf *);
static  void    vme_poll        (int unit, struct peer *);
static  void    vme_control     (u_int, struct refclockstat *, struct refclockstat *);
static  void    vme_buginfo     (int, struct refclockbug *);
struct vmedate *get_datumtime();

/*
 * Transfer vector
 */
struct  refclock refclock_bancomm = {
	vme_start, vme_shutdown, vme_poll,
	vme_control, vme_init, vme_buginfo, NOFLAGS
};

int fd_vme;  /* file descriptor for ioctls */
int regvalue;

/*
 * vme_init - initialize internal vme driver data
 */
static void
vme_init(void)
{
	register int i;

	/*
	 * Just zero the data arrays
	 */
	/*
	  bzero((char *)vmeunits, sizeof vmeunits);
	  bzero((char *)unitinuse, sizeof unitinuse);
	*/

	/*
	 * Initialize fudge factors to default.
	 */
	for (i = 0; i < MAXUNITS; i++) {
		fudgefactor[i].l_ui = 0;
		fudgefactor[i].l_uf = 0;
		stratumtouse[i] = 0;
		sloppyclockflag[i] = 0;
	}
}

/*
 * vme_start - open the VME device and initialize data for processing
 */
static int
vme_start(
	u_int unit,
	struct peer *peer
	)
{
	register struct vmeunit *vme;
	register int i;
	int dummy;
	char vmedev[20];

	/*
	 * Check configuration info.
	 */
	if (unit >= MAXUNITS) {
		msyslog(LOG_ERR, "vme_start: unit %d invalid", unit);
		return (0);
	}
	if (unitinuse[unit]) {
		msyslog(LOG_ERR, "vme_start: unit %d in use", unit);
		return (0);
	}

	/*
	 * Open VME device
	 */
#ifdef DEBUG

	printf("Opening DATUM VME DEVICE \n");
#endif
	if ( (fd_vme = open(VMEFD, O_RDWR)) < 0) {
		msyslog(LOG_ERR, "vme_start: failed open of %s: %m", vmedev);
		return (0);
	}
	else  { /* Release capture lockout in case it was set from before. */
		if( ioctl( fd_vme, RUNLOCK, &dummy ) )
		    msyslog(LOG_ERR, "vme_start: RUNLOCK failed %m");

		regvalue = 0; /* More esoteric stuff to do... */
		if( ioctl( fd_vme, WCR0, &regvalue ) )
		    msyslog(LOG_ERR, "vme_start: WCR0 failed %m");
	}

	/*
	 * Allocate unit structure
	 */
	if (vmeunits[unit] != 0) {
		vme = vmeunits[unit];   /* The one we want is okay */
	} else {
		for (i = 0; i < MAXUNITS; i++) {
			if (!unitinuse[i] && vmeunits[i] != 0)
			    break;
		}
		if (i < MAXUNITS) {
			/*
			 * Reclaim this one
			 */
			vme = vmeunits[i];
			vmeunits[i] = 0;
		} else {
			vme = (struct vmeunit *)
				emalloc(sizeof(struct vmeunit));
		}
	}
	bzero((char *)vme, sizeof(struct vmeunit));
	vmeunits[unit] = vme;

	/*
	 * Set up the structures
	 */
	vme->peer = peer;
	vme->unit = (u_short)unit;
	vme->timestarted = current_time;

	vme->io.clock_recv = vme_receive;
	vme->io.srcclock = (caddr_t)vme;
	vme->io.datalen = 0;
	vme->io.fd = fd_vme;

	/*
	 * All done.  Initialize a few random peer variables, then
	 * return success. Note that root delay and root dispersion are
	 * always zero for this clock.
	 */
	peer->precision = VMEPRECISION;
	peer->stratum = stratumtouse[unit];
	memcpy( (char *)&peer->refid, USNOREFID,4);

	/* peer->refid = htonl(VMEHSREFID); */

	unitinuse[unit] = 1;
	return (1);
}


/*
 * vme_shutdown - shut down a VME clock
 */
static void
vme_shutdown(
	int unit
	)
{
	register struct vmeunit *vme;

	if (unit >= MAXUNITS) {
		msyslog(LOG_ERR, "vme_shutdown: unit %d invalid", unit);
		return;
	}
	if (!unitinuse[unit]) {
		msyslog(LOG_ERR, "vme_shutdown: unit %d not in use", unit);
		return;
	}

	/*
	 * Tell the I/O module to turn us off.  We're history.
	 */
	vme = vmeunits[unit];
	io_closeclock(&vme->io);
	unitinuse[unit] = 0;
}

/*
 * vme_report_event - note the occurance of an event
 *
 * This routine presently just remembers the report and logs it, but
 * does nothing heroic for the trap handler.
 */
static void
vme_report_event(
	struct vmeunit *vme,
	int code
	)
{
	struct peer *peer;
        
	peer = vme->peer;
	if (vme->status != (u_short)code) {
		vme->status = (u_short)code;
		if (code != CEVNT_NOMINAL)
		    vme->lastevent = (u_short)code;
		msyslog(LOG_INFO,
			"clock %s event %x", ntoa(&peer->srcadr), code);
	}
}


/*
 * vme_receive - receive data from the VME device.
 *
 * Note: This interface would be interrupt-driven. We don't use that
 * now, but include a dummy routine for possible future adventures.
 */
static void
vme_receive(
	struct recvbuf *rbufp
	)
{
}

/*
 * vme_poll - called by the transmit procedure
 */
static void
vme_poll(
	int unit,
	struct peer *peer
	)
{
	struct vmedate *tptr; 
	struct vmeunit *vme;
	l_fp tstmp;
	time_t tloc;
	struct tm *tadr;
        
	vme = (struct vmeunit *)emalloc(sizeof(struct vmeunit *));
	tptr = (struct vmedate *)emalloc(sizeof(struct vmedate *));
 
	if (unit >= MAXUNITS) {
		msyslog(LOG_ERR, "vme_poll: unit %d invalid", unit);
		return;
	}
	if (!unitinuse[unit]) {
		msyslog(LOG_ERR, "vme_poll: unit %d not in use", unit);
		return;
	}
	vme = vmeunits[unit];        /* Here is the structure */
	vme->polls++;

	tptr = &vme->vmedata; 
	if ((tptr = get_datumtime()) == NULL ) {
		vme_report_event(vme, CEVNT_BADREPLY);
		return;
	}

	get_systime(&vme->lastrec);
	vme->lasttime = current_time;

	/*
	 * Get VME time and convert to timestamp format. 
	 * The year must come from the system clock.
	 */
	/*
	  time(&tloc);
	  tadr = gmtime(&tloc);
	  tptr->year = (unsigned short)(tadr->tm_year + 1900);
	*/

	sprintf(vme->lastcode, 
		"%3.3d %2.2d:%2.2d:%2.2d.%.6d %1d\0",
		tptr->doy, tptr->hr, tptr->mn,
		tptr->sec, tptr->frac, tptr->status);

	record_clock_stats(&(vme->peer->srcadr), vme->lastcode);
	vme->lencode = (u_short) strlen(vme->lastcode);

	vme->day =  tptr->doy;
	vme->hour =   tptr->hr;
	vme->minute =  tptr->mn;
	vme->second =  tptr->sec;
	vme->usec =   tptr->frac;

#ifdef DEBUG
	if (debug)
	    printf("vme: %3d %02d:%02d:%02d.%06ld %1x\n",
		   vme->day, vme->hour, vme->minute, vme->second,
		   vme->usec, tptr->status);
#endif
	if (tptr->status ) {       /*  Status 0 is locked to ref., 1 is not */
		vme_report_event(vme, CEVNT_BADREPLY);
		return;
	}

	/*
	 * Now, compute the reference time value. Use the heavy
	 * machinery for the seconds and the millisecond field for the
	 * fraction when present. If an error in conversion to internal
	 * format is found, the program declares bad data and exits.
	 * Note that this code does not yet know how to do the years and
	 * relies on the clock-calendar chip for sanity.
	 */
	if (!clocktime(vme->day, vme->hour, vme->minute,
		       vme->second, GMT, vme->lastrec.l_ui,
		       &vme->yearstart, &vme->lastref.l_ui)) {
		vme->baddata++;
		vme_report_event(vme, CEVNT_BADTIME);
		msyslog(LOG_ERR, "refclock_datum: bad data!!");
		return;
	}
	TVUTOTSF(vme->usec, vme->lastref.l_uf);
	tstmp = vme->lastref;

	L_SUB(&tstmp, &vme->lastrec);
	vme->coderecv++;

	L_ADD(&tstmp, &(fudgefactor[vme->unit]));

	refclock_receive(vme->peer);
}

/*
 * vme_control - set fudge factors, return statistics
 */
static void
vme_control(
	u_int unit,
	struct refclockstat *in,
	struct refclockstat *out
	)
{
	register struct vmeunit *vme;

	if (unit >= MAXUNITS) {
		msyslog(LOG_ERR, "vme_control: unit %d invalid)", unit);
		return;
	}

	if (in != 0) {
		if (in->haveflags & CLK_HAVETIME1)
		    fudgefactor[unit] = in->fudgetime1;
		if (in->haveflags & CLK_HAVEVAL1) {
			stratumtouse[unit] = (u_char)(in->fudgeval1 & 0xf);
			if (unitinuse[unit]) {
				struct peer *peer;

                                /*
                                 * Should actually reselect clock, but
                                 * will wait for the next timecode
                                 */
				vme = vmeunits[unit];
				peer = vme->peer;
				peer->stratum = stratumtouse[unit];
				if (stratumtouse[unit] <= 1)
				    memcpy( (char *)&peer->refid, USNOREFID,4);
				else
				    peer->refid = htonl(VMEHSREFID);
			}
		}
		if (in->haveflags & CLK_HAVEFLAG1) {
			sloppyclockflag[unit] = in->flags & CLK_FLAG1;
		}
	}

	if (out != 0) {
		/*              out->type = REFCLK_IRIG_VME; */
		out->type = 15;  /* made up by RES */
		out->haveflags
			= CLK_HAVETIME1|CLK_HAVEVAL1|CLK_HAVEVAL2|CLK_HAVEFLAG1;
		out->clockdesc = VMEDESCRIPTION;
		out->fudgetime1 = fudgefactor[unit];
		out->fudgetime2.l_ui = 0;
		out->fudgetime2.l_uf = 0;
		out->fudgeval1 = (long)stratumtouse[unit];
		out->fudgeval2 = 0;
		out->flags = sloppyclockflag[unit];
		if (unitinuse[unit]) {
			vme = vmeunits[unit];
			out->lencode = vme->lencode;
			out->lastcode = vme->lastcode;
			out->timereset = current_time - vme->timestarted;
			out->polls = vme->polls;
			out->noresponse = vme->noreply;
			out->badformat = vme->badformat;
			out->baddata = vme->baddata;
			out->lastevent = vme->lastevent;
			out->currentstatus = vme->status;
		} else {
			out->lencode = 0;
			out->lastcode = "";
			out->polls = out->noresponse = 0;
			out->badformat = out->baddata = 0;
			out->timereset = 0;
			out->currentstatus = out->lastevent = CEVNT_NOMINAL;
		}
	}
}

/*
 * vme_buginfo - return clock dependent debugging info
 */
static void
vme_buginfo(
	int unit,
	register struct refclockbug *bug
	)
{
	register struct vmeunit *vme;

	if (unit >= MAXUNITS) {
		msyslog(LOG_ERR, "vme_buginfo: unit %d invalid)", unit);
		return;
	}

	if (!unitinuse[unit])
	    return;
	vme = vmeunits[unit];

	bug->nvalues = 11;
	bug->ntimes = 5;
	if (vme->lasttime != 0)
	    bug->values[0] = current_time - vme->lasttime;
	else
	    bug->values[0] = 0;
	bug->values[2] = (u_long)vme->year;
	bug->values[3] = (u_long)vme->day;
	bug->values[4] = (u_long)vme->hour;
	bug->values[5] = (u_long)vme->minute;
	bug->values[6] = (u_long)vme->second;
	bug->values[7] = (u_long)vme->usec;
	bug->values[9] = vme->yearstart;
	bug->stimes = 0x1c;
	bug->times[0] = vme->lastref;
	bug->times[1] = vme->lastrec;
}

struct vmedate *
get_datumtime(void)
{
	unsigned short  status;
	char cbuf[7];
	struct vmedate  *time_vme;
	struct btfp_time vts;
	time_vme = (struct vmedate *)malloc(sizeof(struct vmedate ));

	if( ioctl(fd_vme, READTIME, &vts))
	    msyslog(LOG_ERR, "get_datumtime error: %m");

	/* if you want to actually check the validity of these registers, do a 
	   define of CHECK   above this.  I didn't find it necessary. - RES
	*/

#ifdef CHECK            

	/* Get doy */
	sprintf(cbuf,"%3.3x\0", ((vts.btfp_time[ 0 ] & 0x000f) <<8) +
		((vts.btfp_time[ 1 ] & 0xff00) >> 8));  

	if (isdigit(cbuf[0]) && isdigit(cbuf[1]) && isdigit(cbuf[2]) )
	    time_vme->doy = (unsigned short)atoi(cbuf);
	else
	    time_vme->doy = (unsigned short) 0;

	/* Get hour */
	sprintf(cbuf,"%2.2x\0", vts.btfp_time[ 1 ] & 0x00ff);

	if (isdigit(cbuf[0]) && isdigit(cbuf[1]))
	    time_vme->hr = (unsigned short)atoi(cbuf);
	else
	    time_vme->hr = (unsigned short) 0;

	/* Get minutes */
	sprintf(cbuf,"%2.2x\0", (vts.btfp_time[ 2 ] & 0xff00) >>8);
	if (isdigit(cbuf[0]) && isdigit(cbuf[1]))
	    time_vme->mn = (unsigned short)atoi(cbuf);
	else
	    time_vme->mn = (unsigned short) 0;

	/* Get seconds */
	sprintf(cbuf,"%2.2x\0", vts.btfp_time[ 2 ] & 0x00ff);

	if (isdigit(cbuf[0]) && isdigit(cbuf[1]))
	    time_vme->sec = (unsigned short)atoi(cbuf);
	else
	    time_vme->sec = (unsigned short) 0;

	/* Get microseconds.  Yes, we ignore the 0.1 microsecond digit so we can
	   use the TVTOTSF function  later on...*/

	sprintf(cbuf,"%4.4x%2.2x\0", vts.btfp_time[ 3 ],
		vts.btfp_time[ 4 ]>>8);

	if (isdigit(cbuf[0]) && isdigit(cbuf[1]) && isdigit(cbuf[2])
	    && isdigit(cbuf[3]) && isdigit(cbuf[4]) && isdigit(cbuf[5]))
	    time_vme->frac = (u_long) atoi(cbuf);
	else
	    time_vme->frac = (u_long) 0;
#else

	/* DONT CHECK  just trust the card */

	/* Get doy */
	sprintf(cbuf,"%3.3x\0", ((vts.btfp_time[ 0 ] & 0x000f) <<8) +
		((vts.btfp_time[ 1 ] & 0xff00) >> 8));  
	time_vme->doy = (unsigned short)atoi(cbuf);

	/* Get hour */
	sprintf(cbuf,"%2.2x\0", vts.btfp_time[ 1 ] & 0x00ff);

	time_vme->hr = (unsigned short)atoi(cbuf);

	/* Get minutes */
	sprintf(cbuf,"%2.2x\0", (vts.btfp_time[ 2 ] & 0xff00) >>8);
	time_vme->mn = (unsigned short)atoi(cbuf);

	/* Get seconds */
	sprintf(cbuf,"%2.2x\0", vts.btfp_time[ 2 ] & 0x00ff);
	time_vme->sec = (unsigned short)atoi(cbuf);

	/* Get microseconds.  Yes, we ignore the 0.1 microsecond digit so we can
	   use the TVTOTSF function  later on...*/

	sprintf(cbuf,"%4.4x%2.2x\0", vts.btfp_time[ 3 ],
		vts.btfp_time[ 4 ]>>8);

	time_vme->frac = (u_long) atoi(cbuf);

#endif /* CHECK */

	/* Get status bit */
	status = (vts.btfp_time[0] & 0x0010) >>4;
	time_vme->status = status;  /* Status=0 if locked to ref. */
	/* Status=1 if flywheeling */
	if (status) {        /* lost lock ? */
		return ((void *)NULL);
	}
	else
	    return (time_vme);
}

#else
int refclock_bancomm_bs;
#endif /* REFCLOCK */
