1106163Sroberto/* 2106163Sroberto * refclock_zyfer - clock driver for the Zyfer GPSTarplus Clock 3106163Sroberto * 4106163Sroberto * Harlan Stenn, Jan 2002 5106163Sroberto */ 6106163Sroberto 7106163Sroberto#ifdef HAVE_CONFIG_H 8106163Sroberto#include <config.h> 9106163Sroberto#endif 10106163Sroberto 11106163Sroberto#if defined(REFCLOCK) && defined(CLOCK_ZYFER) 12106163Sroberto 13106163Sroberto#include "ntpd.h" 14106163Sroberto#include "ntp_io.h" 15106163Sroberto#include "ntp_refclock.h" 16106163Sroberto#include "ntp_stdlib.h" 17106163Sroberto#include "ntp_unixtime.h" 18106163Sroberto 19106163Sroberto#include <stdio.h> 20106163Sroberto#include <ctype.h> 21106163Sroberto 22106163Sroberto#ifdef HAVE_SYS_TERMIOS_H 23106163Sroberto# include <sys/termios.h> 24106163Sroberto#endif 25106163Sroberto#ifdef HAVE_SYS_PPSCLOCK_H 26106163Sroberto# include <sys/ppsclock.h> 27106163Sroberto#endif 28106163Sroberto 29106163Sroberto/* 30106163Sroberto * This driver provides support for the TOD serial port of a Zyfer GPStarplus. 31106163Sroberto * This clock also provides PPS as well as IRIG outputs. 32106163Sroberto * Precision is limited by the serial driver, etc. 33106163Sroberto * 34106163Sroberto * If I was really brave I'd hack/generalize the serial driver to deal 35106163Sroberto * with arbitrary on-time characters. This clock *begins* the stream with 36106163Sroberto * `!`, the on-time character, and the string is *not* EOL-terminated. 37106163Sroberto * 38106163Sroberto * Configure the beast for 9600, 8N1. While I see leap-second stuff 39106163Sroberto * in the documentation, the published specs on the TOD format only show 40106163Sroberto * the seconds going to '59'. I see no leap warning in the TOD format. 41106163Sroberto * 42106163Sroberto * The clock sends the following message once per second: 43106163Sroberto * 44106163Sroberto * !TIME,2002,017,07,59,32,2,4,1 45106163Sroberto * YYYY DDD HH MM SS m T O 46106163Sroberto * 47106163Sroberto * ! On-time character 48106163Sroberto * YYYY Year 49106163Sroberto * DDD 001-366 Day of Year 50106163Sroberto * HH 00-23 Hour 51106163Sroberto * MM 00-59 Minute 52106163Sroberto * SS 00-59 Second (probably 00-60) 53106163Sroberto * m 1-5 Time Mode: 54106163Sroberto * 1 = GPS time 55106163Sroberto * 2 = UTC time 56106163Sroberto * 3 = LGPS time (Local GPS) 57106163Sroberto * 4 = LUTC time (Local UTC) 58106163Sroberto * 5 = Manual time 59106163Sroberto * T 4-9 Time Figure Of Merit: 60106163Sroberto * 4 x <= 1us 61106163Sroberto * 5 1us < x <= 10 us 62106163Sroberto * 6 10us < x <= 100us 63106163Sroberto * 7 100us < x <= 1ms 64106163Sroberto * 8 1ms < x <= 10ms 65106163Sroberto * 9 10ms < x 66106163Sroberto * O 0-4 Operation Mode: 67106163Sroberto * 0 Warm-up 68106163Sroberto * 1 Time Locked 69106163Sroberto * 2 Coasting 70106163Sroberto * 3 Recovering 71106163Sroberto * 4 Manual 72106163Sroberto * 73106163Sroberto */ 74106163Sroberto 75106163Sroberto/* 76106163Sroberto * Interface definitions 77106163Sroberto */ 78106163Sroberto#define DEVICE "/dev/zyfer%d" /* device name and unit */ 79106163Sroberto#define SPEED232 B9600 /* uart speed (9600 baud) */ 80106163Sroberto#define PRECISION (-20) /* precision assumed (about 1 us) */ 81106163Sroberto#define REFID "GPS\0" /* reference ID */ 82106163Sroberto#define DESCRIPTION "Zyfer GPStarplus" /* WRU */ 83106163Sroberto 84106163Sroberto#define LENZYFER 29 /* timecode length */ 85106163Sroberto 86106163Sroberto/* 87106163Sroberto * Unit control structure 88106163Sroberto */ 89106163Srobertostruct zyferunit { 90106163Sroberto u_char Rcvbuf[LENZYFER + 1]; 91106163Sroberto u_char polled; /* poll message flag */ 92106163Sroberto int pollcnt; 93106163Sroberto l_fp tstamp; /* timestamp of last poll */ 94106163Sroberto int Rcvptr; 95106163Sroberto}; 96106163Sroberto 97106163Sroberto/* 98106163Sroberto * Function prototypes 99106163Sroberto */ 100106163Srobertostatic int zyfer_start P((int, struct peer *)); 101106163Srobertostatic void zyfer_shutdown P((int, struct peer *)); 102106163Srobertostatic void zyfer_receive P((struct recvbuf *)); 103106163Srobertostatic void zyfer_poll P((int, struct peer *)); 104106163Sroberto 105106163Sroberto/* 106106163Sroberto * Transfer vector 107106163Sroberto */ 108106163Srobertostruct refclock refclock_zyfer = { 109106163Sroberto zyfer_start, /* start up driver */ 110106163Sroberto zyfer_shutdown, /* shut down driver */ 111106163Sroberto zyfer_poll, /* transmit poll message */ 112106163Sroberto noentry, /* not used (old zyfer_control) */ 113106163Sroberto noentry, /* initialize driver (not used) */ 114106163Sroberto noentry, /* not used (old zyfer_buginfo) */ 115106163Sroberto NOFLAGS /* not used */ 116106163Sroberto}; 117106163Sroberto 118106163Sroberto 119106163Sroberto/* 120106163Sroberto * zyfer_start - open the devices and initialize data for processing 121106163Sroberto */ 122106163Srobertostatic int 123106163Srobertozyfer_start( 124106163Sroberto int unit, 125106163Sroberto struct peer *peer 126106163Sroberto ) 127106163Sroberto{ 128106163Sroberto register struct zyferunit *up; 129106163Sroberto struct refclockproc *pp; 130106163Sroberto int fd; 131106163Sroberto char device[20]; 132106163Sroberto 133106163Sroberto /* 134106163Sroberto * Open serial port. 135106163Sroberto * Something like LDISC_ACTS that looked for ! would be nice... 136106163Sroberto */ 137106163Sroberto (void)sprintf(device, DEVICE, unit); 138106163Sroberto if ( !(fd = refclock_open(device, SPEED232, LDISC_RAW)) ) 139106163Sroberto return (0); 140106163Sroberto 141106163Sroberto msyslog(LOG_NOTICE, "zyfer(%d) fd: %d dev <%s>", unit, fd, device); 142106163Sroberto 143106163Sroberto /* 144106163Sroberto * Allocate and initialize unit structure 145106163Sroberto */ 146106163Sroberto if (!(up = (struct zyferunit *) 147106163Sroberto emalloc(sizeof(struct zyferunit)))) { 148106163Sroberto (void) close(fd); 149106163Sroberto return (0); 150106163Sroberto } 151106163Sroberto memset((char *)up, 0, sizeof(struct zyferunit)); 152106163Sroberto pp = peer->procptr; 153106163Sroberto pp->io.clock_recv = zyfer_receive; 154106163Sroberto pp->io.srcclock = (caddr_t)peer; 155106163Sroberto pp->io.datalen = 0; 156106163Sroberto pp->io.fd = fd; 157106163Sroberto if (!io_addclock(&pp->io)) { 158106163Sroberto (void) close(fd); 159106163Sroberto free(up); 160106163Sroberto return (0); 161106163Sroberto } 162106163Sroberto pp->unitptr = (caddr_t)up; 163106163Sroberto 164106163Sroberto /* 165106163Sroberto * Initialize miscellaneous variables 166106163Sroberto */ 167106163Sroberto peer->precision = PRECISION; 168106163Sroberto pp->clockdesc = DESCRIPTION; 169106163Sroberto memcpy((char *)&pp->refid, REFID, 4); 170106163Sroberto up->pollcnt = 2; 171106163Sroberto up->polled = 0; /* May not be needed... */ 172106163Sroberto 173106163Sroberto return (1); 174106163Sroberto} 175106163Sroberto 176106163Sroberto 177106163Sroberto/* 178106163Sroberto * zyfer_shutdown - shut down the clock 179106163Sroberto */ 180106163Srobertostatic void 181106163Srobertozyfer_shutdown( 182106163Sroberto int unit, 183106163Sroberto struct peer *peer 184106163Sroberto ) 185106163Sroberto{ 186106163Sroberto register struct zyferunit *up; 187106163Sroberto struct refclockproc *pp; 188106163Sroberto 189106163Sroberto pp = peer->procptr; 190106163Sroberto up = (struct zyferunit *)pp->unitptr; 191106163Sroberto io_closeclock(&pp->io); 192106163Sroberto free(up); 193106163Sroberto} 194106163Sroberto 195106163Sroberto 196106163Sroberto/* 197106163Sroberto * zyfer_receive - receive data from the serial interface 198106163Sroberto */ 199106163Srobertostatic void 200106163Srobertozyfer_receive( 201106163Sroberto struct recvbuf *rbufp 202106163Sroberto ) 203106163Sroberto{ 204106163Sroberto register struct zyferunit *up; 205106163Sroberto struct refclockproc *pp; 206106163Sroberto struct peer *peer; 207106163Sroberto int tmode; /* Time mode */ 208106163Sroberto int tfom; /* Time Figure Of Merit */ 209106163Sroberto int omode; /* Operation mode */ 210106163Sroberto u_char *p; 211106163Sroberto#ifdef PPS 212106163Sroberto struct ppsclockev ppsev; 213106163Sroberto int request; 214106163Sroberto#ifdef HAVE_CIOGETEV 215106163Sroberto request = CIOGETEV; 216106163Sroberto#endif 217106163Sroberto#ifdef HAVE_TIOCGPPSEV 218106163Sroberto request = TIOCGPPSEV; 219106163Sroberto#endif 220106163Sroberto#endif /* PPS */ 221106163Sroberto 222106163Sroberto peer = (struct peer *)rbufp->recv_srcclock; 223106163Sroberto pp = peer->procptr; 224106163Sroberto up = (struct zyferunit *)pp->unitptr; 225106163Sroberto p = (u_char *) &rbufp->recv_space; 226106163Sroberto /* 227106163Sroberto * If lencode is 0: 228106163Sroberto * - if *rbufp->recv_space is ! 229106163Sroberto * - - call refclock_gtlin to get things going 230106163Sroberto * - else flush 231106163Sroberto * else stuff it on the end of lastcode 232106163Sroberto * If we don't have LENZYFER bytes 233106163Sroberto * - wait for more data 234106163Sroberto * Crack the beast, and if it's OK, process it. 235106163Sroberto * 236106424Sroberto * We use refclock_gtlin() because we might use LDISC_CLK. 237106163Sroberto * 238106163Sroberto * Under FreeBSD, we get the ! followed by two 14-byte packets. 239106163Sroberto */ 240106163Sroberto 241106163Sroberto if (pp->lencode >= LENZYFER) 242106163Sroberto pp->lencode = 0; 243106163Sroberto 244106163Sroberto if (!pp->lencode) { 245106163Sroberto if (*p == '!') 246106163Sroberto pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, 247106163Sroberto BMAX, &pp->lastrec); 248106163Sroberto else 249106163Sroberto return; 250106163Sroberto } else { 251106163Sroberto memcpy(pp->a_lastcode + pp->lencode, p, rbufp->recv_length); 252106163Sroberto pp->lencode += rbufp->recv_length; 253106163Sroberto pp->a_lastcode[pp->lencode] = '\0'; 254106163Sroberto } 255106163Sroberto 256106163Sroberto if (pp->lencode < LENZYFER) 257106163Sroberto return; 258106163Sroberto 259106163Sroberto record_clock_stats(&peer->srcadr, pp->a_lastcode); 260106163Sroberto 261106163Sroberto /* 262106163Sroberto * We get down to business, check the timecode format and decode 263106163Sroberto * its contents. If the timecode has invalid length or is not in 264106163Sroberto * proper format, we declare bad format and exit. 265106163Sroberto */ 266106163Sroberto 267106163Sroberto if (pp->lencode != LENZYFER) { 268106163Sroberto refclock_report(peer, CEVNT_BADTIME); 269106163Sroberto return; 270106163Sroberto } 271106163Sroberto 272106163Sroberto /* 273106163Sroberto * Timecode sample: "!TIME,2002,017,07,59,32,2,4,1" 274106163Sroberto */ 275106163Sroberto if (sscanf(pp->a_lastcode, "!TIME,%4d,%3d,%2d,%2d,%2d,%d,%d,%d", 276106163Sroberto &pp->year, &pp->day, &pp->hour, &pp->minute, &pp->second, 277106163Sroberto &tmode, &tfom, &omode) != 8) { 278106163Sroberto refclock_report(peer, CEVNT_BADREPLY); 279106163Sroberto return; 280106163Sroberto } 281106163Sroberto 282106163Sroberto if (tmode != 2) { 283106163Sroberto refclock_report(peer, CEVNT_BADTIME); 284106163Sroberto return; 285106163Sroberto } 286106163Sroberto 287106163Sroberto /* Should we make sure tfom is 4? */ 288106163Sroberto 289106163Sroberto if (omode != 1) { 290106163Sroberto pp->leap = LEAP_NOTINSYNC; 291106163Sroberto return; 292106163Sroberto } 293106163Sroberto#ifdef PPS 294106163Sroberto if(ioctl(fdpps,request,(caddr_t) &ppsev) >=0) { 295106163Sroberto ppsev.tv.tv_sec += (u_int32) JAN_1970; 296106163Sroberto TVTOTS(&ppsev.tv,&up->tstamp); 297106163Sroberto } 298106163Sroberto /* record the last ppsclock event time stamp */ 299106163Sroberto pp->lastrec = up->tstamp; 300106163Sroberto#endif /* PPS */ 301106163Sroberto if (!refclock_process(pp)) { 302106163Sroberto refclock_report(peer, CEVNT_BADTIME); 303106163Sroberto return; 304106163Sroberto } 305106163Sroberto 306106163Sroberto /* 307106163Sroberto * Good place for record_clock_stats() 308106163Sroberto */ 309106163Sroberto up->pollcnt = 2; 310106163Sroberto 311106163Sroberto if (up->polled) { 312106163Sroberto up->polled = 0; 313106163Sroberto refclock_receive(peer); 314106163Sroberto } 315106163Sroberto} 316106163Sroberto 317106163Sroberto 318106163Sroberto/* 319106163Sroberto * zyfer_poll - called by the transmit procedure 320106163Sroberto */ 321106163Srobertostatic void 322106163Srobertozyfer_poll( 323106163Sroberto int unit, 324106163Sroberto struct peer *peer 325106163Sroberto ) 326106163Sroberto{ 327106163Sroberto register struct zyferunit *up; 328106163Sroberto struct refclockproc *pp; 329106163Sroberto 330106163Sroberto /* 331106163Sroberto * We don't really do anything here, except arm the receiving 332106163Sroberto * side to capture a sample and check for timeouts. 333106163Sroberto */ 334106163Sroberto pp = peer->procptr; 335106163Sroberto up = (struct zyferunit *)pp->unitptr; 336106163Sroberto if (!up->pollcnt) 337106163Sroberto refclock_report(peer, CEVNT_TIMEOUT); 338106163Sroberto else 339106163Sroberto up->pollcnt--; 340106163Sroberto pp->polls++; 341106163Sroberto up->polled = 1; 342106163Sroberto} 343106163Sroberto 344106163Sroberto#else 345106163Srobertoint refclock_zyfer_bs; 346106163Sroberto#endif /* REFCLOCK */ 347