1*eabc0478Schristos /* $NetBSD: refclock_zyfer.c,v 1.6 2024/08/18 20:47:19 christos Exp $ */ 2abb0f93cSkardel 3abb0f93cSkardel /* 4abb0f93cSkardel * refclock_zyfer - clock driver for the Zyfer GPSTarplus Clock 5abb0f93cSkardel * 6abb0f93cSkardel * Harlan Stenn, Jan 2002 7abb0f93cSkardel */ 8abb0f93cSkardel 9abb0f93cSkardel #ifdef HAVE_CONFIG_H 10abb0f93cSkardel #include <config.h> 11abb0f93cSkardel #endif 12abb0f93cSkardel 13abb0f93cSkardel #if defined(REFCLOCK) && defined(CLOCK_ZYFER) 14abb0f93cSkardel 15abb0f93cSkardel #include "ntpd.h" 16abb0f93cSkardel #include "ntp_io.h" 17abb0f93cSkardel #include "ntp_refclock.h" 18abb0f93cSkardel #include "ntp_stdlib.h" 19abb0f93cSkardel #include "ntp_unixtime.h" 20cdfa2a7eSchristos #include "ntp_calgps.h" 21abb0f93cSkardel 22abb0f93cSkardel #include <stdio.h> 23abb0f93cSkardel #include <ctype.h> 24abb0f93cSkardel 258585484eSchristos #if defined(HAVE_TERMIOS_H) 268585484eSchristos # include <termios.h> 278585484eSchristos #elif defined(HAVE_SYS_TERMIOS_H) 28abb0f93cSkardel # include <sys/termios.h> 29abb0f93cSkardel #endif 30abb0f93cSkardel #ifdef HAVE_SYS_PPSCLOCK_H 31abb0f93cSkardel # include <sys/ppsclock.h> 32abb0f93cSkardel #endif 33abb0f93cSkardel 34abb0f93cSkardel /* 35abb0f93cSkardel * This driver provides support for the TOD serial port of a Zyfer GPStarplus. 36abb0f93cSkardel * This clock also provides PPS as well as IRIG outputs. 37abb0f93cSkardel * Precision is limited by the serial driver, etc. 38abb0f93cSkardel * 39abb0f93cSkardel * If I was really brave I'd hack/generalize the serial driver to deal 40abb0f93cSkardel * with arbitrary on-time characters. This clock *begins* the stream with 41abb0f93cSkardel * `!`, the on-time character, and the string is *not* EOL-terminated. 42abb0f93cSkardel * 43abb0f93cSkardel * Configure the beast for 9600, 8N1. While I see leap-second stuff 44abb0f93cSkardel * in the documentation, the published specs on the TOD format only show 45abb0f93cSkardel * the seconds going to '59'. I see no leap warning in the TOD format. 46abb0f93cSkardel * 47abb0f93cSkardel * The clock sends the following message once per second: 48abb0f93cSkardel * 49abb0f93cSkardel * !TIME,2002,017,07,59,32,2,4,1 50abb0f93cSkardel * YYYY DDD HH MM SS m T O 51abb0f93cSkardel * 52abb0f93cSkardel * ! On-time character 53abb0f93cSkardel * YYYY Year 54abb0f93cSkardel * DDD 001-366 Day of Year 55abb0f93cSkardel * HH 00-23 Hour 56abb0f93cSkardel * MM 00-59 Minute 57abb0f93cSkardel * SS 00-59 Second (probably 00-60) 58abb0f93cSkardel * m 1-5 Time Mode: 59abb0f93cSkardel * 1 = GPS time 60abb0f93cSkardel * 2 = UTC time 61abb0f93cSkardel * 3 = LGPS time (Local GPS) 62abb0f93cSkardel * 4 = LUTC time (Local UTC) 63abb0f93cSkardel * 5 = Manual time 64abb0f93cSkardel * T 4-9 Time Figure Of Merit: 65abb0f93cSkardel * 4 x <= 1us 66abb0f93cSkardel * 5 1us < x <= 10 us 67abb0f93cSkardel * 6 10us < x <= 100us 68abb0f93cSkardel * 7 100us < x <= 1ms 69abb0f93cSkardel * 8 1ms < x <= 10ms 70abb0f93cSkardel * 9 10ms < x 71abb0f93cSkardel * O 0-4 Operation Mode: 72abb0f93cSkardel * 0 Warm-up 73abb0f93cSkardel * 1 Time Locked 74abb0f93cSkardel * 2 Coasting 75abb0f93cSkardel * 3 Recovering 76abb0f93cSkardel * 4 Manual 77abb0f93cSkardel * 78abb0f93cSkardel */ 79abb0f93cSkardel 80abb0f93cSkardel /* 81abb0f93cSkardel * Interface definitions 82abb0f93cSkardel */ 83abb0f93cSkardel #define DEVICE "/dev/zyfer%d" /* device name and unit */ 84abb0f93cSkardel #define SPEED232 B9600 /* uart speed (9600 baud) */ 85abb0f93cSkardel #define PRECISION (-20) /* precision assumed (about 1 us) */ 86abb0f93cSkardel #define REFID "GPS\0" /* reference ID */ 87abb0f93cSkardel #define DESCRIPTION "Zyfer GPStarplus" /* WRU */ 88abb0f93cSkardel 89abb0f93cSkardel #define LENZYFER 29 /* timecode length */ 90abb0f93cSkardel 91abb0f93cSkardel /* 92abb0f93cSkardel * Unit control structure 93abb0f93cSkardel */ 94abb0f93cSkardel struct zyferunit { 95abb0f93cSkardel u_char Rcvbuf[LENZYFER + 1]; 96abb0f93cSkardel u_char polled; /* poll message flag */ 97abb0f93cSkardel int pollcnt; 98abb0f93cSkardel l_fp tstamp; /* timestamp of last poll */ 99abb0f93cSkardel int Rcvptr; 100abb0f93cSkardel }; 101abb0f93cSkardel 102abb0f93cSkardel /* 103abb0f93cSkardel * Function prototypes 104abb0f93cSkardel */ 105abb0f93cSkardel static int zyfer_start (int, struct peer *); 106abb0f93cSkardel static void zyfer_shutdown (int, struct peer *); 107abb0f93cSkardel static void zyfer_receive (struct recvbuf *); 108abb0f93cSkardel static void zyfer_poll (int, struct peer *); 109abb0f93cSkardel 110abb0f93cSkardel /* 111abb0f93cSkardel * Transfer vector 112abb0f93cSkardel */ 113abb0f93cSkardel struct refclock refclock_zyfer = { 114abb0f93cSkardel zyfer_start, /* start up driver */ 115abb0f93cSkardel zyfer_shutdown, /* shut down driver */ 116abb0f93cSkardel zyfer_poll, /* transmit poll message */ 117abb0f93cSkardel noentry, /* not used (old zyfer_control) */ 118abb0f93cSkardel noentry, /* initialize driver (not used) */ 119abb0f93cSkardel noentry, /* not used (old zyfer_buginfo) */ 120abb0f93cSkardel NOFLAGS /* not used */ 121abb0f93cSkardel }; 122abb0f93cSkardel 123abb0f93cSkardel 124abb0f93cSkardel /* 125abb0f93cSkardel * zyfer_start - open the devices and initialize data for processing 126abb0f93cSkardel */ 127abb0f93cSkardel static int 128abb0f93cSkardel zyfer_start( 129abb0f93cSkardel int unit, 130abb0f93cSkardel struct peer *peer 131abb0f93cSkardel ) 132abb0f93cSkardel { 133abb0f93cSkardel register struct zyferunit *up; 134abb0f93cSkardel struct refclockproc *pp; 135abb0f93cSkardel int fd; 136abb0f93cSkardel char device[20]; 137abb0f93cSkardel 138abb0f93cSkardel /* 139abb0f93cSkardel * Open serial port. 140abb0f93cSkardel * Something like LDISC_ACTS that looked for ! would be nice... 141abb0f93cSkardel */ 1428585484eSchristos snprintf(device, sizeof(device), DEVICE, unit); 143*eabc0478Schristos fd = refclock_open(&peer->srcadr, device, SPEED232, LDISC_RAW); 1448585484eSchristos if (fd <= 0) 145abb0f93cSkardel return (0); 146abb0f93cSkardel 147abb0f93cSkardel msyslog(LOG_NOTICE, "zyfer(%d) fd: %d dev <%s>", unit, fd, device); 148abb0f93cSkardel 149abb0f93cSkardel /* 150abb0f93cSkardel * Allocate and initialize unit structure 151abb0f93cSkardel */ 1528585484eSchristos up = emalloc(sizeof(struct zyferunit)); 1538585484eSchristos memset(up, 0, sizeof(struct zyferunit)); 154abb0f93cSkardel pp = peer->procptr; 155abb0f93cSkardel pp->io.clock_recv = zyfer_receive; 1568585484eSchristos pp->io.srcclock = peer; 157abb0f93cSkardel pp->io.datalen = 0; 158abb0f93cSkardel pp->io.fd = fd; 159abb0f93cSkardel if (!io_addclock(&pp->io)) { 1608585484eSchristos close(fd); 1618585484eSchristos pp->io.fd = -1; 162abb0f93cSkardel free(up); 163abb0f93cSkardel return (0); 164abb0f93cSkardel } 1658585484eSchristos pp->unitptr = up; 166abb0f93cSkardel 167abb0f93cSkardel /* 168abb0f93cSkardel * Initialize miscellaneous variables 169abb0f93cSkardel */ 170abb0f93cSkardel peer->precision = PRECISION; 171abb0f93cSkardel pp->clockdesc = DESCRIPTION; 172abb0f93cSkardel memcpy((char *)&pp->refid, REFID, 4); 173abb0f93cSkardel up->pollcnt = 2; 174abb0f93cSkardel up->polled = 0; /* May not be needed... */ 175abb0f93cSkardel 176abb0f93cSkardel return (1); 177abb0f93cSkardel } 178abb0f93cSkardel 179abb0f93cSkardel 180abb0f93cSkardel /* 181abb0f93cSkardel * zyfer_shutdown - shut down the clock 182abb0f93cSkardel */ 183abb0f93cSkardel static void 184abb0f93cSkardel zyfer_shutdown( 185abb0f93cSkardel int unit, 186abb0f93cSkardel struct peer *peer 187abb0f93cSkardel ) 188abb0f93cSkardel { 189abb0f93cSkardel register struct zyferunit *up; 190abb0f93cSkardel struct refclockproc *pp; 191abb0f93cSkardel 192abb0f93cSkardel pp = peer->procptr; 1938585484eSchristos up = pp->unitptr; 1948585484eSchristos if (pp->io.fd != -1) 195abb0f93cSkardel io_closeclock(&pp->io); 1968585484eSchristos if (up != NULL) 197abb0f93cSkardel free(up); 198abb0f93cSkardel } 199abb0f93cSkardel 200abb0f93cSkardel 201abb0f93cSkardel /* 202abb0f93cSkardel * zyfer_receive - receive data from the serial interface 203abb0f93cSkardel */ 204abb0f93cSkardel static void 205abb0f93cSkardel zyfer_receive( 206abb0f93cSkardel struct recvbuf *rbufp 207abb0f93cSkardel ) 208abb0f93cSkardel { 209abb0f93cSkardel register struct zyferunit *up; 210abb0f93cSkardel struct refclockproc *pp; 211abb0f93cSkardel struct peer *peer; 212abb0f93cSkardel int tmode; /* Time mode */ 213abb0f93cSkardel int tfom; /* Time Figure Of Merit */ 214abb0f93cSkardel int omode; /* Operation mode */ 215abb0f93cSkardel u_char *p; 216abb0f93cSkardel 217cdfa2a7eSchristos TCivilDate tsdoy; 218cdfa2a7eSchristos TNtpDatum tsntp; 219cdfa2a7eSchristos l_fp tfrac; 220cdfa2a7eSchristos 2218585484eSchristos peer = rbufp->recv_peer; 222abb0f93cSkardel pp = peer->procptr; 2238585484eSchristos up = pp->unitptr; 224abb0f93cSkardel p = (u_char *) &rbufp->recv_space; 225abb0f93cSkardel /* 226abb0f93cSkardel * If lencode is 0: 227abb0f93cSkardel * - if *rbufp->recv_space is ! 228abb0f93cSkardel * - - call refclock_gtlin to get things going 229abb0f93cSkardel * - else flush 230abb0f93cSkardel * else stuff it on the end of lastcode 231abb0f93cSkardel * If we don't have LENZYFER bytes 232abb0f93cSkardel * - wait for more data 233abb0f93cSkardel * Crack the beast, and if it's OK, process it. 234abb0f93cSkardel * 235abb0f93cSkardel * We use refclock_gtlin() because we might use LDISC_CLK. 236abb0f93cSkardel * 237abb0f93cSkardel * Under FreeBSD, we get the ! followed by two 14-byte packets. 238abb0f93cSkardel */ 239abb0f93cSkardel 240abb0f93cSkardel if (pp->lencode >= LENZYFER) 241abb0f93cSkardel pp->lencode = 0; 242abb0f93cSkardel 243abb0f93cSkardel if (!pp->lencode) { 244abb0f93cSkardel if (*p == '!') 245abb0f93cSkardel pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, 246abb0f93cSkardel BMAX, &pp->lastrec); 247abb0f93cSkardel else 248abb0f93cSkardel return; 249abb0f93cSkardel } else { 250abb0f93cSkardel memcpy(pp->a_lastcode + pp->lencode, p, rbufp->recv_length); 251abb0f93cSkardel pp->lencode += rbufp->recv_length; 252abb0f93cSkardel pp->a_lastcode[pp->lencode] = '\0'; 253abb0f93cSkardel } 254abb0f93cSkardel 255abb0f93cSkardel if (pp->lencode < LENZYFER) 256abb0f93cSkardel return; 257abb0f93cSkardel 258abb0f93cSkardel record_clock_stats(&peer->srcadr, pp->a_lastcode); 259abb0f93cSkardel 260abb0f93cSkardel /* 261abb0f93cSkardel * We get down to business, check the timecode format and decode 262abb0f93cSkardel * its contents. If the timecode has invalid length or is not in 263abb0f93cSkardel * proper format, we declare bad format and exit. 264abb0f93cSkardel */ 265abb0f93cSkardel 266abb0f93cSkardel if (pp->lencode != LENZYFER) { 267abb0f93cSkardel refclock_report(peer, CEVNT_BADTIME); 268abb0f93cSkardel return; 269abb0f93cSkardel } 270abb0f93cSkardel 271abb0f93cSkardel /* 272abb0f93cSkardel * Timecode sample: "!TIME,2002,017,07,59,32,2,4,1" 273abb0f93cSkardel */ 274abb0f93cSkardel if (sscanf(pp->a_lastcode, "!TIME,%4d,%3d,%2d,%2d,%2d,%d,%d,%d", 275abb0f93cSkardel &pp->year, &pp->day, &pp->hour, &pp->minute, &pp->second, 276abb0f93cSkardel &tmode, &tfom, &omode) != 8) { 277abb0f93cSkardel refclock_report(peer, CEVNT_BADREPLY); 278abb0f93cSkardel return; 279abb0f93cSkardel } 280abb0f93cSkardel 281abb0f93cSkardel if (tmode != 2) { 282abb0f93cSkardel refclock_report(peer, CEVNT_BADTIME); 283abb0f93cSkardel return; 284abb0f93cSkardel } 285abb0f93cSkardel 286abb0f93cSkardel /* Should we make sure tfom is 4? */ 287abb0f93cSkardel 288abb0f93cSkardel if (omode != 1) { 289abb0f93cSkardel pp->leap = LEAP_NOTINSYNC; 290abb0f93cSkardel return; 291abb0f93cSkardel } 2928585484eSchristos 293cdfa2a7eSchristos /* treat GPS input as subject to era warps */ 294cdfa2a7eSchristos ZERO(tsdoy); 295cdfa2a7eSchristos ZERO(tfrac); 296cdfa2a7eSchristos 297cdfa2a7eSchristos tsdoy.year = pp->year; 298cdfa2a7eSchristos tsdoy.yearday = pp->day; 299cdfa2a7eSchristos tsdoy.hour = pp->hour; 300cdfa2a7eSchristos tsdoy.minute = pp->minute; 301cdfa2a7eSchristos tsdoy.second = pp->second; 302cdfa2a7eSchristos 303cdfa2a7eSchristos /* note: We kept 'month' and 'monthday' zero above. That forces 304cdfa2a7eSchristos * day-of-year based calculation now: 305cdfa2a7eSchristos */ 306cdfa2a7eSchristos tsntp = gpsntp_from_calendar(&tsdoy, tfrac); 307cdfa2a7eSchristos tfrac = ntpfp_from_ntpdatum(&tsntp); 308cdfa2a7eSchristos refclock_process_offset(pp, tfrac, pp->lastrec, pp->fudgetime1); 309abb0f93cSkardel 310abb0f93cSkardel /* 311abb0f93cSkardel * Good place for record_clock_stats() 312abb0f93cSkardel */ 313abb0f93cSkardel up->pollcnt = 2; 314abb0f93cSkardel 315abb0f93cSkardel if (up->polled) { 316abb0f93cSkardel up->polled = 0; 317abb0f93cSkardel refclock_receive(peer); 318abb0f93cSkardel } 319abb0f93cSkardel } 320abb0f93cSkardel 321abb0f93cSkardel 322abb0f93cSkardel /* 323abb0f93cSkardel * zyfer_poll - called by the transmit procedure 324abb0f93cSkardel */ 325abb0f93cSkardel static void 326abb0f93cSkardel zyfer_poll( 327abb0f93cSkardel int unit, 328abb0f93cSkardel struct peer *peer 329abb0f93cSkardel ) 330abb0f93cSkardel { 331abb0f93cSkardel register struct zyferunit *up; 332abb0f93cSkardel struct refclockproc *pp; 333abb0f93cSkardel 334abb0f93cSkardel /* 335abb0f93cSkardel * We don't really do anything here, except arm the receiving 336abb0f93cSkardel * side to capture a sample and check for timeouts. 337abb0f93cSkardel */ 338abb0f93cSkardel pp = peer->procptr; 3398585484eSchristos up = pp->unitptr; 340abb0f93cSkardel if (!up->pollcnt) 341abb0f93cSkardel refclock_report(peer, CEVNT_TIMEOUT); 342abb0f93cSkardel else 343abb0f93cSkardel up->pollcnt--; 344abb0f93cSkardel pp->polls++; 345abb0f93cSkardel up->polled = 1; 346abb0f93cSkardel } 347abb0f93cSkardel 348abb0f93cSkardel #else 349*eabc0478Schristos NONEMPTY_TRANSLATION_UNIT 350abb0f93cSkardel #endif /* REFCLOCK */ 351