/*
 * refclock_gpsvme.c  NTP clock driver for the TrueTime GPS-VME
 * R. Schmidt, Time Service, US Naval Obs.  res@tuttle.usno.navy.mil
 * 
 * The refclock type has been defined as 16 (until new id assigned). 
 * These DEFS are included in the Makefile:
 *      DEFS= -DHAVE_TERMIOS -DSYS_HPUX=9
 *      DEFS_LOCAL=  -DREFCLOCK
 *      CLOCKDEFS=   -DGPSVME
 *  The file map_vme.c does the VME memory mapping, and includes vme_init().
 *  map_vme.c is HP-UX specific, because HPUX cannot mmap() device files! Boo!
 *  The file gps.h   provides TrueTime register info. 
 */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

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

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

/* GLOBAL STUFF BY RES */

#include <time.h>

#define PRIO    120		/* set the realtime priority */
#define NREGS	7		/* number of registers we will use */

extern int init_vme();		/* This is just a call to map_vme() */
				/* It doesn't have to be extern */
unsigned short  *greg[NREGS];	/* GPS registers defined in gps.h */
void *gps_base;			/* Base address of GPS VME card returned by */
				/* the map_vme() call */
extern caddr_t map_vme ();   
extern void unmap_vme();	/* Unmaps the VME space */

struct vmedate {		/* structure needed by ntp */
         unsigned short year;	/* *tptr is a pointer to this */
         unsigned short doy;
         unsigned short hr;
         unsigned short mn;
         unsigned short sec;
         unsigned long frac;
         unsigned short status;
         };

struct vmedate *get_gpsvme_time();

/* 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\0"  /* Or whatever? */
#define VMEREFID        "GPS"   /* reference id */
#define VMEDESCRIPTION  "GPS" /* who we are */
#define VMEHSREFID      0x7f7f1001 /* 127.127.16.01 refid hi strata */

                   /* I'm using clock type 16 until one is assigned */
                   /* This is set also in vme_control, below        */


#define GMT             0       /* hour offset from Greenwich */

/*
 * Imported from ntp_timer module
 */
extern u_long current_time;     /* current time (s) */

/*
 * Imported from ntpd module
 */
extern int debug;               /* global debug flag */

/*
 * 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        P(());
static  int     vme_start       P((u_int, struct peer *));
static  void    vme_shutdown    P((int));
static  void    vme_report_event        P((struct vmeunit *, int));
static  void    vme_receive     P((struct recvbuf *));
static  void    vme_poll        P((int unit, struct peer *));
static  void    vme_control     P((u_int, struct refclockstat *, struct refclockstat *));
static  void    vme_buginfo     P((int, struct refclockbug *));

/*
 * Transfer vector
 */
struct  refclock refclock_gpsvme = {
        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()
{
        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(unit, peer)
        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  VME DEVICE \n");
#endif
        init_vme();   /* This is in the map_vme.c external file */

        /*
         * 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->rootdelay = 0;
        peer->rootdispersion = 0;
        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(unit)
        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.
                 */
         unmap_vme();
        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(vme, code)
        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(rbufp)
        struct recvbuf *rbufp;
{
}

/*
 * vme_poll - called by the transmit procedure
 */
static void
vme_poll(unit, peer)
        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_gpsvme_time()) == 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->a_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->a_lastcode);
        vme->lencode = (u_short) strlen(vme->a_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_gpsvme: 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, &tstmp, GMT, 0,
            &vme->lastrec, &vme->lastrec, vme->leap);
}

/*
 * vme_control - set fudge factors, return statistics
 */
static void
vme_control(unit, in, out)
        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 = 16;  /*set  by RES  SHOULD BE CHANGED */
                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;
                        strcpy(out->a_lastcode, vme->a_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->a_lastcode[0] = '\0';
                        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(unit, bug)
        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;
}
/* -------------------------------------------------------*/
/* get_gpsvme_time()                                      */
/*  R. Schmidt, USNO, 1995                                */
/*  It's ugly, but hey, it works and its free             */

#include "gps.h"  /* defines for TrueTime GPS-VME */

#define PBIAS  193 /* 193 microsecs to read the GPS  experimentally found */

struct vmedate *get_gpsvme_time()
{
        struct vmedate  *time_vme;
        unsigned short set, hr, min, sec, ums, hms, status;
        int ret;
        char ti[3];

        long tloc ;
        time_t  mktime(),time();
        struct tm *gmtime(), *gmt;
        char  *gpsmicro;
        gpsmicro = (char *) malloc(7);  

        time_vme = (struct vmedate *)malloc(sizeof(struct vmedate ));
        *greg = (unsigned short *)malloc(sizeof(short) * NREGS);


/*  reference the freeze command address general register 1 */
        set = *greg[0];
/*  read the registers : */
/* get year */
        time_vme->year  = (unsigned short)  *greg[6];  
/* Get doy */
        time_vme->doy =  (unsigned short) (*greg[5] & MASKDAY);  
/* Get hour */
        time_vme->hr =  (unsigned short) ((*greg[4] & MASKHI) >>8);
/* Get minutes */
        time_vme->mn = (unsigned short)  (*greg[4] & MASKLO);
/* Get seconds */
        time_vme->sec = (unsigned short)  (*greg[3] & MASKHI) >>8;
        /* get microseconds in 2 parts and put together */
        ums  =   *greg[2];
        hms  =   *greg[3] & MASKLO;

        time_vme->status = (unsigned short) *greg[5] >>13;

/*  reference the unfreeze command address general register 1 */
        set = *greg[1];

        sprintf(gpsmicro,"%2.2x%4.4x\0", hms, ums);
        time_vme->frac = (u_long) gpsmicro;

/*      unmap_vme(); */

        if (!status) { 
                return ((void *)NULL);
                }
        else
                return (time_vme);
}

#else /* not (REFCLOCK && GPSVME) */
int refclock_gpsvme_bs;
#endif /* not (REFCLOCK && GPSVME) */
