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