154359Sroberto/* 254359Sroberto * refclock_nmea.c - clock driver for an NMEA GPS CLOCK 354359Sroberto * Michael Petry Jun 20, 1994 454359Sroberto * based on refclock_heathn.c 5290001Sglebius * 6290001Sglebius * Updated to add support for Accord GPS Clock 7290001Sglebius * Venu Gopal Dec 05, 2007 8290001Sglebius * neo.venu@gmail.com, venugopal_d@pgad.gov.in 9290001Sglebius * 10290001Sglebius * Updated to process 'time1' fudge factor 11290001Sglebius * Venu Gopal May 05, 2008 12290001Sglebius * 13290001Sglebius * Converted to common PPSAPI code, separate PPS fudge time1 14290001Sglebius * from serial timecode fudge time2. 15290001Sglebius * Dave Hart July 1, 2009 16290001Sglebius * hart@ntp.org, davehart@davehart.com 1754359Sroberto */ 18290001Sglebius 1954359Sroberto#ifdef HAVE_CONFIG_H 2054359Sroberto#include <config.h> 2154359Sroberto#endif 2254359Sroberto 23290001Sglebius#include "ntp_types.h" 24290001Sglebius 2554359Sroberto#if defined(REFCLOCK) && defined(CLOCK_NMEA) 2654359Sroberto 27290001Sglebius#define NMEA_WRITE_SUPPORT 0 /* no write support at the moment */ 28290001Sglebius 29290001Sglebius#include <sys/stat.h> 30182007Sroberto#include <stdio.h> 31182007Sroberto#include <ctype.h> 32290001Sglebius#ifdef HAVE_SYS_SOCKET_H 33290001Sglebius#include <sys/socket.h> 34290001Sglebius#endif 35182007Sroberto 3654359Sroberto#include "ntpd.h" 3754359Sroberto#include "ntp_io.h" 3882498Sroberto#include "ntp_unixtime.h" 3954359Sroberto#include "ntp_refclock.h" 4054359Sroberto#include "ntp_stdlib.h" 41290001Sglebius#include "ntp_calendar.h" 42290001Sglebius#include "timespecops.h" 4354359Sroberto 4482498Sroberto#ifdef HAVE_PPSAPI 45182007Sroberto# include "ppsapi_timepps.h" 46290001Sglebius# include "refclock_atom.h" 4782498Sroberto#endif /* HAVE_PPSAPI */ 4882498Sroberto 49200576Sroberto 5054359Sroberto/* 51290001Sglebius * This driver supports NMEA-compatible GPS receivers 5254359Sroberto * 53290001Sglebius * Prototype was refclock_trak.c, Thanks a lot. 5454359Sroberto * 5554359Sroberto * The receiver used spits out the NMEA sentences for boat navigation. 56290001Sglebius * And you thought it was an information superhighway. Try a raging river 5754359Sroberto * filled with rapids and whirlpools that rip away your data and warp time. 5882498Sroberto * 5982498Sroberto * If HAVE_PPSAPI is defined code to use the PPSAPI will be compiled in. 6082498Sroberto * On startup if initialization of the PPSAPI fails, it will fall back 6182498Sroberto * to the "normal" timestamps. 6282498Sroberto * 6382498Sroberto * The PPSAPI part of the driver understands fudge flag2 and flag3. If 6482498Sroberto * flag2 is set, it will use the clear edge of the pulse. If flag3 is 6582498Sroberto * set, kernel hardpps is enabled. 6682498Sroberto * 6782498Sroberto * GPS sentences other than RMC (the default) may be enabled by setting 6882498Sroberto * the relevent bits of 'mode' in the server configuration line 6982498Sroberto * server 127.127.20.x mode X 7082498Sroberto * 7182498Sroberto * bit 0 - enables RMC (1) 7282498Sroberto * bit 1 - enables GGA (2) 7382498Sroberto * bit 2 - enables GLL (4) 74290001Sglebius * bit 3 - enables ZDA (8) - Standard Time & Date 75290001Sglebius * bit 3 - enables ZDG (8) - Accord GPS Clock's custom sentence with GPS time 76290001Sglebius * very close to standard ZDA 77290001Sglebius * 78290001Sglebius * Multiple sentences may be selected except when ZDG/ZDA is selected. 79290001Sglebius * 80290001Sglebius * bit 4/5/6 - selects the baudrate for serial port : 81290001Sglebius * 0 for 4800 (default) 82290001Sglebius * 1 for 9600 83290001Sglebius * 2 for 19200 84290001Sglebius * 3 for 38400 85290001Sglebius * 4 for 57600 86290001Sglebius * 5 for 115200 8754359Sroberto */ 88290001Sglebius#define NMEA_MESSAGE_MASK 0x0000FF0FU 89290001Sglebius#define NMEA_BAUDRATE_MASK 0x00000070U 90290001Sglebius#define NMEA_BAUDRATE_SHIFT 4 9154359Sroberto 92290001Sglebius#define NMEA_DELAYMEAS_MASK 0x80 93290001Sglebius#define NMEA_EXTLOG_MASK 0x00010000U 94290001Sglebius#define NMEA_DATETRUST_MASK 0x02000000U 95290001Sglebius 96290001Sglebius#define NMEA_PROTO_IDLEN 5 /* tag name must be at least 5 chars */ 97290001Sglebius#define NMEA_PROTO_MINLEN 6 /* min chars in sentence, excluding CS */ 98290001Sglebius#define NMEA_PROTO_MAXLEN 80 /* max chars in sentence, excluding CS */ 99290001Sglebius#define NMEA_PROTO_FIELDS 32 /* not official; limit on fields per record */ 100290001Sglebius 10154359Sroberto/* 102290001Sglebius * We check the timecode format and decode its contents. We only care 103290001Sglebius * about a few of them, the most important being the $GPRMC format: 104290001Sglebius * 105290001Sglebius * $GPRMC,hhmmss,a,fddmm.xx,n,dddmmm.xx,w,zz.z,yyy.,ddmmyy,dd,v*CC 106290001Sglebius * 107290001Sglebius * mode (0,1,2,3) selects sentence ANY/ALL, RMC, GGA, GLL, ZDA 108290001Sglebius * $GPGLL,3513.8385,S,14900.7851,E,232420.594,A*21 109290001Sglebius * $GPGGA,232420.59,3513.8385,S,14900.7851,E,1,05,3.4,00519,M,,,,*3F 110290001Sglebius * $GPRMC,232418.19,A,3513.8386,S,14900.7853,E,00.0,000.0,121199,12.,E*77 111290001Sglebius * 112290001Sglebius * Defining GPZDA to support Standard Time & Date 113290001Sglebius * sentence. The sentence has the following format 114290001Sglebius * 115290001Sglebius * $--ZDA,HHMMSS.SS,DD,MM,YYYY,TH,TM,*CS<CR><LF> 116290001Sglebius * 117290001Sglebius * Apart from the familiar fields, 118290001Sglebius * 'TH' Time zone Hours 119290001Sglebius * 'TM' Time zone Minutes 120290001Sglebius * 121290001Sglebius * Defining GPZDG to support Accord GPS Clock's custom NMEA 122290001Sglebius * sentence. The sentence has the following format 123290001Sglebius * 124290001Sglebius * $GPZDG,HHMMSS.S,DD,MM,YYYY,AA.BB,V*CS<CR><LF> 125290001Sglebius * 126290001Sglebius * It contains the GPS timestamp valid for next PPS pulse. 127290001Sglebius * Apart from the familiar fields, 128290001Sglebius * 'AA.BB' denotes the signal strength( should be < 05.00 ) 129290001Sglebius * 'V' denotes the GPS sync status : 130290001Sglebius * '0' indicates INVALID time, 131290001Sglebius * '1' indicates accuracy of +/-20 ms 132290001Sglebius * '2' indicates accuracy of +/-100 ns 133290001Sglebius * 134290001Sglebius * Defining PGRMF for Garmin GPS Fix Data 135290001Sglebius * $PGRMF,WN,WS,DATE,TIME,LS,LAT,LAT_DIR,LON,LON_DIR,MODE,FIX,SPD,DIR,PDOP,TDOP 136290001Sglebius * WN -- GPS week number (weeks since 1980-01-06, mod 1024) 137290001Sglebius * WS -- GPS seconds in week 138290001Sglebius * LS -- GPS leap seconds, accumulated ( UTC + LS == GPS ) 139290001Sglebius * FIX -- Fix type: 0=nofix, 1=2D, 2=3D 140290001Sglebius * DATE/TIME are standard date/time strings in UTC time scale 141290001Sglebius * 142290001Sglebius * The GPS time can be used to get the full century for the truncated 143290001Sglebius * date spec. 144290001Sglebius */ 145290001Sglebius 146290001Sglebius/* 14754359Sroberto * Definitions 14854359Sroberto */ 149290001Sglebius#define DEVICE "/dev/gps%d" /* GPS serial device */ 150290001Sglebius#define PPSDEV "/dev/gpspps%d" /* PPSAPI device override */ 15154359Sroberto#define SPEED232 B4800 /* uart speed (4800 bps) */ 15254359Sroberto#define PRECISION (-9) /* precision assumed (about 2 ms) */ 15382498Sroberto#define PPS_PRECISION (-20) /* precision assumed (about 1 us) */ 15454359Sroberto#define REFID "GPS\0" /* reference id */ 15554359Sroberto#define DESCRIPTION "NMEA GPS Clock" /* who we are */ 156290001Sglebius#ifndef O_NOCTTY 157290001Sglebius#define M_NOCTTY 0 158290001Sglebius#else 159290001Sglebius#define M_NOCTTY O_NOCTTY 160290001Sglebius#endif 161290001Sglebius#ifndef O_NONBLOCK 162290001Sglebius#define M_NONBLOCK 0 163290001Sglebius#else 164290001Sglebius#define M_NONBLOCK O_NONBLOCK 165290001Sglebius#endif 166290001Sglebius#define PPSOPENMODE (O_RDWR | M_NOCTTY | M_NONBLOCK) 16754359Sroberto 168290001Sglebius/* NMEA sentence array indexes for those we use */ 169290001Sglebius#define NMEA_GPRMC 0 /* recommended min. nav. */ 170290001Sglebius#define NMEA_GPGGA 1 /* fix and quality */ 171290001Sglebius#define NMEA_GPGLL 2 /* geo. lat/long */ 172290001Sglebius#define NMEA_GPZDA 3 /* date/time */ 173290001Sglebius/* 174290001Sglebius * $GPZDG is a proprietary sentence that violates the spec, by not 175290001Sglebius * using $P and an assigned company identifier to prefix the sentence 176290001Sglebius * identifier. When used with this driver, the system needs to be 177290001Sglebius * isolated from other NTP networks, as it operates in GPS time, not 178290001Sglebius * UTC as is much more common. GPS time is >15 seconds different from 179290001Sglebius * UTC due to not respecting leap seconds since 1970 or so. Other 180290001Sglebius * than the different timebase, $GPZDG is similar to $GPZDA. 181290001Sglebius */ 182290001Sglebius#define NMEA_GPZDG 4 183290001Sglebius#define NMEA_PGRMF 5 184290001Sglebius#define NMEA_ARRAY_SIZE (NMEA_PGRMF + 1) 18554359Sroberto 18654359Sroberto/* 187290001Sglebius * Sentence selection mode bits 18854359Sroberto */ 189290001Sglebius#define USE_GPRMC 0x00000001u 190290001Sglebius#define USE_GPGGA 0x00000002u 191290001Sglebius#define USE_GPGLL 0x00000004u 192290001Sglebius#define USE_GPZDA 0x00000008u 193290001Sglebius#define USE_PGRMF 0x00000100u 19454359Sroberto 195290001Sglebius/* mapping from sentence index to controlling mode bit */ 196290001Sglebiusstatic const u_int32 sentence_mode[NMEA_ARRAY_SIZE] = 197290001Sglebius{ 198290001Sglebius USE_GPRMC, 199290001Sglebius USE_GPGGA, 200290001Sglebius USE_GPGLL, 201290001Sglebius USE_GPZDA, 202290001Sglebius USE_GPZDA, 203290001Sglebius USE_PGRMF 204290001Sglebius}; 205290001Sglebius 206290001Sglebius/* date formats we support */ 207290001Sglebiusenum date_fmt { 208290001Sglebius DATE_1_DDMMYY, /* use 1 field with 2-digit year */ 209290001Sglebius DATE_3_DDMMYYYY /* use 3 fields with 4-digit year */ 210290001Sglebius}; 211290001Sglebius 212290001Sglebius/* results for 'field_init()' 213290001Sglebius * 214290001Sglebius * Note: If a checksum is present, the checksum test must pass OK or the 215290001Sglebius * sentence is tagged invalid. 216290001Sglebius */ 217290001Sglebius#define CHECK_EMPTY -1 /* no data */ 218290001Sglebius#define CHECK_INVALID 0 /* not a valid NMEA sentence */ 219290001Sglebius#define CHECK_VALID 1 /* valid but without checksum */ 220290001Sglebius#define CHECK_CSVALID 2 /* valid with checksum OK */ 221290001Sglebius 22254359Sroberto/* 22354359Sroberto * Unit control structure 22454359Sroberto */ 225290001Sglebiustypedef struct { 22682498Sroberto#ifdef HAVE_PPSAPI 227290001Sglebius struct refclock_atom atom; /* PPSAPI structure */ 228290001Sglebius int ppsapi_fd; /* fd used with PPSAPI */ 229290001Sglebius u_char ppsapi_tried; /* attempt PPSAPI once */ 230290001Sglebius u_char ppsapi_lit; /* time_pps_create() worked */ 231290001Sglebius u_char ppsapi_gate; /* system is on PPS */ 23282498Sroberto#endif /* HAVE_PPSAPI */ 233290001Sglebius u_char gps_time; /* use GPS time, not UTC */ 234290001Sglebius u_short century_cache; /* cached current century */ 235290001Sglebius l_fp last_reftime; /* last processed reference stamp */ 236290001Sglebius short epoch_warp; /* last epoch warp, for logging */ 237290001Sglebius /* tally stats, reset each poll cycle */ 238290001Sglebius struct 239290001Sglebius { 240290001Sglebius u_int total; 241290001Sglebius u_int accepted; 242290001Sglebius u_int rejected; /* GPS said not enough signal */ 243290001Sglebius u_int malformed; /* Bad checksum, invalid date or time */ 244290001Sglebius u_int filtered; /* mode bits, not GPZDG, same second */ 245290001Sglebius u_int pps_used; 246290001Sglebius } 247290001Sglebius tally; 248290001Sglebius /* per sentence checksum seen flag */ 249290001Sglebius u_char cksum_type[NMEA_ARRAY_SIZE]; 250290001Sglebius} nmea_unit; 25154359Sroberto 25254359Sroberto/* 253290001Sglebius * helper for faster field access 254290001Sglebius */ 255290001Sglebiustypedef struct { 256290001Sglebius char *base; /* buffer base */ 257290001Sglebius char *cptr; /* current field ptr */ 258290001Sglebius int blen; /* buffer length */ 259290001Sglebius int cidx; /* current field index */ 260290001Sglebius} nmea_data; 261290001Sglebius 262290001Sglebius/* 263290001Sglebius * NMEA gps week/time information 264290001Sglebius * This record contains the number of weeks since 1980-01-06 modulo 265290001Sglebius * 1024, the seconds elapsed since start of the week, and the number of 266290001Sglebius * leap seconds that are the difference between GPS and UTC time scale. 267290001Sglebius */ 268290001Sglebiustypedef struct { 269290001Sglebius u_int32 wt_time; /* seconds since weekstart */ 270290001Sglebius u_short wt_week; /* week number */ 271290001Sglebius short wt_leap; /* leap seconds */ 272290001Sglebius} gps_weektm; 273290001Sglebius 274290001Sglebius/* 275290001Sglebius * The GPS week time scale starts on Sunday, 1980-01-06. We need the 276290001Sglebius * rata die number of this day. 277290001Sglebius */ 278290001Sglebius#ifndef DAY_GPS_STARTS 279290001Sglebius#define DAY_GPS_STARTS 722820 280290001Sglebius#endif 281290001Sglebius 282290001Sglebius/* 28354359Sroberto * Function prototypes 28454359Sroberto */ 285290001Sglebiusstatic void nmea_init (void); 286290001Sglebiusstatic int nmea_start (int, struct peer *); 287290001Sglebiusstatic void nmea_shutdown (int, struct peer *); 288290001Sglebiusstatic void nmea_receive (struct recvbuf *); 289290001Sglebiusstatic void nmea_poll (int, struct peer *); 29082498Sroberto#ifdef HAVE_PPSAPI 291290001Sglebiusstatic void nmea_control (int, const struct refclockstat *, 292290001Sglebius struct refclockstat *, struct peer *); 293290001Sglebius#define NMEA_CONTROL nmea_control 294290001Sglebius#else 295290001Sglebius#define NMEA_CONTROL noentry 29682498Sroberto#endif /* HAVE_PPSAPI */ 297290001Sglebiusstatic void nmea_timer (int, struct peer *); 29854359Sroberto 299290001Sglebius/* parsing helpers */ 300290001Sglebiusstatic int field_init (nmea_data * data, char * cp, int len); 301290001Sglebiusstatic char * field_parse (nmea_data * data, int fn); 302290001Sglebiusstatic void field_wipe (nmea_data * data, ...); 303290001Sglebiusstatic u_char parse_qual (nmea_data * data, int idx, 304290001Sglebius char tag, int inv); 305290001Sglebiusstatic int parse_time (struct calendar * jd, long * nsec, 306290001Sglebius nmea_data *, int idx); 307290001Sglebiusstatic int parse_date (struct calendar *jd, nmea_data*, 308290001Sglebius int idx, enum date_fmt fmt); 309290001Sglebiusstatic int parse_weekdata (gps_weektm *, nmea_data *, 310290001Sglebius int weekidx, int timeidx, int leapidx); 311290001Sglebius/* calendar / date helpers */ 312290001Sglebiusstatic int unfold_day (struct calendar * jd, u_int32 rec_ui); 313290001Sglebiusstatic int unfold_century (struct calendar * jd, u_int32 rec_ui); 314290001Sglebiusstatic int gpsfix_century (struct calendar * jd, const gps_weektm * wd, 315290001Sglebius u_short * ccentury); 316290001Sglebiusstatic l_fp eval_gps_time (struct peer * peer, const struct calendar * gpst, 317290001Sglebius const struct timespec * gpso, const l_fp * xrecv); 318290001Sglebius 319290001Sglebiusstatic int nmead_open (const char * device); 320290001Sglebiusstatic void save_ltc (struct refclockproc * const, const char * const, 321290001Sglebius size_t); 322290001Sglebius 32354359Sroberto/* 324290001Sglebius * If we want the driver to ouput sentences, too: re-enable the send 325290001Sglebius * support functions by defining NMEA_WRITE_SUPPORT to non-zero... 326290001Sglebius */ 327290001Sglebius#if NMEA_WRITE_SUPPORT 328290001Sglebius 329290001Sglebiusstatic void gps_send(int, const char *, struct peer *); 330290001Sglebius# ifdef SYS_WINNT 331290001Sglebius# undef write /* ports/winnt/include/config.h: #define write _write */ 332290001Sglebiusextern int async_write(int, const void *, unsigned int); 333290001Sglebius# define write(fd, data, octets) async_write(fd, data, octets) 334290001Sglebius# endif /* SYS_WINNT */ 335290001Sglebius 336290001Sglebius#endif /* NMEA_WRITE_SUPPORT */ 337290001Sglebius 338290001Sglebiusstatic int32_t g_gpsMinBase; 339290001Sglebiusstatic int32_t g_gpsMinYear; 340290001Sglebius 341290001Sglebius/* 342290001Sglebius * ------------------------------------------------------------------- 34354359Sroberto * Transfer vector 344290001Sglebius * ------------------------------------------------------------------- 34554359Sroberto */ 346290001Sglebiusstruct refclock refclock_nmea = { 34754359Sroberto nmea_start, /* start up driver */ 348290001Sglebius nmea_shutdown, /* shut down driver */ 34954359Sroberto nmea_poll, /* transmit poll message */ 350290001Sglebius NMEA_CONTROL, /* fudge control */ 351290001Sglebius nmea_init, /* initialize driver */ 35254359Sroberto noentry, /* buginfo */ 353290001Sglebius nmea_timer /* called once per second */ 35454359Sroberto}; 35554359Sroberto 35654359Sroberto/* 357290001Sglebius * ------------------------------------------------------------------- 358290001Sglebius * nmea_init - initialise data 359290001Sglebius * 360290001Sglebius * calculates a few runtime constants that cannot be made compile time 361290001Sglebius * constants. 362290001Sglebius * ------------------------------------------------------------------- 363290001Sglebius */ 364290001Sglebiusstatic void 365290001Sglebiusnmea_init(void) 366290001Sglebius{ 367290001Sglebius struct calendar date; 368290001Sglebius 369290001Sglebius /* - calculate min. base value for GPS epoch & century unfolding 370290001Sglebius * This assumes that the build system was roughly in sync with 371290001Sglebius * the world, and that really synchronising to a time before the 372290001Sglebius * program was created would be unsafe or insane. If the build 373290001Sglebius * date cannot be stablished, at least use the start of GPS 374290001Sglebius * (1980-01-06) as minimum, because GPS can surely NOT 375290001Sglebius * synchronise beyond it's own big bang. We add a little safety 376290001Sglebius * margin for the fuzziness of the build date, which is in an 377290001Sglebius * undefined time zone. */ 378290001Sglebius if (ntpcal_get_build_date(&date)) 379290001Sglebius g_gpsMinBase = ntpcal_date_to_rd(&date) - 2; 380290001Sglebius else 381290001Sglebius g_gpsMinBase = 0; 382290001Sglebius 383290001Sglebius if (g_gpsMinBase < DAY_GPS_STARTS) 384290001Sglebius g_gpsMinBase = DAY_GPS_STARTS; 385290001Sglebius 386290001Sglebius ntpcal_rd_to_date(&date, g_gpsMinBase); 387290001Sglebius g_gpsMinYear = date.year; 388290001Sglebius g_gpsMinBase -= DAY_NTP_STARTS; 389290001Sglebius} 390290001Sglebius 391290001Sglebius/* 392290001Sglebius * ------------------------------------------------------------------- 39354359Sroberto * nmea_start - open the GPS devices and initialize data for processing 394290001Sglebius * 395290001Sglebius * return 0 on error, 1 on success. Even on error the peer structures 396290001Sglebius * must be in a state that permits 'nmea_shutdown()' to clean up all 397290001Sglebius * resources, because it will be called immediately to do so. 398290001Sglebius * ------------------------------------------------------------------- 39954359Sroberto */ 40054359Srobertostatic int 40154359Srobertonmea_start( 402290001Sglebius int unit, 403290001Sglebius struct peer * peer 40454359Sroberto ) 40554359Sroberto{ 406290001Sglebius struct refclockproc * const pp = peer->procptr; 407290001Sglebius nmea_unit * const up = emalloc_zero(sizeof(*up)); 408290001Sglebius char device[20]; 409290001Sglebius size_t devlen; 410290001Sglebius u_int32 rate; 411290001Sglebius int baudrate; 412290001Sglebius const char * baudtext; 41354359Sroberto 41456746Sroberto 415290001Sglebius /* Get baudrate choice from mode byte bits 4/5/6 */ 416290001Sglebius rate = (peer->ttl & NMEA_BAUDRATE_MASK) >> NMEA_BAUDRATE_SHIFT; 41754359Sroberto 418290001Sglebius switch (rate) { 419290001Sglebius case 0: 420290001Sglebius baudrate = SPEED232; 421290001Sglebius baudtext = "4800"; 422290001Sglebius break; 423290001Sglebius case 1: 424290001Sglebius baudrate = B9600; 425290001Sglebius baudtext = "9600"; 426290001Sglebius break; 427290001Sglebius case 2: 428290001Sglebius baudrate = B19200; 429290001Sglebius baudtext = "19200"; 430290001Sglebius break; 431290001Sglebius case 3: 432290001Sglebius baudrate = B38400; 433290001Sglebius baudtext = "38400"; 434290001Sglebius break; 435290001Sglebius#ifdef B57600 436290001Sglebius case 4: 437290001Sglebius baudrate = B57600; 438290001Sglebius baudtext = "57600"; 439290001Sglebius break; 440182007Sroberto#endif 441290001Sglebius#ifdef B115200 442290001Sglebius case 5: 443290001Sglebius baudrate = B115200; 444290001Sglebius baudtext = "115200"; 445290001Sglebius break; 446290001Sglebius#endif 447290001Sglebius default: 448290001Sglebius baudrate = SPEED232; 449290001Sglebius baudtext = "4800 (fallback)"; 450290001Sglebius break; 451290001Sglebius } 452182007Sroberto 453290001Sglebius /* Allocate and initialize unit structure */ 454290001Sglebius pp->unitptr = (caddr_t)up; 455290001Sglebius pp->io.fd = -1; 45654359Sroberto pp->io.clock_recv = nmea_receive; 457290001Sglebius pp->io.srcclock = peer; 45854359Sroberto pp->io.datalen = 0; 459290001Sglebius /* force change detection on first valid message */ 460290001Sglebius memset(&up->last_reftime, 0xFF, sizeof(up->last_reftime)); 461290001Sglebius /* force checksum on GPRMC, see below */ 462290001Sglebius up->cksum_type[NMEA_GPRMC] = CHECK_CSVALID; 463290001Sglebius#ifdef HAVE_PPSAPI 464290001Sglebius up->ppsapi_fd = -1; 465290001Sglebius#endif 466290001Sglebius ZERO(up->tally); 46754359Sroberto 468290001Sglebius /* Initialize miscellaneous variables */ 46982498Sroberto peer->precision = PRECISION; 47054359Sroberto pp->clockdesc = DESCRIPTION; 471290001Sglebius memcpy(&pp->refid, REFID, 4); 47254359Sroberto 473290001Sglebius /* Open serial port. Use CLK line discipline, if available. */ 474290001Sglebius devlen = snprintf(device, sizeof(device), DEVICE, unit); 475290001Sglebius if (devlen >= sizeof(device)) { 476290001Sglebius msyslog(LOG_ERR, "%s clock device name too long", 477290001Sglebius refnumtoa(&peer->srcadr)); 478290001Sglebius return FALSE; /* buffer overflow */ 47982498Sroberto } 480290001Sglebius pp->io.fd = refclock_open(device, baudrate, LDISC_CLK); 481290001Sglebius if (0 >= pp->io.fd) { 482290001Sglebius pp->io.fd = nmead_open(device); 483290001Sglebius if (-1 == pp->io.fd) 484290001Sglebius return FALSE; 485290001Sglebius } 486290001Sglebius LOGIF(CLOCKINFO, (LOG_NOTICE, "%s serial %s open at %s bps", 487290001Sglebius refnumtoa(&peer->srcadr), device, baudtext)); 488290001Sglebius 489290001Sglebius /* succeed if this clock can be added */ 490290001Sglebius return io_addclock(&pp->io) != 0; 49154359Sroberto} 49254359Sroberto 493290001Sglebius 49454359Sroberto/* 495290001Sglebius * ------------------------------------------------------------------- 49654359Sroberto * nmea_shutdown - shut down a GPS clock 497290001Sglebius * 498290001Sglebius * NOTE this routine is called after nmea_start() returns failure, 499290001Sglebius * as well as during a normal shutdown due to ntpq :config unpeer. 500290001Sglebius * ------------------------------------------------------------------- 50154359Sroberto */ 50254359Srobertostatic void 50354359Srobertonmea_shutdown( 504290001Sglebius int unit, 505290001Sglebius struct peer * peer 50654359Sroberto ) 50754359Sroberto{ 508290001Sglebius struct refclockproc * const pp = peer->procptr; 509290001Sglebius nmea_unit * const up = (nmea_unit *)pp->unitptr; 51054359Sroberto 511290001Sglebius UNUSED_ARG(unit); 512290001Sglebius 513290001Sglebius if (up != NULL) { 51482498Sroberto#ifdef HAVE_PPSAPI 515290001Sglebius if (up->ppsapi_lit) 516290001Sglebius time_pps_destroy(up->atom.handle); 517290001Sglebius if (up->ppsapi_tried && up->ppsapi_fd != pp->io.fd) 518290001Sglebius close(up->ppsapi_fd); 519290001Sglebius#endif 520290001Sglebius free(up); 521290001Sglebius } 522290001Sglebius pp->unitptr = (caddr_t)NULL; 523290001Sglebius if (-1 != pp->io.fd) 524290001Sglebius io_closeclock(&pp->io); 525290001Sglebius pp->io.fd = -1; 52654359Sroberto} 52754359Sroberto 52854359Sroberto/* 529290001Sglebius * ------------------------------------------------------------------- 530290001Sglebius * nmea_control - configure fudge params 531290001Sglebius * ------------------------------------------------------------------- 53282498Sroberto */ 533290001Sglebius#ifdef HAVE_PPSAPI 53482498Srobertostatic void 53582498Srobertonmea_control( 536290001Sglebius int unit, 537290001Sglebius const struct refclockstat * in_st, 538290001Sglebius struct refclockstat * out_st, 539290001Sglebius struct peer * peer 54082498Sroberto ) 54182498Sroberto{ 542290001Sglebius struct refclockproc * const pp = peer->procptr; 543290001Sglebius nmea_unit * const up = (nmea_unit *)pp->unitptr; 54482498Sroberto 545290001Sglebius char device[32]; 546290001Sglebius size_t devlen; 547290001Sglebius 548290001Sglebius UNUSED_ARG(in_st); 549290001Sglebius UNUSED_ARG(out_st); 550290001Sglebius 551290001Sglebius /* 552290001Sglebius * PPS control 553290001Sglebius * 554290001Sglebius * If /dev/gpspps$UNIT can be opened that will be used for 555290001Sglebius * PPSAPI. Otherwise, the GPS serial device /dev/gps$UNIT 556290001Sglebius * already opened is used for PPSAPI as well. (This might not 557290001Sglebius * work, in which case the PPS API remains unavailable...) 558290001Sglebius */ 559290001Sglebius 560290001Sglebius /* Light up the PPSAPI interface if not yet attempted. */ 561290001Sglebius if ((CLK_FLAG1 & pp->sloppyclockflag) && !up->ppsapi_tried) { 562290001Sglebius up->ppsapi_tried = TRUE; 563290001Sglebius devlen = snprintf(device, sizeof(device), PPSDEV, unit); 564290001Sglebius if (devlen < sizeof(device)) { 565290001Sglebius up->ppsapi_fd = open(device, PPSOPENMODE, 566290001Sglebius S_IRUSR | S_IWUSR); 567290001Sglebius } else { 568290001Sglebius up->ppsapi_fd = -1; 569290001Sglebius msyslog(LOG_ERR, "%s PPS device name too long", 570290001Sglebius refnumtoa(&peer->srcadr)); 571290001Sglebius } 572290001Sglebius if (-1 == up->ppsapi_fd) 573290001Sglebius up->ppsapi_fd = pp->io.fd; 574290001Sglebius if (refclock_ppsapi(up->ppsapi_fd, &up->atom)) { 575290001Sglebius /* use the PPS API for our own purposes now. */ 576290001Sglebius up->ppsapi_lit = refclock_params( 577290001Sglebius pp->sloppyclockflag, &up->atom); 578290001Sglebius if (!up->ppsapi_lit) { 579290001Sglebius /* failed to configure, drop PPS unit */ 580290001Sglebius time_pps_destroy(up->atom.handle); 581290001Sglebius msyslog(LOG_WARNING, 582290001Sglebius "%s set PPSAPI params fails", 583290001Sglebius refnumtoa(&peer->srcadr)); 584290001Sglebius } 585290001Sglebius /* note: the PPS I/O handle remains valid until 586290001Sglebius * flag1 is cleared or the clock is shut down. 587290001Sglebius */ 588290001Sglebius } else { 589290001Sglebius msyslog(LOG_WARNING, 590290001Sglebius "%s flag1 1 but PPSAPI fails", 591290001Sglebius refnumtoa(&peer->srcadr)); 592290001Sglebius } 593290001Sglebius } 594290001Sglebius 595290001Sglebius /* shut down PPS API if activated */ 596290001Sglebius if (!(CLK_FLAG1 & pp->sloppyclockflag) && up->ppsapi_tried) { 597290001Sglebius /* shutdown PPS API */ 598290001Sglebius if (up->ppsapi_lit) 599290001Sglebius time_pps_destroy(up->atom.handle); 600290001Sglebius up->atom.handle = 0; 601290001Sglebius /* close/drop PPS fd */ 602290001Sglebius if (up->ppsapi_fd != pp->io.fd) 603290001Sglebius close(up->ppsapi_fd); 604290001Sglebius up->ppsapi_fd = -1; 605290001Sglebius 606290001Sglebius /* clear markers and peer items */ 607290001Sglebius up->ppsapi_gate = FALSE; 608290001Sglebius up->ppsapi_lit = FALSE; 609290001Sglebius up->ppsapi_tried = FALSE; 610290001Sglebius 611290001Sglebius peer->flags &= ~FLAG_PPS; 612290001Sglebius peer->precision = PRECISION; 613290001Sglebius } 61482498Sroberto} 615290001Sglebius#endif /* HAVE_PPSAPI */ 61682498Sroberto 61782498Sroberto/* 618290001Sglebius * ------------------------------------------------------------------- 619290001Sglebius * nmea_timer - called once per second 620290001Sglebius * this only polls (older?) Oncore devices now 621290001Sglebius * 622290001Sglebius * Usually 'nmea_receive()' can get a timestamp every second, but at 623290001Sglebius * least one Motorola unit needs prompting each time. Doing so in 624290001Sglebius * 'nmea_poll()' gives only one sample per poll cycle, which actually 625290001Sglebius * defeats the purpose of the median filter. Polling once per second 626290001Sglebius * seems a much better idea. 627290001Sglebius * ------------------------------------------------------------------- 62882498Sroberto */ 629290001Sglebiusstatic void 630290001Sglebiusnmea_timer( 631290001Sglebius int unit, 632290001Sglebius struct peer * peer 63382498Sroberto ) 63482498Sroberto{ 635290001Sglebius#if NMEA_WRITE_SUPPORT 636290001Sglebius 637290001Sglebius struct refclockproc * const pp = peer->procptr; 63882498Sroberto 639290001Sglebius UNUSED_ARG(unit); 64082498Sroberto 641290001Sglebius if (-1 != pp->io.fd) /* any mode bits to evaluate here? */ 642290001Sglebius gps_send(pp->io.fd, "$PMOTG,RMC,0000*1D\r\n", peer); 643290001Sglebius#else 644290001Sglebius 645290001Sglebius UNUSED_ARG(unit); 646290001Sglebius UNUSED_ARG(peer); 647290001Sglebius 648290001Sglebius#endif /* NMEA_WRITE_SUPPORT */ 64982498Sroberto} 65082498Sroberto 651290001Sglebius#ifdef HAVE_PPSAPI 65282498Sroberto/* 653290001Sglebius * ------------------------------------------------------------------- 654290001Sglebius * refclock_ppsrelate(...) -- correlate with PPS edge 65582498Sroberto * 656290001Sglebius * This function is used to correlate a receive time stamp and a 657290001Sglebius * reference time with a PPS edge time stamp. It applies the necessary 658290001Sglebius * fudges (fudge1 for PPS, fudge2 for receive time) and then tries to 659290001Sglebius * move the receive time stamp to the corresponding edge. This can warp 660290001Sglebius * into future, if a transmission delay of more than 500ms is not 661290001Sglebius * compensated with a corresponding fudge time2 value, because then the 662290001Sglebius * next PPS edge is nearer than the last. (Similiar to what the PPS ATOM 663290001Sglebius * driver does, but we deal with full time stamps here, not just phase 664290001Sglebius * shift information.) Likewise, a negative fudge time2 value must be 665290001Sglebius * used if the reference time stamp correlates with the *following* PPS 666290001Sglebius * pulse. 667290001Sglebius * 668290001Sglebius * Note that the receive time fudge value only needs to move the receive 669290001Sglebius * stamp near a PPS edge but that close proximity is not required; 670290001Sglebius * +/-100ms precision should be enough. But since the fudge value will 671290001Sglebius * probably also be used to compensate the transmission delay when no 672290001Sglebius * PPS edge can be related to the time stamp, it's best to get it as 673290001Sglebius * close as possible. 674290001Sglebius * 675290001Sglebius * It should also be noted that the typical use case is matching to the 676290001Sglebius * preceeding edge, as most units relate their sentences to the current 677290001Sglebius * second. 678290001Sglebius * 679290001Sglebius * The function returns PPS_RELATE_NONE (0) if no PPS edge correlation 680290001Sglebius * can be fixed; PPS_RELATE_EDGE (1) when a PPS edge could be fixed, but 681290001Sglebius * the distance to the reference time stamp is too big (exceeds 682290001Sglebius * +/-400ms) and the ATOM driver PLL cannot be used to fix the phase; 683290001Sglebius * and PPS_RELATE_PHASE (2) when the ATOM driver PLL code can be used. 684290001Sglebius * 685290001Sglebius * On output, the receive time stamp is replaced with the corresponding 686290001Sglebius * PPS edge time if a fix could be made; the PPS fudge is updated to 687290001Sglebius * reflect the proper fudge time to apply. (This implies that 688290001Sglebius * 'refclock_process_offset()' must be used!) 689290001Sglebius * ------------------------------------------------------------------- 69082498Sroberto */ 691290001Sglebius#define PPS_RELATE_NONE 0 /* no pps correlation possible */ 692290001Sglebius#define PPS_RELATE_EDGE 1 /* recv time fixed, no phase lock */ 693290001Sglebius#define PPS_RELATE_PHASE 2 /* recv time fixed, phase lock ok */ 694290001Sglebius 69582498Srobertostatic int 696290001Sglebiusrefclock_ppsrelate( 697290001Sglebius const struct refclockproc * pp , /* for sanity */ 698290001Sglebius const struct refclock_atom * ap , /* for PPS io */ 699290001Sglebius const l_fp * reftime , 700290001Sglebius l_fp * rd_stamp, /* i/o read stamp */ 701290001Sglebius double pp_fudge, /* pps fudge */ 702290001Sglebius double * rd_fudge /* i/o read fudge */ 70382498Sroberto ) 70482498Sroberto{ 705290001Sglebius pps_info_t pps_info; 706290001Sglebius struct timespec timeout; 707290001Sglebius l_fp pp_stamp, pp_delta; 708290001Sglebius double delta, idelta; 70982498Sroberto 710290001Sglebius if (pp->leap == LEAP_NOTINSYNC) 711290001Sglebius return PPS_RELATE_NONE; /* clock is insane, no chance */ 71282498Sroberto 713290001Sglebius ZERO(timeout); 714290001Sglebius ZERO(pps_info); 715290001Sglebius if (time_pps_fetch(ap->handle, PPS_TSFMT_TSPEC, 716290001Sglebius &pps_info, &timeout) < 0) 717290001Sglebius return PPS_RELATE_NONE; /* can't get time stamps */ 718290001Sglebius 719290001Sglebius /* get last active PPS edge before receive */ 720290001Sglebius if (ap->pps_params.mode & PPS_CAPTUREASSERT) 721290001Sglebius timeout = pps_info.assert_timestamp; 722290001Sglebius else if (ap->pps_params.mode & PPS_CAPTURECLEAR) 723290001Sglebius timeout = pps_info.clear_timestamp; 724290001Sglebius else 725290001Sglebius return PPS_RELATE_NONE; /* WHICH edge, please?!? */ 726290001Sglebius 727290001Sglebius /* get delta between receive time and PPS time */ 728290001Sglebius pp_stamp = tspec_stamp_to_lfp(timeout); 729290001Sglebius pp_delta = *rd_stamp; 730290001Sglebius L_SUB(&pp_delta, &pp_stamp); 731290001Sglebius LFPTOD(&pp_delta, delta); 732290001Sglebius delta += pp_fudge - *rd_fudge; 733290001Sglebius if (fabs(delta) > 1.5) 734290001Sglebius return PPS_RELATE_NONE; /* PPS timeout control */ 735290001Sglebius 736290001Sglebius /* eventually warp edges, check phase */ 737290001Sglebius idelta = floor(delta + 0.5); 738290001Sglebius pp_fudge -= idelta; 739290001Sglebius delta -= idelta; 740290001Sglebius if (fabs(delta) > 0.45) 741290001Sglebius return PPS_RELATE_NONE; /* dead band control */ 742290001Sglebius 743290001Sglebius /* we actually have a PPS edge to relate with! */ 744290001Sglebius *rd_stamp = pp_stamp; 745290001Sglebius *rd_fudge = pp_fudge; 746290001Sglebius 747290001Sglebius /* if whole system out-of-sync, do not try to PLL */ 748290001Sglebius if (sys_leap == LEAP_NOTINSYNC) 749290001Sglebius return PPS_RELATE_EDGE; /* cannot PLL with atom code */ 750290001Sglebius 751290001Sglebius /* check against reftime if ATOM PLL can be used */ 752290001Sglebius pp_delta = *reftime; 753290001Sglebius L_SUB(&pp_delta, &pp_stamp); 754290001Sglebius LFPTOD(&pp_delta, delta); 755290001Sglebius delta += pp_fudge; 756290001Sglebius if (fabs(delta) > 0.45) 757290001Sglebius return PPS_RELATE_EDGE; /* cannot PLL with atom code */ 758290001Sglebius 759290001Sglebius /* all checks passed, gets an AAA rating here! */ 760290001Sglebius return PPS_RELATE_PHASE; /* can PLL with atom code */ 76182498Sroberto} 762290001Sglebius#endif /* HAVE_PPSAPI */ 76382498Sroberto 76482498Sroberto/* 765290001Sglebius * ------------------------------------------------------------------- 76654359Sroberto * nmea_receive - receive data from the serial interface 767290001Sglebius * 768290001Sglebius * This is the workhorse for NMEA data evaluation: 769290001Sglebius * 770290001Sglebius * + it checks all NMEA data, and rejects sentences that are not valid 771290001Sglebius * NMEA sentences 772290001Sglebius * + it checks whether a sentence is known and to be used 773290001Sglebius * + it parses the time and date data from the NMEA data string and 774290001Sglebius * augments the missing bits. (century in dat, whole date, ...) 775290001Sglebius * + it rejects data that is not from the first accepted sentence in a 776290001Sglebius * burst 777290001Sglebius * + it eventually replaces the receive time with the PPS edge time. 778290001Sglebius * + it feeds the data to the internal processing stages. 779290001Sglebius * ------------------------------------------------------------------- 78054359Sroberto */ 78154359Srobertostatic void 78254359Srobertonmea_receive( 783290001Sglebius struct recvbuf * rbufp 78454359Sroberto ) 78554359Sroberto{ 786290001Sglebius /* declare & init control structure ptrs */ 787290001Sglebius struct peer * const peer = rbufp->recv_peer; 788290001Sglebius struct refclockproc * const pp = peer->procptr; 789290001Sglebius nmea_unit * const up = (nmea_unit*)pp->unitptr; 790290001Sglebius 79182498Sroberto /* Use these variables to hold data until we decide its worth keeping */ 792290001Sglebius nmea_data rdata; 793290001Sglebius char rd_lastcode[BMAX]; 794290001Sglebius l_fp rd_timestamp, rd_reftime; 795290001Sglebius int rd_lencode; 796290001Sglebius double rd_fudge; 79754359Sroberto 798290001Sglebius /* working stuff */ 799290001Sglebius struct calendar date; /* to keep & convert the time stamp */ 800290001Sglebius struct timespec tofs; /* offset to full-second reftime */ 801290001Sglebius gps_weektm gpsw; /* week time storage */ 802290001Sglebius /* results of sentence/date/time parsing */ 803290001Sglebius u_char sentence; /* sentence tag */ 804290001Sglebius int checkres; 805290001Sglebius char * cp; 806290001Sglebius int rc_date; 807290001Sglebius int rc_time; 80854359Sroberto 809290001Sglebius /* make sure data has defined pristine state */ 810290001Sglebius ZERO(tofs); 811290001Sglebius ZERO(date); 812290001Sglebius ZERO(gpsw); 813290001Sglebius sentence = 0; // Should never be needed. 814290001Sglebius rc_date = 0; // Should never be needed. 815290001Sglebius rc_time = 0; // Should never be needed. 816290001Sglebius 817290001Sglebius /* 818290001Sglebius * Read the timecode and timestamp, then initialise field 819290001Sglebius * processing. The <CR><LF> at the NMEA line end is translated 820290001Sglebius * to <LF><LF> by the terminal input routines on most systems, 821290001Sglebius * and this gives us one spurious empty read per record which we 822290001Sglebius * better ignore silently. 82354359Sroberto */ 824290001Sglebius rd_lencode = refclock_gtlin(rbufp, rd_lastcode, 825290001Sglebius sizeof(rd_lastcode), &rd_timestamp); 826290001Sglebius checkres = field_init(&rdata, rd_lastcode, rd_lencode); 827290001Sglebius switch (checkres) { 82854359Sroberto 829290001Sglebius case CHECK_INVALID: 830290001Sglebius DPRINTF(1, ("%s invalid data: '%s'\n", 831290001Sglebius refnumtoa(&peer->srcadr), rd_lastcode)); 832290001Sglebius refclock_report(peer, CEVNT_BADREPLY); 833290001Sglebius return; 83454359Sroberto 835290001Sglebius case CHECK_EMPTY: 836290001Sglebius return; 837290001Sglebius 838290001Sglebius default: 839290001Sglebius DPRINTF(1, ("%s gpsread: %d '%s'\n", 840290001Sglebius refnumtoa(&peer->srcadr), rd_lencode, 841290001Sglebius rd_lastcode)); 842290001Sglebius break; 843290001Sglebius } 844290001Sglebius up->tally.total++; 845290001Sglebius 846290001Sglebius /* 847290001Sglebius * --> below this point we have a valid NMEA sentence <-- 848290001Sglebius * 849290001Sglebius * Check sentence name. Skip first 2 chars (talker ID) in most 850290001Sglebius * cases, to allow for $GLGGA and $GPGGA etc. Since the name 851290001Sglebius * field has at least 5 chars we can simply shift the field 852290001Sglebius * start. 85354359Sroberto */ 854290001Sglebius cp = field_parse(&rdata, 0); 855290001Sglebius if (strncmp(cp + 2, "RMC,", 4) == 0) 856290001Sglebius sentence = NMEA_GPRMC; 857290001Sglebius else if (strncmp(cp + 2, "GGA,", 4) == 0) 858290001Sglebius sentence = NMEA_GPGGA; 859290001Sglebius else if (strncmp(cp + 2, "GLL,", 4) == 0) 860290001Sglebius sentence = NMEA_GPGLL; 861290001Sglebius else if (strncmp(cp + 2, "ZDA,", 4) == 0) 862290001Sglebius sentence = NMEA_GPZDA; 863290001Sglebius else if (strncmp(cp + 2, "ZDG,", 4) == 0) 864290001Sglebius sentence = NMEA_GPZDG; 865290001Sglebius else if (strncmp(cp, "PGRMF,", 6) == 0) 866290001Sglebius sentence = NMEA_PGRMF; 86754359Sroberto else 868290001Sglebius return; /* not something we know about */ 86954359Sroberto 870290001Sglebius /* Eventually output delay measurement now. */ 871290001Sglebius if (peer->ttl & NMEA_DELAYMEAS_MASK) { 872290001Sglebius mprintf_clock_stats(&peer->srcadr, "delay %0.6f %.*s", 873290001Sglebius ldexp(rd_timestamp.l_uf, -32), 874290001Sglebius (int)(strchr(rd_lastcode, ',') - rd_lastcode), 875290001Sglebius rd_lastcode); 876290001Sglebius } 877290001Sglebius 87882498Sroberto /* See if I want to process this message type */ 879290001Sglebius if ((peer->ttl & NMEA_MESSAGE_MASK) && 880290001Sglebius !(peer->ttl & sentence_mode[sentence])) { 881290001Sglebius up->tally.filtered++; 88282498Sroberto return; 883290001Sglebius } 88482498Sroberto 885290001Sglebius /* 886290001Sglebius * make sure it came in clean 887290001Sglebius * 888290001Sglebius * Apparently, older NMEA specifications (which are expensive) 889290001Sglebius * did not require the checksum for all sentences. $GPMRC is 890290001Sglebius * the only one so far identified which has always been required 891290001Sglebius * to include a checksum. 892290001Sglebius * 893290001Sglebius * Today, most NMEA GPS receivers checksum every sentence. To 894290001Sglebius * preserve its error-detection capabilities with modern GPSes 895290001Sglebius * while allowing operation without checksums on all but $GPMRC, 896290001Sglebius * we keep track of whether we've ever seen a valid checksum on 897290001Sglebius * a given sentence, and if so, reject future instances without 898290001Sglebius * checksum. ('up->cksum_type[NMEA_GPRMC]' is set in 899290001Sglebius * 'nmea_start()' to enforce checksums for $GPRMC right from the 900290001Sglebius * start.) 901290001Sglebius */ 902290001Sglebius if (up->cksum_type[sentence] <= (u_char)checkres) { 903290001Sglebius up->cksum_type[sentence] = (u_char)checkres; 904290001Sglebius } else { 905290001Sglebius DPRINTF(1, ("%s checksum missing: '%s'\n", 906290001Sglebius refnumtoa(&peer->srcadr), rd_lastcode)); 907290001Sglebius refclock_report(peer, CEVNT_BADREPLY); 908290001Sglebius up->tally.malformed++; 909290001Sglebius return; 910290001Sglebius } 91182498Sroberto 912290001Sglebius /* 913290001Sglebius * $GPZDG provides GPS time not UTC, and the two mix poorly. 914290001Sglebius * Once have processed a $GPZDG, do not process any further UTC 915290001Sglebius * sentences (all but $GPZDG currently). 916290001Sglebius */ 917290001Sglebius if (up->gps_time && NMEA_GPZDG != sentence) { 918290001Sglebius up->tally.filtered++; 919290001Sglebius return; 920290001Sglebius } 92182498Sroberto 922290001Sglebius DPRINTF(1, ("%s processing %d bytes, timecode '%s'\n", 923290001Sglebius refnumtoa(&peer->srcadr), rd_lencode, rd_lastcode)); 92482498Sroberto 925290001Sglebius /* 926290001Sglebius * Grab fields depending on clock string type and possibly wipe 927290001Sglebius * sensitive data from the last timecode. 928290001Sglebius */ 929290001Sglebius switch (sentence) { 93082498Sroberto 931290001Sglebius case NMEA_GPRMC: 932290001Sglebius /* Check quality byte, fetch data & time */ 933290001Sglebius rc_time = parse_time(&date, &tofs.tv_nsec, &rdata, 1); 934290001Sglebius pp->leap = parse_qual(&rdata, 2, 'A', 0); 935290001Sglebius rc_date = parse_date(&date, &rdata, 9, DATE_1_DDMMYY) 936290001Sglebius && unfold_century(&date, rd_timestamp.l_ui); 937290001Sglebius if (CLK_FLAG4 & pp->sloppyclockflag) 938290001Sglebius field_wipe(&rdata, 3, 4, 5, 6, -1); 939290001Sglebius break; 94082498Sroberto 941290001Sglebius case NMEA_GPGGA: 942290001Sglebius /* Check quality byte, fetch time only */ 943290001Sglebius rc_time = parse_time(&date, &tofs.tv_nsec, &rdata, 1); 944290001Sglebius pp->leap = parse_qual(&rdata, 6, '0', 1); 945290001Sglebius rc_date = unfold_day(&date, rd_timestamp.l_ui); 946290001Sglebius if (CLK_FLAG4 & pp->sloppyclockflag) 947290001Sglebius field_wipe(&rdata, 2, 4, -1); 94882498Sroberto break; 94982498Sroberto 950290001Sglebius case NMEA_GPGLL: 951290001Sglebius /* Check quality byte, fetch time only */ 952290001Sglebius rc_time = parse_time(&date, &tofs.tv_nsec, &rdata, 5); 953290001Sglebius pp->leap = parse_qual(&rdata, 6, 'A', 0); 954290001Sglebius rc_date = unfold_day(&date, rd_timestamp.l_ui); 955290001Sglebius if (CLK_FLAG4 & pp->sloppyclockflag) 956290001Sglebius field_wipe(&rdata, 1, 3, -1); 957290001Sglebius break; 958290001Sglebius 959290001Sglebius case NMEA_GPZDA: 960290001Sglebius /* No quality. Assume best, fetch time & full date */ 961290001Sglebius pp->leap = LEAP_NOWARNING; 962290001Sglebius rc_time = parse_time(&date, &tofs.tv_nsec, &rdata, 1); 963290001Sglebius rc_date = parse_date(&date, &rdata, 2, DATE_3_DDMMYYYY); 964290001Sglebius break; 96582498Sroberto 966290001Sglebius case NMEA_GPZDG: 967290001Sglebius /* Check quality byte, fetch time & full date */ 968290001Sglebius rc_time = parse_time(&date, &tofs.tv_nsec, &rdata, 1); 969290001Sglebius rc_date = parse_date(&date, &rdata, 2, DATE_3_DDMMYYYY); 970290001Sglebius pp->leap = parse_qual(&rdata, 4, '0', 1); 971290001Sglebius tofs.tv_sec = -1; /* GPZDG is following second */ 97282498Sroberto break; 97382498Sroberto 974290001Sglebius case NMEA_PGRMF: 975290001Sglebius /* get date, time, qualifier and GPS weektime. We need 976290001Sglebius * date and time-of-day for the century fix, so we read 977290001Sglebius * them first. 97882498Sroberto */ 979290001Sglebius rc_date = parse_weekdata(&gpsw, &rdata, 1, 2, 5) 980290001Sglebius && parse_date(&date, &rdata, 3, DATE_1_DDMMYY); 981290001Sglebius rc_time = parse_time(&date, &tofs.tv_nsec, &rdata, 4); 982290001Sglebius pp->leap = parse_qual(&rdata, 11, '0', 1); 983290001Sglebius rc_date = rc_date 984290001Sglebius && gpsfix_century(&date, &gpsw, &up->century_cache); 985290001Sglebius if (CLK_FLAG4 & pp->sloppyclockflag) 986290001Sglebius field_wipe(&rdata, 6, 8, -1); 98782498Sroberto break; 988290001Sglebius 989290001Sglebius default: 990290001Sglebius INVARIANT(0); /* Coverity 97123 */ 991290001Sglebius return; 992290001Sglebius } 99382498Sroberto 994290001Sglebius /* Check sanity of time-of-day. */ 995290001Sglebius if (rc_time == 0) { /* no time or conversion error? */ 996290001Sglebius checkres = CEVNT_BADTIME; 997290001Sglebius up->tally.malformed++; 998290001Sglebius } 999290001Sglebius /* Check sanity of date. */ 1000290001Sglebius else if (rc_date == 0) {/* no date or conversion error? */ 1001290001Sglebius checkres = CEVNT_BADDATE; 1002290001Sglebius up->tally.malformed++; 1003290001Sglebius } 1004290001Sglebius /* check clock sanity; [bug 2143] */ 1005290001Sglebius else if (pp->leap == LEAP_NOTINSYNC) { /* no good status? */ 1006290001Sglebius checkres = CEVNT_BADREPLY; 1007290001Sglebius up->tally.rejected++; 1008290001Sglebius } 1009290001Sglebius else 1010290001Sglebius checkres = -1; 101182498Sroberto 1012290001Sglebius if (checkres != -1) { 1013290001Sglebius save_ltc(pp, rd_lastcode, rd_lencode); 1014290001Sglebius refclock_report(peer, checkres); 101582498Sroberto return; 1016290001Sglebius } 101782498Sroberto 1018290001Sglebius DPRINTF(1, ("%s effective timecode: %04u-%02u-%02u %02d:%02d:%02d\n", 1019290001Sglebius refnumtoa(&peer->srcadr), 1020290001Sglebius date.year, date.month, date.monthday, 1021290001Sglebius date.hour, date.minute, date.second)); 1022290001Sglebius 1023290001Sglebius /* Check if we must enter GPS time mode; log so if we do */ 1024290001Sglebius if (!up->gps_time && (sentence == NMEA_GPZDG)) { 1025290001Sglebius msyslog(LOG_INFO, "%s using GPS time as if it were UTC", 1026290001Sglebius refnumtoa(&peer->srcadr)); 1027290001Sglebius up->gps_time = 1; 102882498Sroberto } 1029290001Sglebius 1030290001Sglebius /* 1031290001Sglebius * Get the reference time stamp from the calendar buffer. 1032290001Sglebius * Process the new sample in the median filter and determine the 1033290001Sglebius * timecode timestamp, but only if the PPS is not in control. 1034290001Sglebius * Discard sentence if reference time did not change. 1035290001Sglebius */ 1036290001Sglebius rd_reftime = eval_gps_time(peer, &date, &tofs, &rd_timestamp); 1037290001Sglebius if (L_ISEQU(&up->last_reftime, &rd_reftime)) { 1038290001Sglebius /* Do not touch pp->a_lastcode on purpose! */ 1039290001Sglebius up->tally.filtered++; 1040290001Sglebius return; 1041290001Sglebius } 1042290001Sglebius up->last_reftime = rd_reftime; 1043290001Sglebius rd_fudge = pp->fudgetime2; 104482498Sroberto 1045290001Sglebius DPRINTF(1, ("%s using '%s'\n", 1046290001Sglebius refnumtoa(&peer->srcadr), rd_lastcode)); 104754359Sroberto 1048290001Sglebius /* Data will be accepted. Update stats & log data. */ 1049290001Sglebius up->tally.accepted++; 1050290001Sglebius save_ltc(pp, rd_lastcode, rd_lencode); 1051290001Sglebius pp->lastrec = rd_timestamp; 1052290001Sglebius 1053290001Sglebius#ifdef HAVE_PPSAPI 1054290001Sglebius /* 1055290001Sglebius * If we have PPS running, we try to associate the sentence 1056290001Sglebius * with the last active edge of the PPS signal. 1057290001Sglebius */ 1058290001Sglebius if (up->ppsapi_lit) 1059290001Sglebius switch (refclock_ppsrelate( 1060290001Sglebius pp, &up->atom, &rd_reftime, &rd_timestamp, 1061290001Sglebius pp->fudgetime1, &rd_fudge)) 1062290001Sglebius { 1063290001Sglebius case PPS_RELATE_PHASE: 1064290001Sglebius up->ppsapi_gate = TRUE; 1065290001Sglebius peer->precision = PPS_PRECISION; 1066290001Sglebius peer->flags |= FLAG_PPS; 1067290001Sglebius DPRINTF(2, ("%s PPS_RELATE_PHASE\n", 1068290001Sglebius refnumtoa(&peer->srcadr))); 1069290001Sglebius up->tally.pps_used++; 1070290001Sglebius break; 1071290001Sglebius 1072290001Sglebius case PPS_RELATE_EDGE: 1073290001Sglebius up->ppsapi_gate = TRUE; 1074290001Sglebius peer->precision = PPS_PRECISION; 1075290001Sglebius DPRINTF(2, ("%s PPS_RELATE_EDGE\n", 1076290001Sglebius refnumtoa(&peer->srcadr))); 1077290001Sglebius break; 1078290001Sglebius 1079290001Sglebius case PPS_RELATE_NONE: 1080290001Sglebius default: 1081290001Sglebius /* 1082290001Sglebius * Resetting precision and PPS flag is done in 1083290001Sglebius * 'nmea_poll', since it might be a glitch. But 1084290001Sglebius * at the end of the poll cycle we know... 1085290001Sglebius */ 1086290001Sglebius DPRINTF(2, ("%s PPS_RELATE_NONE\n", 1087290001Sglebius refnumtoa(&peer->srcadr))); 1088290001Sglebius break; 108954359Sroberto } 1090290001Sglebius#endif /* HAVE_PPSAPI */ 109154359Sroberto 1092290001Sglebius refclock_process_offset(pp, rd_reftime, rd_timestamp, rd_fudge); 1093290001Sglebius} 109482498Sroberto 1095290001Sglebius 1096290001Sglebius/* 1097290001Sglebius * ------------------------------------------------------------------- 1098290001Sglebius * nmea_poll - called by the transmit procedure 1099290001Sglebius * 1100290001Sglebius * Does the necessary bookkeeping stuff to keep the reported state of 1101290001Sglebius * the clock in sync with reality. 1102290001Sglebius * 1103290001Sglebius * We go to great pains to avoid changing state here, since there may 1104290001Sglebius * be more than one eavesdropper receiving the same timecode. 1105290001Sglebius * ------------------------------------------------------------------- 1106290001Sglebius */ 1107290001Sglebiusstatic void 1108290001Sglebiusnmea_poll( 1109290001Sglebius int unit, 1110290001Sglebius struct peer * peer 1111290001Sglebius ) 1112290001Sglebius{ 1113290001Sglebius struct refclockproc * const pp = peer->procptr; 1114290001Sglebius nmea_unit * const up = (nmea_unit *)pp->unitptr; 1115290001Sglebius 111682498Sroberto /* 1117290001Sglebius * Process median filter samples. If none received, declare a 1118290001Sglebius * timeout and keep going. 111982498Sroberto */ 1120290001Sglebius#ifdef HAVE_PPSAPI 1121290001Sglebius /* 1122290001Sglebius * If we don't have PPS pulses and time stamps, turn PPS down 1123290001Sglebius * for now. 1124290001Sglebius */ 1125290001Sglebius if (!up->ppsapi_gate) { 1126290001Sglebius peer->flags &= ~FLAG_PPS; 1127290001Sglebius peer->precision = PRECISION; 1128290001Sglebius } else { 1129290001Sglebius up->ppsapi_gate = FALSE; 1130290001Sglebius } 1131290001Sglebius#endif /* HAVE_PPSAPI */ 1132290001Sglebius 1133290001Sglebius /* 1134290001Sglebius * If the median filter is empty, claim a timeout. Else process 1135290001Sglebius * the input data and keep the stats going. 1136290001Sglebius */ 1137290001Sglebius if (pp->coderecv == pp->codeproc) { 1138290001Sglebius refclock_report(peer, CEVNT_TIMEOUT); 1139290001Sglebius } else { 1140290001Sglebius pp->polls++; 1141290001Sglebius pp->lastref = pp->lastrec; 1142290001Sglebius refclock_receive(peer); 1143290001Sglebius } 1144290001Sglebius 1145290001Sglebius /* 1146290001Sglebius * If extended logging is required, write the tally stats to the 1147290001Sglebius * clockstats file; otherwise just do a normal clock stats 1148290001Sglebius * record. Clear the tally stats anyway. 114982498Sroberto */ 1150290001Sglebius if (peer->ttl & NMEA_EXTLOG_MASK) { 1151290001Sglebius /* Log & reset counters with extended logging */ 1152290001Sglebius const char *nmea = pp->a_lastcode; 1153290001Sglebius if (*nmea == '\0') nmea = "(none)"; 1154290001Sglebius mprintf_clock_stats( 1155290001Sglebius &peer->srcadr, "%s %u %u %u %u %u %u", 1156290001Sglebius nmea, 1157290001Sglebius up->tally.total, up->tally.accepted, 1158290001Sglebius up->tally.rejected, up->tally.malformed, 1159290001Sglebius up->tally.filtered, up->tally.pps_used); 1160290001Sglebius } else { 1161290001Sglebius record_clock_stats(&peer->srcadr, pp->a_lastcode); 116282498Sroberto } 1163290001Sglebius ZERO(up->tally); 1164290001Sglebius} 116582498Sroberto 1166290001Sglebius/* 1167290001Sglebius * ------------------------------------------------------------------- 1168290001Sglebius * Save the last timecode string, making sure it's properly truncated 1169290001Sglebius * if necessary and NUL terminated in any case. 1170290001Sglebius */ 1171290001Sglebiusstatic void 1172290001Sglebiussave_ltc( 1173290001Sglebius struct refclockproc * const pp, 1174290001Sglebius const char * const tc, 1175290001Sglebius size_t len 1176290001Sglebius ) 1177290001Sglebius{ 1178290001Sglebius if (len >= sizeof(pp->a_lastcode)) 1179290001Sglebius len = sizeof(pp->a_lastcode) - 1; 1180290001Sglebius pp->lencode = (u_short)len; 1181290001Sglebius memcpy(pp->a_lastcode, tc, len); 1182290001Sglebius pp->a_lastcode[len] = '\0'; 1183290001Sglebius} 1184290001Sglebius 1185290001Sglebius 1186290001Sglebius#if NMEA_WRITE_SUPPORT 1187290001Sglebius/* 1188290001Sglebius * ------------------------------------------------------------------- 1189290001Sglebius * gps_send(fd, cmd, peer) Sends a command to the GPS receiver. 1190290001Sglebius * as in gps_send(fd, "rqts,u", peer); 1191290001Sglebius * 1192290001Sglebius * If 'cmd' starts with a '$' it is assumed that this command is in raw 1193290001Sglebius * format, that is, starts with '$', ends with '<cr><lf>' and that any 1194290001Sglebius * checksum is correctly provided; the command will be send 'as is' in 1195290001Sglebius * that case. Otherwise the function will create the necessary frame 1196290001Sglebius * (start char, chksum, final CRLF) on the fly. 1197290001Sglebius * 1198290001Sglebius * We don't currently send any data, but would like to send RTCM SC104 1199290001Sglebius * messages for differential positioning. It should also give us better 1200290001Sglebius * time. Without a PPS output, we're Just fooling ourselves because of 1201290001Sglebius * the serial code paths 1202290001Sglebius * ------------------------------------------------------------------- 1203290001Sglebius */ 1204290001Sglebiusstatic void 1205290001Sglebiusgps_send( 1206290001Sglebius int fd, 1207290001Sglebius const char * cmd, 1208290001Sglebius struct peer * peer 1209290001Sglebius ) 1210290001Sglebius{ 1211290001Sglebius /* $...*xy<CR><LF><NUL> add 7 */ 1212290001Sglebius char buf[NMEA_PROTO_MAXLEN + 7]; 1213290001Sglebius int len; 1214290001Sglebius u_char dcs; 1215290001Sglebius const u_char *beg, *end; 1216290001Sglebius 1217290001Sglebius if (*cmd != '$') { 1218290001Sglebius /* get checksum and length */ 1219290001Sglebius beg = end = (const u_char*)cmd; 1220290001Sglebius dcs = 0; 1221290001Sglebius while (*end >= ' ' && *end != '*') 1222290001Sglebius dcs ^= *end++; 1223290001Sglebius len = end - beg; 1224290001Sglebius /* format into output buffer with overflow check */ 1225290001Sglebius len = snprintf(buf, sizeof(buf), "$%.*s*%02X\r\n", 1226290001Sglebius len, beg, dcs); 1227290001Sglebius if ((size_t)len >= sizeof(buf)) { 1228290001Sglebius DPRINTF(1, ("%s gps_send: buffer overflow for command '%s'\n", 1229290001Sglebius refnumtoa(&peer->srcadr), cmd)); 1230290001Sglebius return; /* game over player 1 */ 1231290001Sglebius } 1232290001Sglebius cmd = buf; 1233290001Sglebius } else { 1234290001Sglebius len = strlen(cmd); 123554359Sroberto } 123654359Sroberto 1237290001Sglebius DPRINTF(1, ("%s gps_send: '%.*s'\n", refnumtoa(&peer->srcadr), 1238290001Sglebius len - 2, cmd)); 123982498Sroberto 1240290001Sglebius /* send out the whole stuff */ 1241290001Sglebius if (write(fd, cmd, len) == -1) 1242290001Sglebius refclock_report(peer, CEVNT_FAULT); 1243290001Sglebius} 1244290001Sglebius#endif /* NMEA_WRITE_SUPPORT */ 1245290001Sglebius 1246290001Sglebius/* 1247290001Sglebius * ------------------------------------------------------------------- 1248290001Sglebius * helpers for faster field splitting 1249290001Sglebius * ------------------------------------------------------------------- 1250290001Sglebius * 1251290001Sglebius * set up a field record, check syntax and verify checksum 1252290001Sglebius * 1253290001Sglebius * format is $XXXXX,1,2,3,4*ML 1254290001Sglebius * 1255290001Sglebius * 8-bit XOR of characters between $ and * noninclusive is transmitted 1256290001Sglebius * in last two chars M and L holding most and least significant nibbles 1257290001Sglebius * in hex representation such as: 1258290001Sglebius * 1259290001Sglebius * $GPGLL,5057.970,N,00146.110,E,142451,A*27 1260290001Sglebius * $GPVTG,089.0,T,,,15.2,N,,*7F 1261290001Sglebius * 1262290001Sglebius * Some other constraints: 1263290001Sglebius * + The field name must at least 5 upcase characters or digits and must 1264290001Sglebius * start with a character. 1265290001Sglebius * + The checksum (if present) must be uppercase hex digits. 1266290001Sglebius * + The length of a sentence is limited to 80 characters (not including 1267290001Sglebius * the final CR/LF nor the checksum, but including the leading '$') 1268290001Sglebius * 1269290001Sglebius * Return values: 1270290001Sglebius * + CHECK_INVALID 1271290001Sglebius * The data does not form a valid NMEA sentence or a checksum error 1272290001Sglebius * occurred. 1273290001Sglebius * + CHECK_VALID 1274290001Sglebius * The data is a valid NMEA sentence but contains no checksum. 1275290001Sglebius * + CHECK_CSVALID 1276290001Sglebius * The data is a valid NMEA sentence and passed the checksum test. 1277290001Sglebius * ------------------------------------------------------------------- 1278290001Sglebius */ 1279290001Sglebiusstatic int 1280290001Sglebiusfield_init( 1281290001Sglebius nmea_data * data, /* context structure */ 1282290001Sglebius char * cptr, /* start of raw data */ 1283290001Sglebius int dlen /* data len, not counting trailing NUL */ 1284290001Sglebius ) 1285290001Sglebius{ 1286290001Sglebius u_char cs_l; /* checksum local computed */ 1287290001Sglebius u_char cs_r; /* checksum remote given */ 1288290001Sglebius char * eptr; /* buffer end end pointer */ 1289290001Sglebius char tmp; /* char buffer */ 1290290001Sglebius 1291290001Sglebius cs_l = 0; 1292290001Sglebius cs_r = 0; 1293290001Sglebius /* some basic input constraints */ 1294290001Sglebius if (dlen < 0) 1295290001Sglebius dlen = 0; 1296290001Sglebius eptr = cptr + dlen; 1297290001Sglebius *eptr = '\0'; 1298290001Sglebius 1299290001Sglebius /* load data context */ 1300290001Sglebius data->base = cptr; 1301290001Sglebius data->cptr = cptr; 1302290001Sglebius data->cidx = 0; 1303290001Sglebius data->blen = dlen; 1304290001Sglebius 1305290001Sglebius /* syntax check follows here. check allowed character 1306290001Sglebius * sequences, updating the local computed checksum as we go. 1307290001Sglebius * 1308290001Sglebius * regex equiv: '^\$[A-Z][A-Z0-9]{4,}[^*]*(\*[0-9A-F]{2})?$' 130954359Sroberto */ 1310290001Sglebius 1311290001Sglebius /* -*- start character: '^\$' */ 1312290001Sglebius if (*cptr == '\0') 1313290001Sglebius return CHECK_EMPTY; 1314290001Sglebius if (*cptr++ != '$') 1315290001Sglebius return CHECK_INVALID; 1316290001Sglebius 1317290001Sglebius /* -*- advance context beyond start character */ 1318290001Sglebius data->base++; 1319290001Sglebius data->cptr++; 1320290001Sglebius data->blen--; 1321290001Sglebius 1322290001Sglebius /* -*- field name: '[A-Z][A-Z0-9]{4,},' */ 1323290001Sglebius if (*cptr < 'A' || *cptr > 'Z') 1324290001Sglebius return CHECK_INVALID; 1325290001Sglebius cs_l ^= *cptr++; 1326290001Sglebius while ((*cptr >= 'A' && *cptr <= 'Z') || 1327290001Sglebius (*cptr >= '0' && *cptr <= '9') ) 1328290001Sglebius cs_l ^= *cptr++; 1329290001Sglebius if (*cptr != ',' || (cptr - data->base) < NMEA_PROTO_IDLEN) 1330290001Sglebius return CHECK_INVALID; 1331290001Sglebius cs_l ^= *cptr++; 1332290001Sglebius 1333290001Sglebius /* -*- data: '[^*]*' */ 1334290001Sglebius while (*cptr && *cptr != '*') 1335290001Sglebius cs_l ^= *cptr++; 1336290001Sglebius 1337290001Sglebius /* -*- checksum field: (\*[0-9A-F]{2})?$ */ 1338290001Sglebius if (*cptr == '\0') 1339290001Sglebius return CHECK_VALID; 1340290001Sglebius if (*cptr != '*' || cptr != eptr - 3 || 1341290001Sglebius (cptr - data->base) >= NMEA_PROTO_MAXLEN) 1342290001Sglebius return CHECK_INVALID; 1343290001Sglebius 1344290001Sglebius for (cptr++; (tmp = *cptr) != '\0'; cptr++) { 1345290001Sglebius if (tmp >= '0' && tmp <= '9') 1346290001Sglebius cs_r = (cs_r << 4) + (tmp - '0'); 1347290001Sglebius else if (tmp >= 'A' && tmp <= 'F') 1348290001Sglebius cs_r = (cs_r << 4) + (tmp - 'A' + 10); 1349290001Sglebius else 1350290001Sglebius break; 135154359Sroberto } 1352290001Sglebius 1353290001Sglebius /* -*- make sure we are at end of string and csum matches */ 1354290001Sglebius if (cptr != eptr || cs_l != cs_r) 1355290001Sglebius return CHECK_INVALID; 1356290001Sglebius 1357290001Sglebius return CHECK_CSVALID; 1358290001Sglebius} 1359290001Sglebius 1360290001Sglebius/* 1361290001Sglebius * ------------------------------------------------------------------- 1362290001Sglebius * fetch a data field by index, zero being the name field. If this 1363290001Sglebius * function is called repeatedly with increasing indices, the total load 1364290001Sglebius * is O(n), n being the length of the string; if it is called with 1365290001Sglebius * decreasing indices, the total load is O(n^2). Try not to go backwards 1366290001Sglebius * too often. 1367290001Sglebius * ------------------------------------------------------------------- 1368290001Sglebius */ 1369290001Sglebiusstatic char * 1370290001Sglebiusfield_parse( 1371290001Sglebius nmea_data * data, 1372290001Sglebius int fn 1373290001Sglebius ) 1374290001Sglebius{ 1375290001Sglebius char tmp; 1376290001Sglebius 1377290001Sglebius if (fn < data->cidx) { 1378290001Sglebius data->cidx = 0; 1379290001Sglebius data->cptr = data->base; 138082498Sroberto } 1381290001Sglebius while ((fn > data->cidx) && (tmp = *data->cptr) != '\0') { 1382290001Sglebius data->cidx += (tmp == ','); 1383290001Sglebius data->cptr++; 1384290001Sglebius } 1385290001Sglebius return data->cptr; 1386290001Sglebius} 138754359Sroberto 1388290001Sglebius/* 1389290001Sglebius * ------------------------------------------------------------------- 1390290001Sglebius * Wipe (that is, overwrite with '_') data fields and the checksum in 1391290001Sglebius * the last timecode. The list of field indices is given as integers 1392290001Sglebius * in a varargs list, preferrably in ascending order, in any case 1393290001Sglebius * terminated by a negative field index. 1394290001Sglebius * 1395290001Sglebius * A maximum number of 8 fields can be overwritten at once to guard 1396290001Sglebius * against runaway (that is, unterminated) argument lists. 1397290001Sglebius * 1398290001Sglebius * This function affects what a remote user can see with 1399290001Sglebius * 1400290001Sglebius * ntpq -c clockvar <server> 1401290001Sglebius * 1402290001Sglebius * Note that this also removes the wiped fields from any clockstats 1403290001Sglebius * log. Some NTP operators monitor their NMEA GPS using the change in 1404290001Sglebius * location in clockstats over time as as a proxy for the quality of 1405290001Sglebius * GPS reception and thereby time reported. 1406290001Sglebius * ------------------------------------------------------------------- 1407290001Sglebius */ 1408290001Sglebiusstatic void 1409290001Sglebiusfield_wipe( 1410290001Sglebius nmea_data * data, 1411290001Sglebius ... 1412290001Sglebius ) 1413290001Sglebius{ 1414290001Sglebius va_list va; /* vararg index list */ 1415290001Sglebius int fcnt; /* safeguard against runaway arglist */ 1416290001Sglebius int fidx; /* field to nuke, or -1 for checksum */ 1417290001Sglebius char * cp; /* overwrite destination */ 1418290001Sglebius 1419290001Sglebius fcnt = 8; 1420290001Sglebius cp = NULL; 1421290001Sglebius va_start(va, data); 1422290001Sglebius do { 1423290001Sglebius fidx = va_arg(va, int); 1424290001Sglebius if (fidx >= 0 && fidx <= NMEA_PROTO_FIELDS) { 1425290001Sglebius cp = field_parse(data, fidx); 1426290001Sglebius } else { 1427290001Sglebius cp = data->base + data->blen; 1428290001Sglebius if (data->blen >= 3 && cp[-3] == '*') 1429290001Sglebius cp -= 2; 1430290001Sglebius } 1431290001Sglebius for ( ; '\0' != *cp && '*' != *cp && ',' != *cp; cp++) 1432290001Sglebius if ('.' != *cp) 1433290001Sglebius *cp = '_'; 1434290001Sglebius } while (fcnt-- && fidx >= 0); 1435290001Sglebius va_end(va); 1436290001Sglebius} 1437290001Sglebius 1438290001Sglebius/* 1439290001Sglebius * ------------------------------------------------------------------- 1440290001Sglebius * PARSING HELPERS 1441290001Sglebius * ------------------------------------------------------------------- 1442290001Sglebius * 1443290001Sglebius * Check sync status 1444290001Sglebius * 1445290001Sglebius * If the character at the data field start matches the tag value, 1446290001Sglebius * return LEAP_NOWARNING and LEAP_NOTINSYNC otherwise. If the 'inverted' 1447290001Sglebius * flag is given, just the opposite value is returned. If there is no 1448290001Sglebius * data field (*cp points to the NUL byte) the result is LEAP_NOTINSYNC. 1449290001Sglebius * ------------------------------------------------------------------- 1450290001Sglebius */ 1451290001Sglebiusstatic u_char 1452290001Sglebiusparse_qual( 1453290001Sglebius nmea_data * rd, 1454290001Sglebius int idx, 1455290001Sglebius char tag, 1456290001Sglebius int inv 1457290001Sglebius ) 1458290001Sglebius{ 1459290001Sglebius static const u_char table[2] = 1460290001Sglebius { LEAP_NOTINSYNC, LEAP_NOWARNING }; 1461290001Sglebius char * dp; 1462290001Sglebius 1463290001Sglebius dp = field_parse(rd, idx); 1464290001Sglebius 1465290001Sglebius return table[ *dp && ((*dp == tag) == !inv) ]; 1466290001Sglebius} 1467290001Sglebius 1468290001Sglebius/* 1469290001Sglebius * ------------------------------------------------------------------- 1470290001Sglebius * Parse a time stamp in HHMMSS[.sss] format with error checking. 1471290001Sglebius * 1472290001Sglebius * returns 1 on success, 0 on failure 1473290001Sglebius * ------------------------------------------------------------------- 1474290001Sglebius */ 1475290001Sglebiusstatic int 1476290001Sglebiusparse_time( 1477290001Sglebius struct calendar * jd, /* result calendar pointer */ 1478290001Sglebius long * ns, /* storage for nsec fraction */ 1479290001Sglebius nmea_data * rd, 1480290001Sglebius int idx 1481290001Sglebius ) 1482290001Sglebius{ 1483290001Sglebius static const unsigned long weight[4] = { 1484290001Sglebius 0, 100000000, 10000000, 1000000 1485290001Sglebius }; 1486290001Sglebius 1487290001Sglebius int rc; 1488290001Sglebius u_int h; 1489290001Sglebius u_int m; 1490290001Sglebius u_int s; 1491290001Sglebius int p1; 1492290001Sglebius int p2; 1493290001Sglebius u_long f; 1494290001Sglebius char * dp; 1495290001Sglebius 1496290001Sglebius dp = field_parse(rd, idx); 1497290001Sglebius rc = sscanf(dp, "%2u%2u%2u%n.%3lu%n", &h, &m, &s, &p1, &f, &p2); 1498290001Sglebius if (rc < 3 || p1 != 6) { 1499290001Sglebius DPRINTF(1, ("nmea: invalid time code: '%.6s'\n", dp)); 1500290001Sglebius return FALSE; 150154359Sroberto } 1502290001Sglebius 1503290001Sglebius /* value sanity check */ 1504290001Sglebius if (h > 23 || m > 59 || s > 60) { 1505290001Sglebius DPRINTF(1, ("nmea: invalid time spec %02u:%02u:%02u\n", 1506290001Sglebius h, m, s)); 1507290001Sglebius return FALSE; 1508290001Sglebius } 150954359Sroberto 1510290001Sglebius jd->hour = (u_char)h; 1511290001Sglebius jd->minute = (u_char)m; 1512290001Sglebius jd->second = (u_char)s; 1513290001Sglebius /* if we have a fraction, scale it up to nanoseconds. */ 1514290001Sglebius if (rc == 4) 1515290001Sglebius *ns = f * weight[p2 - p1 - 1]; 1516290001Sglebius else 1517290001Sglebius *ns = 0; 1518290001Sglebius 1519290001Sglebius return TRUE; 1520290001Sglebius} 1521290001Sglebius 1522290001Sglebius/* 1523290001Sglebius * ------------------------------------------------------------------- 1524290001Sglebius * Parse a date string from an NMEA sentence. This could either be a 1525290001Sglebius * partial date in DDMMYY format in one field, or DD,MM,YYYY full date 1526290001Sglebius * spec spanning three fields. This function does some extensive error 1527290001Sglebius * checking to make sure the date string was consistent. 1528290001Sglebius * 1529290001Sglebius * returns 1 on success, 0 on failure 1530290001Sglebius * ------------------------------------------------------------------- 1531290001Sglebius */ 1532290001Sglebiusstatic int 1533290001Sglebiusparse_date( 1534290001Sglebius struct calendar * jd, /* result pointer */ 1535290001Sglebius nmea_data * rd, 1536290001Sglebius int idx, 1537290001Sglebius enum date_fmt fmt 1538290001Sglebius ) 1539290001Sglebius{ 1540290001Sglebius int rc; 1541290001Sglebius u_int y; 1542290001Sglebius u_int m; 1543290001Sglebius u_int d; 1544290001Sglebius int p; 1545290001Sglebius char * dp; 1546290001Sglebius 1547290001Sglebius dp = field_parse(rd, idx); 1548290001Sglebius switch (fmt) { 1549290001Sglebius 1550290001Sglebius case DATE_1_DDMMYY: 1551290001Sglebius rc = sscanf(dp, "%2u%2u%2u%n", &d, &m, &y, &p); 1552290001Sglebius if (rc != 3 || p != 6) { 1553290001Sglebius DPRINTF(1, ("nmea: invalid date code: '%.6s'\n", 1554290001Sglebius dp)); 1555290001Sglebius return FALSE; 155654359Sroberto } 1557290001Sglebius break; 1558290001Sglebius 1559290001Sglebius case DATE_3_DDMMYYYY: 1560290001Sglebius rc = sscanf(dp, "%2u,%2u,%4u%n", &d, &m, &y, &p); 1561290001Sglebius if (rc != 3 || p != 10) { 1562290001Sglebius DPRINTF(1, ("nmea: invalid date code: '%.10s'\n", 1563290001Sglebius dp)); 1564290001Sglebius return FALSE; 156554359Sroberto } 1566290001Sglebius break; 1567290001Sglebius 1568290001Sglebius default: 1569290001Sglebius DPRINTF(1, ("nmea: invalid parse format: %d\n", fmt)); 1570290001Sglebius return FALSE; 157154359Sroberto } 157254359Sroberto 1573290001Sglebius /* value sanity check */ 1574290001Sglebius if (d < 1 || d > 31 || m < 1 || m > 12) { 1575290001Sglebius DPRINTF(1, ("nmea: invalid date spec (YMD) %04u:%02u:%02u\n", 1576290001Sglebius y, m, d)); 1577290001Sglebius return FALSE; 157854359Sroberto } 1579290001Sglebius 1580290001Sglebius /* store results */ 1581290001Sglebius jd->monthday = (u_char)d; 1582290001Sglebius jd->month = (u_char)m; 1583290001Sglebius jd->year = (u_short)y; 158454359Sroberto 1585290001Sglebius return TRUE; 1586290001Sglebius} 158782498Sroberto 1588290001Sglebius/* 1589290001Sglebius * ------------------------------------------------------------------- 1590290001Sglebius * Parse GPS week time info from an NMEA sentence. This info contains 1591290001Sglebius * the GPS week number, the GPS time-of-week and the leap seconds GPS 1592290001Sglebius * to UTC. 1593290001Sglebius * 1594290001Sglebius * returns 1 on success, 0 on failure 1595290001Sglebius * ------------------------------------------------------------------- 1596290001Sglebius */ 1597290001Sglebiusstatic int 1598290001Sglebiusparse_weekdata( 1599290001Sglebius gps_weektm * wd, 1600290001Sglebius nmea_data * rd, 1601290001Sglebius int weekidx, 1602290001Sglebius int timeidx, 1603290001Sglebius int leapidx 1604290001Sglebius ) 1605290001Sglebius{ 1606290001Sglebius u_long secs; 1607290001Sglebius int fcnt; 1608290001Sglebius 1609290001Sglebius /* parse fields and count success */ 1610290001Sglebius fcnt = sscanf(field_parse(rd, weekidx), "%hu", &wd->wt_week); 1611290001Sglebius fcnt += sscanf(field_parse(rd, timeidx), "%lu", &secs); 1612290001Sglebius fcnt += sscanf(field_parse(rd, leapidx), "%hd", &wd->wt_leap); 1613290001Sglebius if (fcnt != 3 || wd->wt_week >= 1024 || secs >= 7*SECSPERDAY) { 1614290001Sglebius DPRINTF(1, ("nmea: parse_weekdata: invalid weektime spec\n")); 1615290001Sglebius return FALSE; 161654359Sroberto } 1617290001Sglebius wd->wt_time = (u_int32)secs; 161854359Sroberto 1619290001Sglebius return TRUE; 1620290001Sglebius} 162182498Sroberto 1622290001Sglebius/* 1623290001Sglebius * ------------------------------------------------------------------- 1624290001Sglebius * funny calendar-oriented stuff -- perhaps a bit hard to grok. 1625290001Sglebius * ------------------------------------------------------------------- 1626290001Sglebius * 1627290001Sglebius * Unfold a time-of-day (seconds since midnight) around the current 1628290001Sglebius * system time in a manner that guarantees an absolute difference of 1629290001Sglebius * less than 12hrs. 1630290001Sglebius * 1631290001Sglebius * This function is used for NMEA sentences that contain no date 1632290001Sglebius * information. This requires the system clock to be in +/-12hrs 1633290001Sglebius * around the true time, or the clock will synchronize the system 1day 1634290001Sglebius * off if not augmented with a time sources that also provide the 1635290001Sglebius * necessary date information. 1636290001Sglebius * 1637290001Sglebius * The function updates the calendar structure it also uses as 1638290001Sglebius * input to fetch the time from. 1639290001Sglebius * 1640290001Sglebius * returns 1 on success, 0 on failure 1641290001Sglebius * ------------------------------------------------------------------- 1642290001Sglebius */ 1643290001Sglebiusstatic int 1644290001Sglebiusunfold_day( 1645290001Sglebius struct calendar * jd, 1646290001Sglebius u_int32 rec_ui 1647290001Sglebius ) 1648290001Sglebius{ 1649290001Sglebius vint64 rec_qw; 1650290001Sglebius ntpcal_split rec_ds; 165182498Sroberto 165254359Sroberto /* 1653290001Sglebius * basically this is the peridiodic extension of the receive 1654290001Sglebius * time - 12hrs to the time-of-day with a period of 1 day. 1655290001Sglebius * But we would have to execute this in 64bit arithmetic, and we 1656290001Sglebius * cannot assume we can do this; therefore this is done 1657290001Sglebius * in split representation. 165854359Sroberto */ 1659290001Sglebius rec_qw = ntpcal_ntp_to_ntp(rec_ui - SECSPERDAY/2, NULL); 1660290001Sglebius rec_ds = ntpcal_daysplit(&rec_qw); 1661290001Sglebius rec_ds.lo = ntpcal_periodic_extend(rec_ds.lo, 1662290001Sglebius ntpcal_date_to_daysec(jd), 1663290001Sglebius SECSPERDAY); 1664290001Sglebius rec_ds.hi += ntpcal_daysec_to_date(jd, rec_ds.lo); 1665290001Sglebius return (ntpcal_rd_to_date(jd, rec_ds.hi + DAY_NTP_STARTS) >= 0); 1666290001Sglebius} 166754359Sroberto 1668290001Sglebius/* 1669290001Sglebius * ------------------------------------------------------------------- 1670290001Sglebius * A 2-digit year is expanded into full year spec around the year found 1671290001Sglebius * in 'jd->year'. This should be in +79/-19 years around the system time, 1672290001Sglebius * or the result will be off by 100 years. The assymetric behaviour was 1673290001Sglebius * chosen to enable inital sync for systems that do not have a 1674290001Sglebius * battery-backup clock and start with a date that is typically years in 1675290001Sglebius * the past. 1676290001Sglebius * 1677290001Sglebius * Since the GPS epoch starts at 1980-01-06, the resulting year will be 1678290001Sglebius * not be before 1980 in any case. 1679290001Sglebius * 1680290001Sglebius * returns 1 on success, 0 on failure 1681290001Sglebius * ------------------------------------------------------------------- 1682290001Sglebius */ 1683290001Sglebiusstatic int 1684290001Sglebiusunfold_century( 1685290001Sglebius struct calendar * jd, 1686290001Sglebius u_int32 rec_ui 1687290001Sglebius ) 1688290001Sglebius{ 1689290001Sglebius struct calendar rec; 1690290001Sglebius int32 baseyear; 169182498Sroberto 1692290001Sglebius ntpcal_ntp_to_date(&rec, rec_ui, NULL); 1693290001Sglebius baseyear = rec.year - 20; 1694290001Sglebius if (baseyear < g_gpsMinYear) 1695290001Sglebius baseyear = g_gpsMinYear; 1696290001Sglebius jd->year = (u_short)ntpcal_periodic_extend(baseyear, jd->year, 1697290001Sglebius 100); 169882498Sroberto 1699290001Sglebius return ((baseyear <= jd->year) && (baseyear + 100 > jd->year)); 170054359Sroberto} 170154359Sroberto 170254359Sroberto/* 1703290001Sglebius * ------------------------------------------------------------------- 1704290001Sglebius * A 2-digit year is expanded into a full year spec by correlation with 1705290001Sglebius * a GPS week number and the current leap second count. 170654359Sroberto * 1707290001Sglebius * The GPS week time scale counts weeks since Sunday, 1980-01-06, modulo 1708290001Sglebius * 1024 and seconds since start of the week. The GPS time scale is based 1709290001Sglebius * on international atomic time (TAI), so the leap second difference to 1710290001Sglebius * UTC is also needed for a proper conversion. 1711290001Sglebius * 1712290001Sglebius * A brute-force analysis (that is, test for every date) shows that a 1713290001Sglebius * wrong assignment of the century can not happen between the years 1900 1714290001Sglebius * to 2399 when comparing the week signatures for different 1715290001Sglebius * centuries. (I *think* that will not happen for 400*1024 years, but I 1716290001Sglebius * have no valid proof. -*-perlinger@ntp.org-*-) 1717290001Sglebius * 1718290001Sglebius * This function is bound to to work between years 1980 and 2399 1719290001Sglebius * (inclusive), which should suffice for now ;-) 1720290001Sglebius * 1721290001Sglebius * Note: This function needs a full date&time spec on input due to the 1722290001Sglebius * necessary leap second corrections! 1723290001Sglebius * 1724290001Sglebius * returns 1 on success, 0 on failure 1725290001Sglebius * ------------------------------------------------------------------- 172654359Sroberto */ 1727290001Sglebiusstatic int 1728290001Sglebiusgpsfix_century( 1729290001Sglebius struct calendar * jd, 1730290001Sglebius const gps_weektm * wd, 1731290001Sglebius u_short * century 1732290001Sglebius ) 173354359Sroberto{ 1734290001Sglebius int32 days; 1735290001Sglebius int32 doff; 1736290001Sglebius u_short week; 1737290001Sglebius u_short year; 1738290001Sglebius int loop; 173954359Sroberto 1740290001Sglebius /* Get day offset. Assumes that the input time is in range and 1741290001Sglebius * that the leap seconds do not shift more than +/-1 day. 1742290001Sglebius */ 1743290001Sglebius doff = ntpcal_date_to_daysec(jd) + wd->wt_leap; 1744290001Sglebius doff = (doff >= SECSPERDAY) - (doff < 0); 174554359Sroberto 174654359Sroberto /* 1747290001Sglebius * Loop over centuries to get a match, starting with the last 1748290001Sglebius * successful one. (Or with the 19th century if the cached value 1749290001Sglebius * is out of range...) 175054359Sroberto */ 1751290001Sglebius year = jd->year % 100; 1752290001Sglebius for (loop = 5; loop > 0; loop--,(*century)++) { 1753290001Sglebius if (*century < 19 || *century >= 24) 1754290001Sglebius *century = 19; 1755290001Sglebius /* Get days and week in GPS epoch */ 1756290001Sglebius jd->year = year + *century * 100; 1757290001Sglebius days = ntpcal_date_to_rd(jd) - DAY_GPS_STARTS + doff; 1758290001Sglebius week = (days / 7) % 1024; 1759290001Sglebius if (days >= 0 && wd->wt_week == week) 1760290001Sglebius return TRUE; /* matched... */ 1761290001Sglebius } 176254359Sroberto 1763290001Sglebius jd->year = year; 1764290001Sglebius return FALSE; /* match failed... */ 176554359Sroberto} 176654359Sroberto 176754359Sroberto/* 1768290001Sglebius * ------------------------------------------------------------------- 1769290001Sglebius * And now the final execise: Considering the fact that many (most?) 1770290001Sglebius * GPS receivers cannot handle a GPS epoch wrap well, we try to 1771290001Sglebius * compensate for that problem by unwrapping a GPS epoch around the 1772290001Sglebius * receive stamp. Another execise in periodic unfolding, of course, 1773290001Sglebius * but with enough points to take care of. 177454359Sroberto * 1775290001Sglebius * Note: The integral part of 'tofs' is intended to handle small(!) 1776290001Sglebius * systematic offsets, as -1 for handling $GPZDG, which gives the 1777290001Sglebius * following second. (sigh...) The absolute value shall be less than a 1778290001Sglebius * day (86400 seconds). 1779290001Sglebius * ------------------------------------------------------------------- 178054359Sroberto */ 1781290001Sglebiusstatic l_fp 1782290001Sglebiuseval_gps_time( 1783290001Sglebius struct peer * peer, /* for logging etc */ 1784290001Sglebius const struct calendar * gpst, /* GPS time stamp */ 1785290001Sglebius const struct timespec * tofs, /* GPS frac second & offset */ 1786290001Sglebius const l_fp * xrecv /* receive time stamp */ 178754359Sroberto ) 178854359Sroberto{ 1789290001Sglebius struct refclockproc * const pp = peer->procptr; 1790290001Sglebius nmea_unit * const up = (nmea_unit *)pp->unitptr; 179154359Sroberto 1792290001Sglebius l_fp retv; 1793290001Sglebius 1794290001Sglebius /* components of calculation */ 1795290001Sglebius int32_t rcv_sec, rcv_day; /* receive ToD and day */ 1796290001Sglebius int32_t gps_sec, gps_day; /* GPS ToD and day in NTP epoch */ 1797290001Sglebius int32_t adj_day, weeks; /* adjusted GPS day and week shift */ 1798290001Sglebius 1799290001Sglebius /* some temporaries to shuffle data */ 1800290001Sglebius vint64 vi64; 1801290001Sglebius ntpcal_split rs64; 1802290001Sglebius 1803290001Sglebius /* evaluate time stamp from receiver. */ 1804290001Sglebius gps_sec = ntpcal_date_to_daysec(gpst); 1805290001Sglebius gps_day = ntpcal_date_to_rd(gpst) - DAY_NTP_STARTS; 1806290001Sglebius 1807290001Sglebius /* merge in fractional offset */ 1808290001Sglebius retv = tspec_intv_to_lfp(*tofs); 1809290001Sglebius gps_sec += retv.l_i; 1810290001Sglebius 1811290001Sglebius /* If we fully trust the GPS receiver, just combine days and 1812290001Sglebius * seconds and be done. */ 1813290001Sglebius if (peer->ttl & NMEA_DATETRUST_MASK) { 1814290001Sglebius retv.l_ui = ntpcal_dayjoin(gps_day, gps_sec).D_s.lo; 1815290001Sglebius return retv; 181654359Sroberto } 1817290001Sglebius 1818290001Sglebius /* So we do not trust the GPS receiver to deliver a correct date 1819290001Sglebius * due to the GPS epoch changes. We map the date from the 1820290001Sglebius * receiver into the +/-512 week interval around the receive 1821290001Sglebius * time in that case. This would be a tad easier with 64bit 1822290001Sglebius * calculations, but again, we restrict the code to 32bit ops 1823290001Sglebius * when possible. */ 1824290001Sglebius 1825290001Sglebius /* - make sure the GPS fractional day is normalised 1826290001Sglebius * Applying the offset value might have put us slightly over the 1827290001Sglebius * edge of the allowed range for seconds-of-day. Doing a full 1828290001Sglebius * division with floor correction is overkill here; a simple 1829290001Sglebius * addition or subtraction step is sufficient. Using WHILE loops 1830290001Sglebius * gives the right result even if the offset exceeds one day, 1831290001Sglebius * which is NOT what it's intented for! */ 1832290001Sglebius while (gps_sec >= SECSPERDAY) { 1833290001Sglebius gps_sec -= SECSPERDAY; 1834290001Sglebius gps_day += 1; 1835290001Sglebius } 1836290001Sglebius while (gps_sec < 0) { 1837290001Sglebius gps_sec += SECSPERDAY; 1838290001Sglebius gps_day -= 1; 1839290001Sglebius } 1840290001Sglebius 1841290001Sglebius /* - get unfold base: day of full recv time - 512 weeks */ 1842290001Sglebius vi64 = ntpcal_ntp_to_ntp(xrecv->l_ui, NULL); 1843290001Sglebius rs64 = ntpcal_daysplit(&vi64); 1844290001Sglebius rcv_sec = rs64.lo; 1845290001Sglebius rcv_day = rs64.hi - 512 * 7; 1846290001Sglebius 1847290001Sglebius /* - take the fractional days into account 1848290001Sglebius * If the fractional day of the GPS time is smaller than the 1849290001Sglebius * fractional day of the receive time, we shift the base day for 1850290001Sglebius * the unfold by 1. */ 1851290001Sglebius if ( gps_sec < rcv_sec 1852290001Sglebius || (gps_sec == rcv_sec && retv.l_uf < xrecv->l_uf)) 1853290001Sglebius rcv_day += 1; 1854290001Sglebius 1855290001Sglebius /* - don't warp ahead of GPS invention! */ 1856290001Sglebius if (rcv_day < g_gpsMinBase) 1857290001Sglebius rcv_day = g_gpsMinBase; 1858290001Sglebius 1859290001Sglebius /* - let the magic happen: */ 1860290001Sglebius adj_day = ntpcal_periodic_extend(rcv_day, gps_day, 1024*7); 1861290001Sglebius 1862290001Sglebius /* - check if we should log a GPS epoch warp */ 1863290001Sglebius weeks = (adj_day - gps_day) / 7; 1864290001Sglebius if (weeks != up->epoch_warp) { 1865290001Sglebius up->epoch_warp = weeks; 1866290001Sglebius LOGIF(CLOCKINFO, (LOG_INFO, 1867290001Sglebius "%s Changed GPS epoch warp to %d weeks", 1868290001Sglebius refnumtoa(&peer->srcadr), weeks)); 1869290001Sglebius } 1870290001Sglebius 1871290001Sglebius /* - build result and be done */ 1872290001Sglebius retv.l_ui = ntpcal_dayjoin(adj_day, gps_sec).D_s.lo; 1873290001Sglebius return retv; 187454359Sroberto} 187554359Sroberto 1876290001Sglebius/* 1877290001Sglebius * =================================================================== 1878290001Sglebius * 1879290001Sglebius * NMEAD support 1880290001Sglebius * 1881290001Sglebius * original nmead support added by Jon Miner (cp_n18@yahoo.com) 1882290001Sglebius * 1883290001Sglebius * See http://home.hiwaay.net/~taylorc/gps/nmea-server/ 1884290001Sglebius * for information about nmead 1885290001Sglebius * 1886290001Sglebius * To use this, you need to create a link from /dev/gpsX to 1887290001Sglebius * the server:port where nmead is running. Something like this: 1888290001Sglebius * 1889290001Sglebius * ln -s server:port /dev/gps1 1890290001Sglebius * 1891290001Sglebius * Split into separate function by Juergen Perlinger 1892290001Sglebius * (perlinger-at-ntp-dot-org) 1893290001Sglebius * 1894290001Sglebius * =================================================================== 1895290001Sglebius */ 1896290001Sglebiusstatic int 1897290001Sglebiusnmead_open( 1898290001Sglebius const char * device 189954359Sroberto ) 190054359Sroberto{ 1901290001Sglebius int fd = -1; /* result file descriptor */ 1902290001Sglebius 1903290001Sglebius#ifdef HAVE_READLINK 1904290001Sglebius char host[80]; /* link target buffer */ 1905290001Sglebius char * port; /* port name or number */ 1906290001Sglebius int rc; /* result code (several)*/ 1907290001Sglebius int sh; /* socket handle */ 1908290001Sglebius struct addrinfo ai_hint; /* resolution hint */ 1909290001Sglebius struct addrinfo *ai_list; /* resolution result */ 1910290001Sglebius struct addrinfo *ai; /* result scan ptr */ 191154359Sroberto 1912290001Sglebius fd = -1; 1913290001Sglebius 1914290001Sglebius /* try to read as link, make sure no overflow occurs */ 1915290001Sglebius rc = readlink(device, host, sizeof(host)); 1916290001Sglebius if ((size_t)rc >= sizeof(host)) 1917290001Sglebius return fd; /* error / overflow / truncation */ 1918290001Sglebius host[rc] = '\0'; /* readlink does not place NUL */ 1919290001Sglebius 1920290001Sglebius /* get port */ 1921290001Sglebius port = strchr(host, ':'); 1922290001Sglebius if (!port) 1923290001Sglebius return fd; /* not 'host:port' syntax ? */ 1924290001Sglebius *port++ = '\0'; /* put in separator */ 1925290001Sglebius 1926290001Sglebius /* get address infos and try to open socket 1927290001Sglebius * 1928290001Sglebius * This getaddrinfo() is naughty in ntpd's nonblocking main 1929290001Sglebius * thread, but you have to go out of your wary to use this code 1930290001Sglebius * and typically the blocking is at startup where its impact is 1931290001Sglebius * reduced. The same holds for the 'connect()', as it is 1932290001Sglebius * blocking, too... 1933290001Sglebius */ 1934290001Sglebius ZERO(ai_hint); 1935290001Sglebius ai_hint.ai_protocol = IPPROTO_TCP; 1936290001Sglebius ai_hint.ai_socktype = SOCK_STREAM; 1937290001Sglebius if (getaddrinfo(host, port, &ai_hint, &ai_list)) 1938290001Sglebius return fd; 1939290001Sglebius 1940290001Sglebius for (ai = ai_list; ai && (fd == -1); ai = ai->ai_next) { 1941290001Sglebius sh = socket(ai->ai_family, ai->ai_socktype, 1942290001Sglebius ai->ai_protocol); 1943290001Sglebius if (INVALID_SOCKET == sh) 1944290001Sglebius continue; 1945290001Sglebius rc = connect(sh, ai->ai_addr, ai->ai_addrlen); 1946290001Sglebius if (-1 != rc) 1947290001Sglebius fd = sh; 1948290001Sglebius else 1949290001Sglebius close(sh); 195054359Sroberto } 1951290001Sglebius freeaddrinfo(ai_list); 1952290001Sglebius#else 1953290001Sglebius fd = -1; 1954290001Sglebius#endif 1955290001Sglebius 1956290001Sglebius return fd; 195754359Sroberto} 195854359Sroberto#else 1959290001SglebiusNONEMPTY_TRANSLATION_UNIT 1960290001Sglebius#endif /* REFCLOCK && CLOCK_NMEA */ 1961