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