154359Sroberto/* 254359Sroberto * refclock_nmea.c - clock driver for an NMEA GPS CLOCK 354359Sroberto * Michael Petry Jun 20, 1994 454359Sroberto * based on refclock_heathn.c 5290000Sglebius * 6290000Sglebius * Updated to add support for Accord GPS Clock 7290000Sglebius * Venu Gopal Dec 05, 2007 8290000Sglebius * neo.venu@gmail.com, venugopal_d@pgad.gov.in 9290000Sglebius * 10290000Sglebius * Updated to process 'time1' fudge factor 11290000Sglebius * Venu Gopal May 05, 2008 12290000Sglebius * 13290000Sglebius * Converted to common PPSAPI code, separate PPS fudge time1 14290000Sglebius * from serial timecode fudge time2. 15290000Sglebius * Dave Hart July 1, 2009 16290000Sglebius * hart@ntp.org, davehart@davehart.com 1754359Sroberto */ 18290000Sglebius 1954359Sroberto#ifdef HAVE_CONFIG_H 2054359Sroberto#include <config.h> 2154359Sroberto#endif 2254359Sroberto 23290000Sglebius#include "ntp_types.h" 24290000Sglebius 2554359Sroberto#if defined(REFCLOCK) && defined(CLOCK_NMEA) 2654359Sroberto 27290000Sglebius#define NMEA_WRITE_SUPPORT 0 /* no write support at the moment */ 28290000Sglebius 29290000Sglebius#include <sys/stat.h> 30182007Sroberto#include <stdio.h> 31182007Sroberto#include <ctype.h> 32290000Sglebius#ifdef HAVE_SYS_SOCKET_H 33290000Sglebius#include <sys/socket.h> 34290000Sglebius#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" 41290000Sglebius#include "ntp_calendar.h" 42290000Sglebius#include "timespecops.h" 4354359Sroberto 4482498Sroberto#ifdef HAVE_PPSAPI 45182007Sroberto# include "ppsapi_timepps.h" 46290000Sglebius# include "refclock_atom.h" 4782498Sroberto#endif /* HAVE_PPSAPI */ 4882498Sroberto 49200576Sroberto 5054359Sroberto/* 51290000Sglebius * This driver supports NMEA-compatible GPS receivers 5254359Sroberto * 53290000Sglebius * Prototype was refclock_trak.c, Thanks a lot. 5454359Sroberto * 5554359Sroberto * The receiver used spits out the NMEA sentences for boat navigation. 56290000Sglebius * 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) 74290000Sglebius * bit 3 - enables ZDA (8) - Standard Time & Date 75290000Sglebius * bit 3 - enables ZDG (8) - Accord GPS Clock's custom sentence with GPS time 76290000Sglebius * very close to standard ZDA 77290000Sglebius * 78290000Sglebius * Multiple sentences may be selected except when ZDG/ZDA is selected. 79290000Sglebius * 80290000Sglebius * bit 4/5/6 - selects the baudrate for serial port : 81290000Sglebius * 0 for 4800 (default) 82290000Sglebius * 1 for 9600 83290000Sglebius * 2 for 19200 84290000Sglebius * 3 for 38400 85290000Sglebius * 4 for 57600 86290000Sglebius * 5 for 115200 8754359Sroberto */ 88290000Sglebius#define NMEA_MESSAGE_MASK 0x0000FF0FU 89290000Sglebius#define NMEA_BAUDRATE_MASK 0x00000070U 90290000Sglebius#define NMEA_BAUDRATE_SHIFT 4 9154359Sroberto 92290000Sglebius#define NMEA_DELAYMEAS_MASK 0x80 93290000Sglebius#define NMEA_EXTLOG_MASK 0x00010000U 94290000Sglebius#define NMEA_DATETRUST_MASK 0x02000000U 95290000Sglebius 96290000Sglebius#define NMEA_PROTO_IDLEN 5 /* tag name must be at least 5 chars */ 97290000Sglebius#define NMEA_PROTO_MINLEN 6 /* min chars in sentence, excluding CS */ 98290000Sglebius#define NMEA_PROTO_MAXLEN 80 /* max chars in sentence, excluding CS */ 99290000Sglebius#define NMEA_PROTO_FIELDS 32 /* not official; limit on fields per record */ 100290000Sglebius 10154359Sroberto/* 102290000Sglebius * We check the timecode format and decode its contents. We only care 103290000Sglebius * about a few of them, the most important being the $GPRMC format: 104290000Sglebius * 105290000Sglebius * $GPRMC,hhmmss,a,fddmm.xx,n,dddmmm.xx,w,zz.z,yyy.,ddmmyy,dd,v*CC 106290000Sglebius * 107290000Sglebius * mode (0,1,2,3) selects sentence ANY/ALL, RMC, GGA, GLL, ZDA 108290000Sglebius * $GPGLL,3513.8385,S,14900.7851,E,232420.594,A*21 109290000Sglebius * $GPGGA,232420.59,3513.8385,S,14900.7851,E,1,05,3.4,00519,M,,,,*3F 110290000Sglebius * $GPRMC,232418.19,A,3513.8386,S,14900.7853,E,00.0,000.0,121199,12.,E*77 111290000Sglebius * 112290000Sglebius * Defining GPZDA to support Standard Time & Date 113290000Sglebius * sentence. The sentence has the following format 114290000Sglebius * 115290000Sglebius * $--ZDA,HHMMSS.SS,DD,MM,YYYY,TH,TM,*CS<CR><LF> 116290000Sglebius * 117290000Sglebius * Apart from the familiar fields, 118290000Sglebius * 'TH' Time zone Hours 119290000Sglebius * 'TM' Time zone Minutes 120290000Sglebius * 121290000Sglebius * Defining GPZDG to support Accord GPS Clock's custom NMEA 122290000Sglebius * sentence. The sentence has the following format 123290000Sglebius * 124290000Sglebius * $GPZDG,HHMMSS.S,DD,MM,YYYY,AA.BB,V*CS<CR><LF> 125290000Sglebius * 126290000Sglebius * It contains the GPS timestamp valid for next PPS pulse. 127290000Sglebius * Apart from the familiar fields, 128290000Sglebius * 'AA.BB' denotes the signal strength( should be < 05.00 ) 129290000Sglebius * 'V' denotes the GPS sync status : 130290000Sglebius * '0' indicates INVALID time, 131290000Sglebius * '1' indicates accuracy of +/-20 ms 132290000Sglebius * '2' indicates accuracy of +/-100 ns 133290000Sglebius * 134290000Sglebius * Defining PGRMF for Garmin GPS Fix Data 135290000Sglebius * $PGRMF,WN,WS,DATE,TIME,LS,LAT,LAT_DIR,LON,LON_DIR,MODE,FIX,SPD,DIR,PDOP,TDOP 136290000Sglebius * WN -- GPS week number (weeks since 1980-01-06, mod 1024) 137290000Sglebius * WS -- GPS seconds in week 138290000Sglebius * LS -- GPS leap seconds, accumulated ( UTC + LS == GPS ) 139290000Sglebius * FIX -- Fix type: 0=nofix, 1=2D, 2=3D 140290000Sglebius * DATE/TIME are standard date/time strings in UTC time scale 141290000Sglebius * 142290000Sglebius * The GPS time can be used to get the full century for the truncated 143290000Sglebius * date spec. 144290000Sglebius */ 145290000Sglebius 146290000Sglebius/* 14754359Sroberto * Definitions 14854359Sroberto */ 149290000Sglebius#define DEVICE "/dev/gps%d" /* GPS serial device */ 150290000Sglebius#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 */ 156290000Sglebius#ifndef O_NOCTTY 157290000Sglebius#define M_NOCTTY 0 158290000Sglebius#else 159290000Sglebius#define M_NOCTTY O_NOCTTY 160290000Sglebius#endif 161290000Sglebius#ifndef O_NONBLOCK 162290000Sglebius#define M_NONBLOCK 0 163290000Sglebius#else 164290000Sglebius#define M_NONBLOCK O_NONBLOCK 165290000Sglebius#endif 166290000Sglebius#define PPSOPENMODE (O_RDWR | M_NOCTTY | M_NONBLOCK) 16754359Sroberto 168290000Sglebius/* NMEA sentence array indexes for those we use */ 169290000Sglebius#define NMEA_GPRMC 0 /* recommended min. nav. */ 170290000Sglebius#define NMEA_GPGGA 1 /* fix and quality */ 171290000Sglebius#define NMEA_GPGLL 2 /* geo. lat/long */ 172290000Sglebius#define NMEA_GPZDA 3 /* date/time */ 173290000Sglebius/* 174290000Sglebius * $GPZDG is a proprietary sentence that violates the spec, by not 175290000Sglebius * using $P and an assigned company identifier to prefix the sentence 176290000Sglebius * identifier. When used with this driver, the system needs to be 177290000Sglebius * isolated from other NTP networks, as it operates in GPS time, not 178290000Sglebius * UTC as is much more common. GPS time is >15 seconds different from 179290000Sglebius * UTC due to not respecting leap seconds since 1970 or so. Other 180290000Sglebius * than the different timebase, $GPZDG is similar to $GPZDA. 181290000Sglebius */ 182290000Sglebius#define NMEA_GPZDG 4 183290000Sglebius#define NMEA_PGRMF 5 184290000Sglebius#define NMEA_ARRAY_SIZE (NMEA_PGRMF + 1) 18554359Sroberto 18654359Sroberto/* 187290000Sglebius * Sentence selection mode bits 18854359Sroberto */ 189290000Sglebius#define USE_GPRMC 0x00000001u 190290000Sglebius#define USE_GPGGA 0x00000002u 191290000Sglebius#define USE_GPGLL 0x00000004u 192290000Sglebius#define USE_GPZDA 0x00000008u 193290000Sglebius#define USE_PGRMF 0x00000100u 19454359Sroberto 195290000Sglebius/* mapping from sentence index to controlling mode bit */ 196290000Sglebiusstatic const u_int32 sentence_mode[NMEA_ARRAY_SIZE] = 197290000Sglebius{ 198290000Sglebius USE_GPRMC, 199290000Sglebius USE_GPGGA, 200290000Sglebius USE_GPGLL, 201290000Sglebius USE_GPZDA, 202290000Sglebius USE_GPZDA, 203290000Sglebius USE_PGRMF 204290000Sglebius}; 205290000Sglebius 206290000Sglebius/* date formats we support */ 207290000Sglebiusenum date_fmt { 208290000Sglebius DATE_1_DDMMYY, /* use 1 field with 2-digit year */ 209290000Sglebius DATE_3_DDMMYYYY /* use 3 fields with 4-digit year */ 210290000Sglebius}; 211290000Sglebius 212290000Sglebius/* results for 'field_init()' 213290000Sglebius * 214290000Sglebius * Note: If a checksum is present, the checksum test must pass OK or the 215290000Sglebius * sentence is tagged invalid. 216290000Sglebius */ 217290000Sglebius#define CHECK_EMPTY -1 /* no data */ 218290000Sglebius#define CHECK_INVALID 0 /* not a valid NMEA sentence */ 219290000Sglebius#define CHECK_VALID 1 /* valid but without checksum */ 220290000Sglebius#define CHECK_CSVALID 2 /* valid with checksum OK */ 221290000Sglebius 22254359Sroberto/* 22354359Sroberto * Unit control structure 22454359Sroberto */ 225290000Sglebiustypedef struct { 22682498Sroberto#ifdef HAVE_PPSAPI 227290000Sglebius struct refclock_atom atom; /* PPSAPI structure */ 228290000Sglebius int ppsapi_fd; /* fd used with PPSAPI */ 229290000Sglebius u_char ppsapi_tried; /* attempt PPSAPI once */ 230290000Sglebius u_char ppsapi_lit; /* time_pps_create() worked */ 231290000Sglebius u_char ppsapi_gate; /* system is on PPS */ 23282498Sroberto#endif /* HAVE_PPSAPI */ 233290000Sglebius u_char gps_time; /* use GPS time, not UTC */ 234290000Sglebius u_short century_cache; /* cached current century */ 235290000Sglebius l_fp last_reftime; /* last processed reference stamp */ 236290000Sglebius short epoch_warp; /* last epoch warp, for logging */ 237290000Sglebius /* tally stats, reset each poll cycle */ 238290000Sglebius struct 239290000Sglebius { 240290000Sglebius u_int total; 241290000Sglebius u_int accepted; 242290000Sglebius u_int rejected; /* GPS said not enough signal */ 243290000Sglebius u_int malformed; /* Bad checksum, invalid date or time */ 244290000Sglebius u_int filtered; /* mode bits, not GPZDG, same second */ 245290000Sglebius u_int pps_used; 246290000Sglebius } 247290000Sglebius tally; 248290000Sglebius /* per sentence checksum seen flag */ 249290000Sglebius u_char cksum_type[NMEA_ARRAY_SIZE]; 250290000Sglebius} nmea_unit; 25154359Sroberto 25254359Sroberto/* 253290000Sglebius * helper for faster field access 254290000Sglebius */ 255290000Sglebiustypedef struct { 256290000Sglebius char *base; /* buffer base */ 257290000Sglebius char *cptr; /* current field ptr */ 258290000Sglebius int blen; /* buffer length */ 259290000Sglebius int cidx; /* current field index */ 260290000Sglebius} nmea_data; 261290000Sglebius 262290000Sglebius/* 263290000Sglebius * NMEA gps week/time information 264290000Sglebius * This record contains the number of weeks since 1980-01-06 modulo 265290000Sglebius * 1024, the seconds elapsed since start of the week, and the number of 266290000Sglebius * leap seconds that are the difference between GPS and UTC time scale. 267290000Sglebius */ 268290000Sglebiustypedef struct { 269290000Sglebius u_int32 wt_time; /* seconds since weekstart */ 270290000Sglebius u_short wt_week; /* week number */ 271290000Sglebius short wt_leap; /* leap seconds */ 272290000Sglebius} gps_weektm; 273290000Sglebius 274290000Sglebius/* 275290000Sglebius * The GPS week time scale starts on Sunday, 1980-01-06. We need the 276290000Sglebius * rata die number of this day. 277290000Sglebius */ 278290000Sglebius#ifndef DAY_GPS_STARTS 279290000Sglebius#define DAY_GPS_STARTS 722820 280290000Sglebius#endif 281290000Sglebius 282290000Sglebius/* 28354359Sroberto * Function prototypes 28454359Sroberto */ 285290000Sglebiusstatic void nmea_init (void); 286290000Sglebiusstatic int nmea_start (int, struct peer *); 287290000Sglebiusstatic void nmea_shutdown (int, struct peer *); 288290000Sglebiusstatic void nmea_receive (struct recvbuf *); 289290000Sglebiusstatic void nmea_poll (int, struct peer *); 29082498Sroberto#ifdef HAVE_PPSAPI 291290000Sglebiusstatic void nmea_control (int, const struct refclockstat *, 292290000Sglebius struct refclockstat *, struct peer *); 293290000Sglebius#define NMEA_CONTROL nmea_control 294290000Sglebius#else 295290000Sglebius#define NMEA_CONTROL noentry 29682498Sroberto#endif /* HAVE_PPSAPI */ 297290000Sglebiusstatic void nmea_timer (int, struct peer *); 29854359Sroberto 299290000Sglebius/* parsing helpers */ 300290000Sglebiusstatic int field_init (nmea_data * data, char * cp, int len); 301290000Sglebiusstatic char * field_parse (nmea_data * data, int fn); 302290000Sglebiusstatic void field_wipe (nmea_data * data, ...); 303290000Sglebiusstatic u_char parse_qual (nmea_data * data, int idx, 304290000Sglebius char tag, int inv); 305290000Sglebiusstatic int parse_time (struct calendar * jd, long * nsec, 306290000Sglebius nmea_data *, int idx); 307290000Sglebiusstatic int parse_date (struct calendar *jd, nmea_data*, 308290000Sglebius int idx, enum date_fmt fmt); 309290000Sglebiusstatic int parse_weekdata (gps_weektm *, nmea_data *, 310290000Sglebius int weekidx, int timeidx, int leapidx); 311290000Sglebius/* calendar / date helpers */ 312290000Sglebiusstatic int unfold_day (struct calendar * jd, u_int32 rec_ui); 313290000Sglebiusstatic int unfold_century (struct calendar * jd, u_int32 rec_ui); 314290000Sglebiusstatic int gpsfix_century (struct calendar * jd, const gps_weektm * wd, 315290000Sglebius u_short * ccentury); 316290000Sglebiusstatic l_fp eval_gps_time (struct peer * peer, const struct calendar * gpst, 317290000Sglebius const struct timespec * gpso, const l_fp * xrecv); 318290000Sglebius 319290000Sglebiusstatic int nmead_open (const char * device); 320290000Sglebiusstatic void save_ltc (struct refclockproc * const, const char * const, 321290000Sglebius size_t); 322290000Sglebius 32354359Sroberto/* 324290000Sglebius * If we want the driver to ouput sentences, too: re-enable the send 325290000Sglebius * support functions by defining NMEA_WRITE_SUPPORT to non-zero... 326290000Sglebius */ 327290000Sglebius#if NMEA_WRITE_SUPPORT 328290000Sglebius 329290000Sglebiusstatic void gps_send(int, const char *, struct peer *); 330290000Sglebius# ifdef SYS_WINNT 331290000Sglebius# undef write /* ports/winnt/include/config.h: #define write _write */ 332290000Sglebiusextern int async_write(int, const void *, unsigned int); 333290000Sglebius# define write(fd, data, octets) async_write(fd, data, octets) 334290000Sglebius# endif /* SYS_WINNT */ 335290000Sglebius 336290000Sglebius#endif /* NMEA_WRITE_SUPPORT */ 337290000Sglebius 338290000Sglebiusstatic int32_t g_gpsMinBase; 339290000Sglebiusstatic int32_t g_gpsMinYear; 340290000Sglebius 341290000Sglebius/* 342290000Sglebius * ------------------------------------------------------------------- 34354359Sroberto * Transfer vector 344290000Sglebius * ------------------------------------------------------------------- 34554359Sroberto */ 346290000Sglebiusstruct refclock refclock_nmea = { 34754359Sroberto nmea_start, /* start up driver */ 348290000Sglebius nmea_shutdown, /* shut down driver */ 34954359Sroberto nmea_poll, /* transmit poll message */ 350290000Sglebius NMEA_CONTROL, /* fudge control */ 351290000Sglebius nmea_init, /* initialize driver */ 35254359Sroberto noentry, /* buginfo */ 353290000Sglebius nmea_timer /* called once per second */ 35454359Sroberto}; 35554359Sroberto 35654359Sroberto/* 357290000Sglebius * ------------------------------------------------------------------- 358290000Sglebius * nmea_init - initialise data 359290000Sglebius * 360290000Sglebius * calculates a few runtime constants that cannot be made compile time 361290000Sglebius * constants. 362290000Sglebius * ------------------------------------------------------------------- 363290000Sglebius */ 364290000Sglebiusstatic void 365290000Sglebiusnmea_init(void) 366290000Sglebius{ 367290000Sglebius struct calendar date; 368290000Sglebius 369290000Sglebius /* - calculate min. base value for GPS epoch & century unfolding 370290000Sglebius * This assumes that the build system was roughly in sync with 371290000Sglebius * the world, and that really synchronising to a time before the 372290000Sglebius * program was created would be unsafe or insane. If the build 373290000Sglebius * date cannot be stablished, at least use the start of GPS 374290000Sglebius * (1980-01-06) as minimum, because GPS can surely NOT 375290000Sglebius * synchronise beyond it's own big bang. We add a little safety 376290000Sglebius * margin for the fuzziness of the build date, which is in an 377290000Sglebius * undefined time zone. */ 378290000Sglebius if (ntpcal_get_build_date(&date)) 379290000Sglebius g_gpsMinBase = ntpcal_date_to_rd(&date) - 2; 380290000Sglebius else 381290000Sglebius g_gpsMinBase = 0; 382290000Sglebius 383290000Sglebius if (g_gpsMinBase < DAY_GPS_STARTS) 384290000Sglebius g_gpsMinBase = DAY_GPS_STARTS; 385290000Sglebius 386290000Sglebius ntpcal_rd_to_date(&date, g_gpsMinBase); 387290000Sglebius g_gpsMinYear = date.year; 388290000Sglebius g_gpsMinBase -= DAY_NTP_STARTS; 389290000Sglebius} 390290000Sglebius 391290000Sglebius/* 392290000Sglebius * ------------------------------------------------------------------- 39354359Sroberto * nmea_start - open the GPS devices and initialize data for processing 394290000Sglebius * 395290000Sglebius * return 0 on error, 1 on success. Even on error the peer structures 396290000Sglebius * must be in a state that permits 'nmea_shutdown()' to clean up all 397290000Sglebius * resources, because it will be called immediately to do so. 398290000Sglebius * ------------------------------------------------------------------- 39954359Sroberto */ 40054359Srobertostatic int 40154359Srobertonmea_start( 402290000Sglebius int unit, 403290000Sglebius struct peer * peer 40454359Sroberto ) 40554359Sroberto{ 406290000Sglebius struct refclockproc * const pp = peer->procptr; 407290000Sglebius nmea_unit * const up = emalloc_zero(sizeof(*up)); 408290000Sglebius char device[20]; 409290000Sglebius size_t devlen; 410290000Sglebius u_int32 rate; 411290000Sglebius int baudrate; 412290000Sglebius const char * baudtext; 41354359Sroberto 41456746Sroberto 415290000Sglebius /* Get baudrate choice from mode byte bits 4/5/6 */ 416290000Sglebius rate = (peer->ttl & NMEA_BAUDRATE_MASK) >> NMEA_BAUDRATE_SHIFT; 41754359Sroberto 418290000Sglebius switch (rate) { 419290000Sglebius case 0: 420290000Sglebius baudrate = SPEED232; 421290000Sglebius baudtext = "4800"; 422290000Sglebius break; 423290000Sglebius case 1: 424290000Sglebius baudrate = B9600; 425290000Sglebius baudtext = "9600"; 426290000Sglebius break; 427290000Sglebius case 2: 428290000Sglebius baudrate = B19200; 429290000Sglebius baudtext = "19200"; 430290000Sglebius break; 431290000Sglebius case 3: 432290000Sglebius baudrate = B38400; 433290000Sglebius baudtext = "38400"; 434290000Sglebius break; 435290000Sglebius#ifdef B57600 436290000Sglebius case 4: 437290000Sglebius baudrate = B57600; 438290000Sglebius baudtext = "57600"; 439290000Sglebius break; 440182007Sroberto#endif 441290000Sglebius#ifdef B115200 442290000Sglebius case 5: 443290000Sglebius baudrate = B115200; 444290000Sglebius baudtext = "115200"; 445290000Sglebius break; 446290000Sglebius#endif 447290000Sglebius default: 448290000Sglebius baudrate = SPEED232; 449290000Sglebius baudtext = "4800 (fallback)"; 450290000Sglebius break; 451290000Sglebius } 452182007Sroberto 453290000Sglebius /* Allocate and initialize unit structure */ 454290000Sglebius pp->unitptr = (caddr_t)up; 455290000Sglebius pp->io.fd = -1; 45654359Sroberto pp->io.clock_recv = nmea_receive; 457290000Sglebius pp->io.srcclock = peer; 45854359Sroberto pp->io.datalen = 0; 459290000Sglebius /* force change detection on first valid message */ 460290000Sglebius memset(&up->last_reftime, 0xFF, sizeof(up->last_reftime)); 461290000Sglebius /* force checksum on GPRMC, see below */ 462290000Sglebius up->cksum_type[NMEA_GPRMC] = CHECK_CSVALID; 463290000Sglebius#ifdef HAVE_PPSAPI 464290000Sglebius up->ppsapi_fd = -1; 465290000Sglebius#endif 466290000Sglebius ZERO(up->tally); 46754359Sroberto 468290000Sglebius /* Initialize miscellaneous variables */ 46982498Sroberto peer->precision = PRECISION; 47054359Sroberto pp->clockdesc = DESCRIPTION; 471290000Sglebius memcpy(&pp->refid, REFID, 4); 47254359Sroberto 473290000Sglebius /* Open serial port. Use CLK line discipline, if available. */ 474290000Sglebius devlen = snprintf(device, sizeof(device), DEVICE, unit); 475290000Sglebius if (devlen >= sizeof(device)) { 476290000Sglebius msyslog(LOG_ERR, "%s clock device name too long", 477290000Sglebius refnumtoa(&peer->srcadr)); 478290000Sglebius return FALSE; /* buffer overflow */ 47982498Sroberto } 480290000Sglebius pp->io.fd = refclock_open(device, baudrate, LDISC_CLK); 481290000Sglebius if (0 >= pp->io.fd) { 482290000Sglebius pp->io.fd = nmead_open(device); 483290000Sglebius if (-1 == pp->io.fd) 484290000Sglebius return FALSE; 485290000Sglebius } 486290000Sglebius LOGIF(CLOCKINFO, (LOG_NOTICE, "%s serial %s open at %s bps", 487290000Sglebius refnumtoa(&peer->srcadr), device, baudtext)); 488290000Sglebius 489290000Sglebius /* succeed if this clock can be added */ 490290000Sglebius return io_addclock(&pp->io) != 0; 49154359Sroberto} 49254359Sroberto 493290000Sglebius 49454359Sroberto/* 495290000Sglebius * ------------------------------------------------------------------- 49654359Sroberto * nmea_shutdown - shut down a GPS clock 497290000Sglebius * 498290000Sglebius * NOTE this routine is called after nmea_start() returns failure, 499290000Sglebius * as well as during a normal shutdown due to ntpq :config unpeer. 500290000Sglebius * ------------------------------------------------------------------- 50154359Sroberto */ 50254359Srobertostatic void 50354359Srobertonmea_shutdown( 504290000Sglebius int unit, 505290000Sglebius struct peer * peer 50654359Sroberto ) 50754359Sroberto{ 508290000Sglebius struct refclockproc * const pp = peer->procptr; 509290000Sglebius nmea_unit * const up = (nmea_unit *)pp->unitptr; 51054359Sroberto 511290000Sglebius UNUSED_ARG(unit); 512290000Sglebius 513290000Sglebius if (up != NULL) { 51482498Sroberto#ifdef HAVE_PPSAPI 515290000Sglebius if (up->ppsapi_lit) 516290000Sglebius time_pps_destroy(up->atom.handle); 517290000Sglebius if (up->ppsapi_tried && up->ppsapi_fd != pp->io.fd) 518290000Sglebius close(up->ppsapi_fd); 519290000Sglebius#endif 520290000Sglebius free(up); 521290000Sglebius } 522290000Sglebius pp->unitptr = (caddr_t)NULL; 523290000Sglebius if (-1 != pp->io.fd) 524290000Sglebius io_closeclock(&pp->io); 525290000Sglebius pp->io.fd = -1; 52654359Sroberto} 52754359Sroberto 52854359Sroberto/* 529290000Sglebius * ------------------------------------------------------------------- 530290000Sglebius * nmea_control - configure fudge params 531290000Sglebius * ------------------------------------------------------------------- 53282498Sroberto */ 533290000Sglebius#ifdef HAVE_PPSAPI 53482498Srobertostatic void 53582498Srobertonmea_control( 536290000Sglebius int unit, 537290000Sglebius const struct refclockstat * in_st, 538290000Sglebius struct refclockstat * out_st, 539290000Sglebius struct peer * peer 54082498Sroberto ) 54182498Sroberto{ 542290000Sglebius struct refclockproc * const pp = peer->procptr; 543290000Sglebius nmea_unit * const up = (nmea_unit *)pp->unitptr; 54482498Sroberto 545290000Sglebius char device[32]; 546290000Sglebius size_t devlen; 547290000Sglebius 548290000Sglebius UNUSED_ARG(in_st); 549290000Sglebius UNUSED_ARG(out_st); 550290000Sglebius 551290000Sglebius /* 552290000Sglebius * PPS control 553290000Sglebius * 554290000Sglebius * If /dev/gpspps$UNIT can be opened that will be used for 555290000Sglebius * PPSAPI. Otherwise, the GPS serial device /dev/gps$UNIT 556290000Sglebius * already opened is used for PPSAPI as well. (This might not 557290000Sglebius * work, in which case the PPS API remains unavailable...) 558290000Sglebius */ 559290000Sglebius 560290000Sglebius /* Light up the PPSAPI interface if not yet attempted. */ 561290000Sglebius if ((CLK_FLAG1 & pp->sloppyclockflag) && !up->ppsapi_tried) { 562290000Sglebius up->ppsapi_tried = TRUE; 563290000Sglebius devlen = snprintf(device, sizeof(device), PPSDEV, unit); 564290000Sglebius if (devlen < sizeof(device)) { 565290000Sglebius up->ppsapi_fd = open(device, PPSOPENMODE, 566290000Sglebius S_IRUSR | S_IWUSR); 567290000Sglebius } else { 568290000Sglebius up->ppsapi_fd = -1; 569290000Sglebius msyslog(LOG_ERR, "%s PPS device name too long", 570290000Sglebius refnumtoa(&peer->srcadr)); 571290000Sglebius } 572290000Sglebius if (-1 == up->ppsapi_fd) 573290000Sglebius up->ppsapi_fd = pp->io.fd; 574290000Sglebius if (refclock_ppsapi(up->ppsapi_fd, &up->atom)) { 575290000Sglebius /* use the PPS API for our own purposes now. */ 576290000Sglebius up->ppsapi_lit = refclock_params( 577290000Sglebius pp->sloppyclockflag, &up->atom); 578290000Sglebius if (!up->ppsapi_lit) { 579290000Sglebius /* failed to configure, drop PPS unit */ 580290000Sglebius time_pps_destroy(up->atom.handle); 581290000Sglebius msyslog(LOG_WARNING, 582290000Sglebius "%s set PPSAPI params fails", 583290000Sglebius refnumtoa(&peer->srcadr)); 584290000Sglebius } 585290000Sglebius /* note: the PPS I/O handle remains valid until 586290000Sglebius * flag1 is cleared or the clock is shut down. 587290000Sglebius */ 588290000Sglebius } else { 589290000Sglebius msyslog(LOG_WARNING, 590290000Sglebius "%s flag1 1 but PPSAPI fails", 591290000Sglebius refnumtoa(&peer->srcadr)); 592290000Sglebius } 593290000Sglebius } 594290000Sglebius 595290000Sglebius /* shut down PPS API if activated */ 596290000Sglebius if (!(CLK_FLAG1 & pp->sloppyclockflag) && up->ppsapi_tried) { 597290000Sglebius /* shutdown PPS API */ 598290000Sglebius if (up->ppsapi_lit) 599290000Sglebius time_pps_destroy(up->atom.handle); 600290000Sglebius up->atom.handle = 0; 601290000Sglebius /* close/drop PPS fd */ 602290000Sglebius if (up->ppsapi_fd != pp->io.fd) 603290000Sglebius close(up->ppsapi_fd); 604290000Sglebius up->ppsapi_fd = -1; 605290000Sglebius 606290000Sglebius /* clear markers and peer items */ 607290000Sglebius up->ppsapi_gate = FALSE; 608290000Sglebius up->ppsapi_lit = FALSE; 609290000Sglebius up->ppsapi_tried = FALSE; 610290000Sglebius 611290000Sglebius peer->flags &= ~FLAG_PPS; 612290000Sglebius peer->precision = PRECISION; 613290000Sglebius } 61482498Sroberto} 615290000Sglebius#endif /* HAVE_PPSAPI */ 61682498Sroberto 61782498Sroberto/* 618290000Sglebius * ------------------------------------------------------------------- 619290000Sglebius * nmea_timer - called once per second 620290000Sglebius * this only polls (older?) Oncore devices now 621290000Sglebius * 622290000Sglebius * Usually 'nmea_receive()' can get a timestamp every second, but at 623290000Sglebius * least one Motorola unit needs prompting each time. Doing so in 624290000Sglebius * 'nmea_poll()' gives only one sample per poll cycle, which actually 625290000Sglebius * defeats the purpose of the median filter. Polling once per second 626290000Sglebius * seems a much better idea. 627290000Sglebius * ------------------------------------------------------------------- 62882498Sroberto */ 629290000Sglebiusstatic void 630290000Sglebiusnmea_timer( 631290000Sglebius int unit, 632290000Sglebius struct peer * peer 63382498Sroberto ) 63482498Sroberto{ 635290000Sglebius#if NMEA_WRITE_SUPPORT 636290000Sglebius 637290000Sglebius struct refclockproc * const pp = peer->procptr; 63882498Sroberto 639290000Sglebius UNUSED_ARG(unit); 64082498Sroberto 641290000Sglebius if (-1 != pp->io.fd) /* any mode bits to evaluate here? */ 642290000Sglebius gps_send(pp->io.fd, "$PMOTG,RMC,0000*1D\r\n", peer); 643290000Sglebius#else 644290000Sglebius 645290000Sglebius UNUSED_ARG(unit); 646290000Sglebius UNUSED_ARG(peer); 647290000Sglebius 648290000Sglebius#endif /* NMEA_WRITE_SUPPORT */ 64982498Sroberto} 65082498Sroberto 651290000Sglebius#ifdef HAVE_PPSAPI 65282498Sroberto/* 653290000Sglebius * ------------------------------------------------------------------- 654290000Sglebius * refclock_ppsrelate(...) -- correlate with PPS edge 65582498Sroberto * 656290000Sglebius * This function is used to correlate a receive time stamp and a 657290000Sglebius * reference time with a PPS edge time stamp. It applies the necessary 658290000Sglebius * fudges (fudge1 for PPS, fudge2 for receive time) and then tries to 659290000Sglebius * move the receive time stamp to the corresponding edge. This can warp 660290000Sglebius * into future, if a transmission delay of more than 500ms is not 661290000Sglebius * compensated with a corresponding fudge time2 value, because then the 662290000Sglebius * next PPS edge is nearer than the last. (Similiar to what the PPS ATOM 663290000Sglebius * driver does, but we deal with full time stamps here, not just phase 664290000Sglebius * shift information.) Likewise, a negative fudge time2 value must be 665290000Sglebius * used if the reference time stamp correlates with the *following* PPS 666290000Sglebius * pulse. 667290000Sglebius * 668290000Sglebius * Note that the receive time fudge value only needs to move the receive 669290000Sglebius * stamp near a PPS edge but that close proximity is not required; 670290000Sglebius * +/-100ms precision should be enough. But since the fudge value will 671290000Sglebius * probably also be used to compensate the transmission delay when no 672290000Sglebius * PPS edge can be related to the time stamp, it's best to get it as 673290000Sglebius * close as possible. 674290000Sglebius * 675290000Sglebius * It should also be noted that the typical use case is matching to the 676290000Sglebius * preceeding edge, as most units relate their sentences to the current 677290000Sglebius * second. 678290000Sglebius * 679290000Sglebius * The function returns PPS_RELATE_NONE (0) if no PPS edge correlation 680290000Sglebius * can be fixed; PPS_RELATE_EDGE (1) when a PPS edge could be fixed, but 681290000Sglebius * the distance to the reference time stamp is too big (exceeds 682290000Sglebius * +/-400ms) and the ATOM driver PLL cannot be used to fix the phase; 683290000Sglebius * and PPS_RELATE_PHASE (2) when the ATOM driver PLL code can be used. 684290000Sglebius * 685290000Sglebius * On output, the receive time stamp is replaced with the corresponding 686290000Sglebius * PPS edge time if a fix could be made; the PPS fudge is updated to 687290000Sglebius * reflect the proper fudge time to apply. (This implies that 688290000Sglebius * 'refclock_process_offset()' must be used!) 689290000Sglebius * ------------------------------------------------------------------- 69082498Sroberto */ 691290000Sglebius#define PPS_RELATE_NONE 0 /* no pps correlation possible */ 692290000Sglebius#define PPS_RELATE_EDGE 1 /* recv time fixed, no phase lock */ 693290000Sglebius#define PPS_RELATE_PHASE 2 /* recv time fixed, phase lock ok */ 694290000Sglebius 69582498Srobertostatic int 696290000Sglebiusrefclock_ppsrelate( 697290000Sglebius const struct refclockproc * pp , /* for sanity */ 698290000Sglebius const struct refclock_atom * ap , /* for PPS io */ 699290000Sglebius const l_fp * reftime , 700290000Sglebius l_fp * rd_stamp, /* i/o read stamp */ 701290000Sglebius double pp_fudge, /* pps fudge */ 702290000Sglebius double * rd_fudge /* i/o read fudge */ 70382498Sroberto ) 70482498Sroberto{ 705290000Sglebius pps_info_t pps_info; 706290000Sglebius struct timespec timeout; 707290000Sglebius l_fp pp_stamp, pp_delta; 708290000Sglebius double delta, idelta; 70982498Sroberto 710290000Sglebius if (pp->leap == LEAP_NOTINSYNC) 711290000Sglebius return PPS_RELATE_NONE; /* clock is insane, no chance */ 71282498Sroberto 713290000Sglebius ZERO(timeout); 714290000Sglebius ZERO(pps_info); 715290000Sglebius if (time_pps_fetch(ap->handle, PPS_TSFMT_TSPEC, 716290000Sglebius &pps_info, &timeout) < 0) 717290000Sglebius return PPS_RELATE_NONE; /* can't get time stamps */ 718290000Sglebius 719290000Sglebius /* get last active PPS edge before receive */ 720290000Sglebius if (ap->pps_params.mode & PPS_CAPTUREASSERT) 721290000Sglebius timeout = pps_info.assert_timestamp; 722290000Sglebius else if (ap->pps_params.mode & PPS_CAPTURECLEAR) 723290000Sglebius timeout = pps_info.clear_timestamp; 724290000Sglebius else 725290000Sglebius return PPS_RELATE_NONE; /* WHICH edge, please?!? */ 726290000Sglebius 727290000Sglebius /* get delta between receive time and PPS time */ 728290000Sglebius pp_stamp = tspec_stamp_to_lfp(timeout); 729290000Sglebius pp_delta = *rd_stamp; 730290000Sglebius L_SUB(&pp_delta, &pp_stamp); 731290000Sglebius LFPTOD(&pp_delta, delta); 732290000Sglebius delta += pp_fudge - *rd_fudge; 733290000Sglebius if (fabs(delta) > 1.5) 734290000Sglebius return PPS_RELATE_NONE; /* PPS timeout control */ 735290000Sglebius 736290000Sglebius /* eventually warp edges, check phase */ 737290000Sglebius idelta = floor(delta + 0.5); 738290000Sglebius pp_fudge -= idelta; 739290000Sglebius delta -= idelta; 740290000Sglebius if (fabs(delta) > 0.45) 741290000Sglebius return PPS_RELATE_NONE; /* dead band control */ 742290000Sglebius 743290000Sglebius /* we actually have a PPS edge to relate with! */ 744290000Sglebius *rd_stamp = pp_stamp; 745290000Sglebius *rd_fudge = pp_fudge; 746290000Sglebius 747290000Sglebius /* if whole system out-of-sync, do not try to PLL */ 748290000Sglebius if (sys_leap == LEAP_NOTINSYNC) 749290000Sglebius return PPS_RELATE_EDGE; /* cannot PLL with atom code */ 750290000Sglebius 751290000Sglebius /* check against reftime if ATOM PLL can be used */ 752290000Sglebius pp_delta = *reftime; 753290000Sglebius L_SUB(&pp_delta, &pp_stamp); 754290000Sglebius LFPTOD(&pp_delta, delta); 755290000Sglebius delta += pp_fudge; 756290000Sglebius if (fabs(delta) > 0.45) 757290000Sglebius return PPS_RELATE_EDGE; /* cannot PLL with atom code */ 758290000Sglebius 759290000Sglebius /* all checks passed, gets an AAA rating here! */ 760290000Sglebius return PPS_RELATE_PHASE; /* can PLL with atom code */ 76182498Sroberto} 762290000Sglebius#endif /* HAVE_PPSAPI */ 76382498Sroberto 76482498Sroberto/* 765290000Sglebius * ------------------------------------------------------------------- 76654359Sroberto * nmea_receive - receive data from the serial interface 767290000Sglebius * 768290000Sglebius * This is the workhorse for NMEA data evaluation: 769290000Sglebius * 770290000Sglebius * + it checks all NMEA data, and rejects sentences that are not valid 771290000Sglebius * NMEA sentences 772290000Sglebius * + it checks whether a sentence is known and to be used 773290000Sglebius * + it parses the time and date data from the NMEA data string and 774290000Sglebius * augments the missing bits. (century in dat, whole date, ...) 775290000Sglebius * + it rejects data that is not from the first accepted sentence in a 776290000Sglebius * burst 777290000Sglebius * + it eventually replaces the receive time with the PPS edge time. 778290000Sglebius * + it feeds the data to the internal processing stages. 779290000Sglebius * ------------------------------------------------------------------- 78054359Sroberto */ 78154359Srobertostatic void 78254359Srobertonmea_receive( 783290000Sglebius struct recvbuf * rbufp 78454359Sroberto ) 78554359Sroberto{ 786290000Sglebius /* declare & init control structure ptrs */ 787290000Sglebius struct peer * const peer = rbufp->recv_peer; 788290000Sglebius struct refclockproc * const pp = peer->procptr; 789290000Sglebius nmea_unit * const up = (nmea_unit*)pp->unitptr; 790290000Sglebius 79182498Sroberto /* Use these variables to hold data until we decide its worth keeping */ 792290000Sglebius nmea_data rdata; 793290000Sglebius char rd_lastcode[BMAX]; 794290000Sglebius l_fp rd_timestamp, rd_reftime; 795290000Sglebius int rd_lencode; 796290000Sglebius double rd_fudge; 79754359Sroberto 798290000Sglebius /* working stuff */ 799290000Sglebius struct calendar date; /* to keep & convert the time stamp */ 800290000Sglebius struct timespec tofs; /* offset to full-second reftime */ 801290000Sglebius gps_weektm gpsw; /* week time storage */ 802290000Sglebius /* results of sentence/date/time parsing */ 803290000Sglebius u_char sentence; /* sentence tag */ 804290000Sglebius int checkres; 805290000Sglebius char * cp; 806290000Sglebius int rc_date; 807290000Sglebius int rc_time; 80854359Sroberto 809290000Sglebius /* make sure data has defined pristine state */ 810290000Sglebius ZERO(tofs); 811290000Sglebius ZERO(date); 812290000Sglebius ZERO(gpsw); 813290000Sglebius sentence = 0; // Should never be needed. 814290000Sglebius rc_date = 0; // Should never be needed. 815290000Sglebius rc_time = 0; // Should never be needed. 816290000Sglebius 817290000Sglebius /* 818290000Sglebius * Read the timecode and timestamp, then initialise field 819290000Sglebius * processing. The <CR><LF> at the NMEA line end is translated 820290000Sglebius * to <LF><LF> by the terminal input routines on most systems, 821290000Sglebius * and this gives us one spurious empty read per record which we 822290000Sglebius * better ignore silently. 82354359Sroberto */ 824290000Sglebius rd_lencode = refclock_gtlin(rbufp, rd_lastcode, 825290000Sglebius sizeof(rd_lastcode), &rd_timestamp); 826290000Sglebius checkres = field_init(&rdata, rd_lastcode, rd_lencode); 827290000Sglebius switch (checkres) { 82854359Sroberto 829290000Sglebius case CHECK_INVALID: 830290000Sglebius DPRINTF(1, ("%s invalid data: '%s'\n", 831290000Sglebius refnumtoa(&peer->srcadr), rd_lastcode)); 832290000Sglebius refclock_report(peer, CEVNT_BADREPLY); 833290000Sglebius return; 83454359Sroberto 835290000Sglebius case CHECK_EMPTY: 836290000Sglebius return; 837290000Sglebius 838290000Sglebius default: 839290000Sglebius DPRINTF(1, ("%s gpsread: %d '%s'\n", 840290000Sglebius refnumtoa(&peer->srcadr), rd_lencode, 841290000Sglebius rd_lastcode)); 842290000Sglebius break; 843290000Sglebius } 844290000Sglebius up->tally.total++; 845290000Sglebius 846290000Sglebius /* 847290000Sglebius * --> below this point we have a valid NMEA sentence <-- 848290000Sglebius * 849290000Sglebius * Check sentence name. Skip first 2 chars (talker ID) in most 850290000Sglebius * cases, to allow for $GLGGA and $GPGGA etc. Since the name 851290000Sglebius * field has at least 5 chars we can simply shift the field 852290000Sglebius * start. 85354359Sroberto */ 854290000Sglebius cp = field_parse(&rdata, 0); 855290000Sglebius if (strncmp(cp + 2, "RMC,", 4) == 0) 856290000Sglebius sentence = NMEA_GPRMC; 857290000Sglebius else if (strncmp(cp + 2, "GGA,", 4) == 0) 858290000Sglebius sentence = NMEA_GPGGA; 859290000Sglebius else if (strncmp(cp + 2, "GLL,", 4) == 0) 860290000Sglebius sentence = NMEA_GPGLL; 861290000Sglebius else if (strncmp(cp + 2, "ZDA,", 4) == 0) 862290000Sglebius sentence = NMEA_GPZDA; 863290000Sglebius else if (strncmp(cp + 2, "ZDG,", 4) == 0) 864290000Sglebius sentence = NMEA_GPZDG; 865290000Sglebius else if (strncmp(cp, "PGRMF,", 6) == 0) 866290000Sglebius sentence = NMEA_PGRMF; 86754359Sroberto else 868290000Sglebius return; /* not something we know about */ 86954359Sroberto 870290000Sglebius /* Eventually output delay measurement now. */ 871290000Sglebius if (peer->ttl & NMEA_DELAYMEAS_MASK) { 872290000Sglebius mprintf_clock_stats(&peer->srcadr, "delay %0.6f %.*s", 873290000Sglebius ldexp(rd_timestamp.l_uf, -32), 874290000Sglebius (int)(strchr(rd_lastcode, ',') - rd_lastcode), 875290000Sglebius rd_lastcode); 876290000Sglebius } 877290000Sglebius 87882498Sroberto /* See if I want to process this message type */ 879290000Sglebius if ((peer->ttl & NMEA_MESSAGE_MASK) && 880290000Sglebius !(peer->ttl & sentence_mode[sentence])) { 881290000Sglebius up->tally.filtered++; 88282498Sroberto return; 883290000Sglebius } 88482498Sroberto 885290000Sglebius /* 886290000Sglebius * make sure it came in clean 887290000Sglebius * 888290000Sglebius * Apparently, older NMEA specifications (which are expensive) 889290000Sglebius * did not require the checksum for all sentences. $GPMRC is 890290000Sglebius * the only one so far identified which has always been required 891290000Sglebius * to include a checksum. 892290000Sglebius * 893290000Sglebius * Today, most NMEA GPS receivers checksum every sentence. To 894290000Sglebius * preserve its error-detection capabilities with modern GPSes 895290000Sglebius * while allowing operation without checksums on all but $GPMRC, 896290000Sglebius * we keep track of whether we've ever seen a valid checksum on 897290000Sglebius * a given sentence, and if so, reject future instances without 898290000Sglebius * checksum. ('up->cksum_type[NMEA_GPRMC]' is set in 899290000Sglebius * 'nmea_start()' to enforce checksums for $GPRMC right from the 900290000Sglebius * start.) 901290000Sglebius */ 902290000Sglebius if (up->cksum_type[sentence] <= (u_char)checkres) { 903290000Sglebius up->cksum_type[sentence] = (u_char)checkres; 904290000Sglebius } else { 905290000Sglebius DPRINTF(1, ("%s checksum missing: '%s'\n", 906290000Sglebius refnumtoa(&peer->srcadr), rd_lastcode)); 907290000Sglebius refclock_report(peer, CEVNT_BADREPLY); 908290000Sglebius up->tally.malformed++; 909290000Sglebius return; 910290000Sglebius } 91182498Sroberto 912290000Sglebius /* 913290000Sglebius * $GPZDG provides GPS time not UTC, and the two mix poorly. 914290000Sglebius * Once have processed a $GPZDG, do not process any further UTC 915290000Sglebius * sentences (all but $GPZDG currently). 916290000Sglebius */ 917290000Sglebius if (up->gps_time && NMEA_GPZDG != sentence) { 918290000Sglebius up->tally.filtered++; 919290000Sglebius return; 920290000Sglebius } 92182498Sroberto 922290000Sglebius DPRINTF(1, ("%s processing %d bytes, timecode '%s'\n", 923290000Sglebius refnumtoa(&peer->srcadr), rd_lencode, rd_lastcode)); 92482498Sroberto 925290000Sglebius /* 926290000Sglebius * Grab fields depending on clock string type and possibly wipe 927290000Sglebius * sensitive data from the last timecode. 928290000Sglebius */ 929290000Sglebius switch (sentence) { 93082498Sroberto 931290000Sglebius case NMEA_GPRMC: 932290000Sglebius /* Check quality byte, fetch data & time */ 933290000Sglebius rc_time = parse_time(&date, &tofs.tv_nsec, &rdata, 1); 934290000Sglebius pp->leap = parse_qual(&rdata, 2, 'A', 0); 935290000Sglebius rc_date = parse_date(&date, &rdata, 9, DATE_1_DDMMYY) 936290000Sglebius && unfold_century(&date, rd_timestamp.l_ui); 937290000Sglebius if (CLK_FLAG4 & pp->sloppyclockflag) 938290000Sglebius field_wipe(&rdata, 3, 4, 5, 6, -1); 939290000Sglebius break; 94082498Sroberto 941290000Sglebius case NMEA_GPGGA: 942290000Sglebius /* Check quality byte, fetch time only */ 943290000Sglebius rc_time = parse_time(&date, &tofs.tv_nsec, &rdata, 1); 944290000Sglebius pp->leap = parse_qual(&rdata, 6, '0', 1); 945290000Sglebius rc_date = unfold_day(&date, rd_timestamp.l_ui); 946290000Sglebius if (CLK_FLAG4 & pp->sloppyclockflag) 947290000Sglebius field_wipe(&rdata, 2, 4, -1); 94882498Sroberto break; 94982498Sroberto 950290000Sglebius case NMEA_GPGLL: 951290000Sglebius /* Check quality byte, fetch time only */ 952290000Sglebius rc_time = parse_time(&date, &tofs.tv_nsec, &rdata, 5); 953290000Sglebius pp->leap = parse_qual(&rdata, 6, 'A', 0); 954290000Sglebius rc_date = unfold_day(&date, rd_timestamp.l_ui); 955290000Sglebius if (CLK_FLAG4 & pp->sloppyclockflag) 956290000Sglebius field_wipe(&rdata, 1, 3, -1); 957290000Sglebius break; 958290000Sglebius 959290000Sglebius case NMEA_GPZDA: 960290000Sglebius /* No quality. Assume best, fetch time & full date */ 961290000Sglebius pp->leap = LEAP_NOWARNING; 962290000Sglebius rc_time = parse_time(&date, &tofs.tv_nsec, &rdata, 1); 963290000Sglebius rc_date = parse_date(&date, &rdata, 2, DATE_3_DDMMYYYY); 964290000Sglebius break; 96582498Sroberto 966290000Sglebius case NMEA_GPZDG: 967290000Sglebius /* Check quality byte, fetch time & full date */ 968290000Sglebius rc_time = parse_time(&date, &tofs.tv_nsec, &rdata, 1); 969290000Sglebius rc_date = parse_date(&date, &rdata, 2, DATE_3_DDMMYYYY); 970290000Sglebius pp->leap = parse_qual(&rdata, 4, '0', 1); 971290000Sglebius tofs.tv_sec = -1; /* GPZDG is following second */ 97282498Sroberto break; 97382498Sroberto 974290000Sglebius case NMEA_PGRMF: 975290000Sglebius /* get date, time, qualifier and GPS weektime. We need 976290000Sglebius * date and time-of-day for the century fix, so we read 977290000Sglebius * them first. 97882498Sroberto */ 979290000Sglebius rc_date = parse_weekdata(&gpsw, &rdata, 1, 2, 5) 980290000Sglebius && parse_date(&date, &rdata, 3, DATE_1_DDMMYY); 981290000Sglebius rc_time = parse_time(&date, &tofs.tv_nsec, &rdata, 4); 982290000Sglebius pp->leap = parse_qual(&rdata, 11, '0', 1); 983290000Sglebius rc_date = rc_date 984290000Sglebius && gpsfix_century(&date, &gpsw, &up->century_cache); 985290000Sglebius if (CLK_FLAG4 & pp->sloppyclockflag) 986290000Sglebius field_wipe(&rdata, 6, 8, -1); 98782498Sroberto break; 988290000Sglebius 989290000Sglebius default: 990290000Sglebius INVARIANT(0); /* Coverity 97123 */ 991290000Sglebius return; 992290000Sglebius } 99382498Sroberto 994290000Sglebius /* Check sanity of time-of-day. */ 995290000Sglebius if (rc_time == 0) { /* no time or conversion error? */ 996290000Sglebius checkres = CEVNT_BADTIME; 997290000Sglebius up->tally.malformed++; 998290000Sglebius } 999290000Sglebius /* Check sanity of date. */ 1000290000Sglebius else if (rc_date == 0) {/* no date or conversion error? */ 1001290000Sglebius checkres = CEVNT_BADDATE; 1002290000Sglebius up->tally.malformed++; 1003290000Sglebius } 1004290000Sglebius /* check clock sanity; [bug 2143] */ 1005290000Sglebius else if (pp->leap == LEAP_NOTINSYNC) { /* no good status? */ 1006290000Sglebius checkres = CEVNT_BADREPLY; 1007290000Sglebius up->tally.rejected++; 1008290000Sglebius } 1009290000Sglebius else 1010290000Sglebius checkres = -1; 101182498Sroberto 1012290000Sglebius if (checkres != -1) { 1013290000Sglebius save_ltc(pp, rd_lastcode, rd_lencode); 1014290000Sglebius refclock_report(peer, checkres); 101582498Sroberto return; 1016290000Sglebius } 101782498Sroberto 1018290000Sglebius DPRINTF(1, ("%s effective timecode: %04u-%02u-%02u %02d:%02d:%02d\n", 1019290000Sglebius refnumtoa(&peer->srcadr), 1020290000Sglebius date.year, date.month, date.monthday, 1021290000Sglebius date.hour, date.minute, date.second)); 1022290000Sglebius 1023290000Sglebius /* Check if we must enter GPS time mode; log so if we do */ 1024290000Sglebius if (!up->gps_time && (sentence == NMEA_GPZDG)) { 1025290000Sglebius msyslog(LOG_INFO, "%s using GPS time as if it were UTC", 1026290000Sglebius refnumtoa(&peer->srcadr)); 1027290000Sglebius up->gps_time = 1; 102882498Sroberto } 1029290000Sglebius 1030290000Sglebius /* 1031290000Sglebius * Get the reference time stamp from the calendar buffer. 1032290000Sglebius * Process the new sample in the median filter and determine the 1033290000Sglebius * timecode timestamp, but only if the PPS is not in control. 1034290000Sglebius * Discard sentence if reference time did not change. 1035290000Sglebius */ 1036290000Sglebius rd_reftime = eval_gps_time(peer, &date, &tofs, &rd_timestamp); 1037290000Sglebius if (L_ISEQU(&up->last_reftime, &rd_reftime)) { 1038290000Sglebius /* Do not touch pp->a_lastcode on purpose! */ 1039290000Sglebius up->tally.filtered++; 1040290000Sglebius return; 1041290000Sglebius } 1042290000Sglebius up->last_reftime = rd_reftime; 1043290000Sglebius rd_fudge = pp->fudgetime2; 104482498Sroberto 1045290000Sglebius DPRINTF(1, ("%s using '%s'\n", 1046290000Sglebius refnumtoa(&peer->srcadr), rd_lastcode)); 104754359Sroberto 1048290000Sglebius /* Data will be accepted. Update stats & log data. */ 1049290000Sglebius up->tally.accepted++; 1050290000Sglebius save_ltc(pp, rd_lastcode, rd_lencode); 1051290000Sglebius pp->lastrec = rd_timestamp; 1052290000Sglebius 1053290000Sglebius#ifdef HAVE_PPSAPI 1054290000Sglebius /* 1055290000Sglebius * If we have PPS running, we try to associate the sentence 1056290000Sglebius * with the last active edge of the PPS signal. 1057290000Sglebius */ 1058290000Sglebius if (up->ppsapi_lit) 1059290000Sglebius switch (refclock_ppsrelate( 1060290000Sglebius pp, &up->atom, &rd_reftime, &rd_timestamp, 1061290000Sglebius pp->fudgetime1, &rd_fudge)) 1062290000Sglebius { 1063290000Sglebius case PPS_RELATE_PHASE: 1064290000Sglebius up->ppsapi_gate = TRUE; 1065290000Sglebius peer->precision = PPS_PRECISION; 1066290000Sglebius peer->flags |= FLAG_PPS; 1067290000Sglebius DPRINTF(2, ("%s PPS_RELATE_PHASE\n", 1068290000Sglebius refnumtoa(&peer->srcadr))); 1069290000Sglebius up->tally.pps_used++; 1070290000Sglebius break; 1071290000Sglebius 1072290000Sglebius case PPS_RELATE_EDGE: 1073290000Sglebius up->ppsapi_gate = TRUE; 1074290000Sglebius peer->precision = PPS_PRECISION; 1075290000Sglebius DPRINTF(2, ("%s PPS_RELATE_EDGE\n", 1076290000Sglebius refnumtoa(&peer->srcadr))); 1077290000Sglebius break; 1078290000Sglebius 1079290000Sglebius case PPS_RELATE_NONE: 1080290000Sglebius default: 1081290000Sglebius /* 1082290000Sglebius * Resetting precision and PPS flag is done in 1083290000Sglebius * 'nmea_poll', since it might be a glitch. But 1084290000Sglebius * at the end of the poll cycle we know... 1085290000Sglebius */ 1086290000Sglebius DPRINTF(2, ("%s PPS_RELATE_NONE\n", 1087290000Sglebius refnumtoa(&peer->srcadr))); 1088290000Sglebius break; 108954359Sroberto } 1090290000Sglebius#endif /* HAVE_PPSAPI */ 109154359Sroberto 1092290000Sglebius refclock_process_offset(pp, rd_reftime, rd_timestamp, rd_fudge); 1093290000Sglebius} 109482498Sroberto 1095290000Sglebius 1096290000Sglebius/* 1097290000Sglebius * ------------------------------------------------------------------- 1098290000Sglebius * nmea_poll - called by the transmit procedure 1099290000Sglebius * 1100290000Sglebius * Does the necessary bookkeeping stuff to keep the reported state of 1101290000Sglebius * the clock in sync with reality. 1102290000Sglebius * 1103290000Sglebius * We go to great pains to avoid changing state here, since there may 1104290000Sglebius * be more than one eavesdropper receiving the same timecode. 1105290000Sglebius * ------------------------------------------------------------------- 1106290000Sglebius */ 1107290000Sglebiusstatic void 1108290000Sglebiusnmea_poll( 1109290000Sglebius int unit, 1110290000Sglebius struct peer * peer 1111290000Sglebius ) 1112290000Sglebius{ 1113290000Sglebius struct refclockproc * const pp = peer->procptr; 1114290000Sglebius nmea_unit * const up = (nmea_unit *)pp->unitptr; 1115290000Sglebius 111682498Sroberto /* 1117290000Sglebius * Process median filter samples. If none received, declare a 1118290000Sglebius * timeout and keep going. 111982498Sroberto */ 1120290000Sglebius#ifdef HAVE_PPSAPI 1121290000Sglebius /* 1122290000Sglebius * If we don't have PPS pulses and time stamps, turn PPS down 1123290000Sglebius * for now. 1124290000Sglebius */ 1125290000Sglebius if (!up->ppsapi_gate) { 1126290000Sglebius peer->flags &= ~FLAG_PPS; 1127290000Sglebius peer->precision = PRECISION; 1128290000Sglebius } else { 1129290000Sglebius up->ppsapi_gate = FALSE; 1130290000Sglebius } 1131290000Sglebius#endif /* HAVE_PPSAPI */ 1132290000Sglebius 1133290000Sglebius /* 1134290000Sglebius * If the median filter is empty, claim a timeout. Else process 1135290000Sglebius * the input data and keep the stats going. 1136290000Sglebius */ 1137290000Sglebius if (pp->coderecv == pp->codeproc) { 1138290000Sglebius refclock_report(peer, CEVNT_TIMEOUT); 1139290000Sglebius } else { 1140290000Sglebius pp->polls++; 1141290000Sglebius pp->lastref = pp->lastrec; 1142290000Sglebius refclock_receive(peer); 1143290000Sglebius } 1144290000Sglebius 1145290000Sglebius /* 1146290000Sglebius * If extended logging is required, write the tally stats to the 1147290000Sglebius * clockstats file; otherwise just do a normal clock stats 1148290000Sglebius * record. Clear the tally stats anyway. 114982498Sroberto */ 1150290000Sglebius if (peer->ttl & NMEA_EXTLOG_MASK) { 1151290000Sglebius /* Log & reset counters with extended logging */ 1152290000Sglebius const char *nmea = pp->a_lastcode; 1153290000Sglebius if (*nmea == '\0') nmea = "(none)"; 1154290000Sglebius mprintf_clock_stats( 1155290000Sglebius &peer->srcadr, "%s %u %u %u %u %u %u", 1156290000Sglebius nmea, 1157290000Sglebius up->tally.total, up->tally.accepted, 1158290000Sglebius up->tally.rejected, up->tally.malformed, 1159290000Sglebius up->tally.filtered, up->tally.pps_used); 1160290000Sglebius } else { 1161290000Sglebius record_clock_stats(&peer->srcadr, pp->a_lastcode); 116282498Sroberto } 1163290000Sglebius ZERO(up->tally); 1164290000Sglebius} 116582498Sroberto 1166290000Sglebius/* 1167290000Sglebius * ------------------------------------------------------------------- 1168290000Sglebius * Save the last timecode string, making sure it's properly truncated 1169290000Sglebius * if necessary and NUL terminated in any case. 1170290000Sglebius */ 1171290000Sglebiusstatic void 1172290000Sglebiussave_ltc( 1173290000Sglebius struct refclockproc * const pp, 1174290000Sglebius const char * const tc, 1175290000Sglebius size_t len 1176290000Sglebius ) 1177290000Sglebius{ 1178290000Sglebius if (len >= sizeof(pp->a_lastcode)) 1179290000Sglebius len = sizeof(pp->a_lastcode) - 1; 1180290000Sglebius pp->lencode = (u_short)len; 1181290000Sglebius memcpy(pp->a_lastcode, tc, len); 1182290000Sglebius pp->a_lastcode[len] = '\0'; 1183290000Sglebius} 1184290000Sglebius 1185290000Sglebius 1186290000Sglebius#if NMEA_WRITE_SUPPORT 1187290000Sglebius/* 1188290000Sglebius * ------------------------------------------------------------------- 1189290000Sglebius * gps_send(fd, cmd, peer) Sends a command to the GPS receiver. 1190290000Sglebius * as in gps_send(fd, "rqts,u", peer); 1191290000Sglebius * 1192290000Sglebius * If 'cmd' starts with a '$' it is assumed that this command is in raw 1193290000Sglebius * format, that is, starts with '$', ends with '<cr><lf>' and that any 1194290000Sglebius * checksum is correctly provided; the command will be send 'as is' in 1195290000Sglebius * that case. Otherwise the function will create the necessary frame 1196290000Sglebius * (start char, chksum, final CRLF) on the fly. 1197290000Sglebius * 1198290000Sglebius * We don't currently send any data, but would like to send RTCM SC104 1199290000Sglebius * messages for differential positioning. It should also give us better 1200290000Sglebius * time. Without a PPS output, we're Just fooling ourselves because of 1201290000Sglebius * the serial code paths 1202290000Sglebius * ------------------------------------------------------------------- 1203290000Sglebius */ 1204290000Sglebiusstatic void 1205290000Sglebiusgps_send( 1206290000Sglebius int fd, 1207290000Sglebius const char * cmd, 1208290000Sglebius struct peer * peer 1209290000Sglebius ) 1210290000Sglebius{ 1211290000Sglebius /* $...*xy<CR><LF><NUL> add 7 */ 1212290000Sglebius char buf[NMEA_PROTO_MAXLEN + 7]; 1213290000Sglebius int len; 1214290000Sglebius u_char dcs; 1215290000Sglebius const u_char *beg, *end; 1216290000Sglebius 1217290000Sglebius if (*cmd != '$') { 1218290000Sglebius /* get checksum and length */ 1219290000Sglebius beg = end = (const u_char*)cmd; 1220290000Sglebius dcs = 0; 1221290000Sglebius while (*end >= ' ' && *end != '*') 1222290000Sglebius dcs ^= *end++; 1223290000Sglebius len = end - beg; 1224290000Sglebius /* format into output buffer with overflow check */ 1225290000Sglebius len = snprintf(buf, sizeof(buf), "$%.*s*%02X\r\n", 1226290000Sglebius len, beg, dcs); 1227290000Sglebius if ((size_t)len >= sizeof(buf)) { 1228290000Sglebius DPRINTF(1, ("%s gps_send: buffer overflow for command '%s'\n", 1229290000Sglebius refnumtoa(&peer->srcadr), cmd)); 1230290000Sglebius return; /* game over player 1 */ 1231290000Sglebius } 1232290000Sglebius cmd = buf; 1233290000Sglebius } else { 1234290000Sglebius len = strlen(cmd); 123554359Sroberto } 123654359Sroberto 1237290000Sglebius DPRINTF(1, ("%s gps_send: '%.*s'\n", refnumtoa(&peer->srcadr), 1238290000Sglebius len - 2, cmd)); 123982498Sroberto 1240290000Sglebius /* send out the whole stuff */ 1241290000Sglebius if (write(fd, cmd, len) == -1) 1242290000Sglebius refclock_report(peer, CEVNT_FAULT); 1243290000Sglebius} 1244290000Sglebius#endif /* NMEA_WRITE_SUPPORT */ 1245290000Sglebius 1246290000Sglebius/* 1247290000Sglebius * ------------------------------------------------------------------- 1248290000Sglebius * helpers for faster field splitting 1249290000Sglebius * ------------------------------------------------------------------- 1250290000Sglebius * 1251290000Sglebius * set up a field record, check syntax and verify checksum 1252290000Sglebius * 1253290000Sglebius * format is $XXXXX,1,2,3,4*ML 1254290000Sglebius * 1255290000Sglebius * 8-bit XOR of characters between $ and * noninclusive is transmitted 1256290000Sglebius * in last two chars M and L holding most and least significant nibbles 1257290000Sglebius * in hex representation such as: 1258290000Sglebius * 1259290000Sglebius * $GPGLL,5057.970,N,00146.110,E,142451,A*27 1260290000Sglebius * $GPVTG,089.0,T,,,15.2,N,,*7F 1261290000Sglebius * 1262290000Sglebius * Some other constraints: 1263290000Sglebius * + The field name must at least 5 upcase characters or digits and must 1264290000Sglebius * start with a character. 1265290000Sglebius * + The checksum (if present) must be uppercase hex digits. 1266290000Sglebius * + The length of a sentence is limited to 80 characters (not including 1267290000Sglebius * the final CR/LF nor the checksum, but including the leading '$') 1268290000Sglebius * 1269290000Sglebius * Return values: 1270290000Sglebius * + CHECK_INVALID 1271290000Sglebius * The data does not form a valid NMEA sentence or a checksum error 1272290000Sglebius * occurred. 1273290000Sglebius * + CHECK_VALID 1274290000Sglebius * The data is a valid NMEA sentence but contains no checksum. 1275290000Sglebius * + CHECK_CSVALID 1276290000Sglebius * The data is a valid NMEA sentence and passed the checksum test. 1277290000Sglebius * ------------------------------------------------------------------- 1278290000Sglebius */ 1279290000Sglebiusstatic int 1280290000Sglebiusfield_init( 1281290000Sglebius nmea_data * data, /* context structure */ 1282290000Sglebius char * cptr, /* start of raw data */ 1283290000Sglebius int dlen /* data len, not counting trailing NUL */ 1284290000Sglebius ) 1285290000Sglebius{ 1286290000Sglebius u_char cs_l; /* checksum local computed */ 1287290000Sglebius u_char cs_r; /* checksum remote given */ 1288290000Sglebius char * eptr; /* buffer end end pointer */ 1289290000Sglebius char tmp; /* char buffer */ 1290290000Sglebius 1291290000Sglebius cs_l = 0; 1292290000Sglebius cs_r = 0; 1293290000Sglebius /* some basic input constraints */ 1294290000Sglebius if (dlen < 0) 1295290000Sglebius dlen = 0; 1296290000Sglebius eptr = cptr + dlen; 1297290000Sglebius *eptr = '\0'; 1298290000Sglebius 1299290000Sglebius /* load data context */ 1300290000Sglebius data->base = cptr; 1301290000Sglebius data->cptr = cptr; 1302290000Sglebius data->cidx = 0; 1303290000Sglebius data->blen = dlen; 1304290000Sglebius 1305290000Sglebius /* syntax check follows here. check allowed character 1306290000Sglebius * sequences, updating the local computed checksum as we go. 1307290000Sglebius * 1308290000Sglebius * regex equiv: '^\$[A-Z][A-Z0-9]{4,}[^*]*(\*[0-9A-F]{2})?$' 130954359Sroberto */ 1310290000Sglebius 1311290000Sglebius /* -*- start character: '^\$' */ 1312290000Sglebius if (*cptr == '\0') 1313290000Sglebius return CHECK_EMPTY; 1314290000Sglebius if (*cptr++ != '$') 1315290000Sglebius return CHECK_INVALID; 1316290000Sglebius 1317290000Sglebius /* -*- advance context beyond start character */ 1318290000Sglebius data->base++; 1319290000Sglebius data->cptr++; 1320290000Sglebius data->blen--; 1321290000Sglebius 1322290000Sglebius /* -*- field name: '[A-Z][A-Z0-9]{4,},' */ 1323290000Sglebius if (*cptr < 'A' || *cptr > 'Z') 1324290000Sglebius return CHECK_INVALID; 1325290000Sglebius cs_l ^= *cptr++; 1326290000Sglebius while ((*cptr >= 'A' && *cptr <= 'Z') || 1327290000Sglebius (*cptr >= '0' && *cptr <= '9') ) 1328290000Sglebius cs_l ^= *cptr++; 1329290000Sglebius if (*cptr != ',' || (cptr - data->base) < NMEA_PROTO_IDLEN) 1330290000Sglebius return CHECK_INVALID; 1331290000Sglebius cs_l ^= *cptr++; 1332290000Sglebius 1333290000Sglebius /* -*- data: '[^*]*' */ 1334290000Sglebius while (*cptr && *cptr != '*') 1335290000Sglebius cs_l ^= *cptr++; 1336290000Sglebius 1337290000Sglebius /* -*- checksum field: (\*[0-9A-F]{2})?$ */ 1338290000Sglebius if (*cptr == '\0') 1339290000Sglebius return CHECK_VALID; 1340290000Sglebius if (*cptr != '*' || cptr != eptr - 3 || 1341290000Sglebius (cptr - data->base) >= NMEA_PROTO_MAXLEN) 1342290000Sglebius return CHECK_INVALID; 1343290000Sglebius 1344290000Sglebius for (cptr++; (tmp = *cptr) != '\0'; cptr++) { 1345290000Sglebius if (tmp >= '0' && tmp <= '9') 1346290000Sglebius cs_r = (cs_r << 4) + (tmp - '0'); 1347290000Sglebius else if (tmp >= 'A' && tmp <= 'F') 1348290000Sglebius cs_r = (cs_r << 4) + (tmp - 'A' + 10); 1349290000Sglebius else 1350290000Sglebius break; 135154359Sroberto } 1352290000Sglebius 1353290000Sglebius /* -*- make sure we are at end of string and csum matches */ 1354290000Sglebius if (cptr != eptr || cs_l != cs_r) 1355290000Sglebius return CHECK_INVALID; 1356290000Sglebius 1357290000Sglebius return CHECK_CSVALID; 1358290000Sglebius} 1359290000Sglebius 1360290000Sglebius/* 1361290000Sglebius * ------------------------------------------------------------------- 1362290000Sglebius * fetch a data field by index, zero being the name field. If this 1363290000Sglebius * function is called repeatedly with increasing indices, the total load 1364290000Sglebius * is O(n), n being the length of the string; if it is called with 1365290000Sglebius * decreasing indices, the total load is O(n^2). Try not to go backwards 1366290000Sglebius * too often. 1367290000Sglebius * ------------------------------------------------------------------- 1368290000Sglebius */ 1369290000Sglebiusstatic char * 1370290000Sglebiusfield_parse( 1371290000Sglebius nmea_data * data, 1372290000Sglebius int fn 1373290000Sglebius ) 1374290000Sglebius{ 1375290000Sglebius char tmp; 1376290000Sglebius 1377290000Sglebius if (fn < data->cidx) { 1378290000Sglebius data->cidx = 0; 1379290000Sglebius data->cptr = data->base; 138082498Sroberto } 1381290000Sglebius while ((fn > data->cidx) && (tmp = *data->cptr) != '\0') { 1382290000Sglebius data->cidx += (tmp == ','); 1383290000Sglebius data->cptr++; 1384290000Sglebius } 1385290000Sglebius return data->cptr; 1386290000Sglebius} 138754359Sroberto 1388290000Sglebius/* 1389290000Sglebius * ------------------------------------------------------------------- 1390290000Sglebius * Wipe (that is, overwrite with '_') data fields and the checksum in 1391290000Sglebius * the last timecode. The list of field indices is given as integers 1392290000Sglebius * in a varargs list, preferrably in ascending order, in any case 1393290000Sglebius * terminated by a negative field index. 1394290000Sglebius * 1395290000Sglebius * A maximum number of 8 fields can be overwritten at once to guard 1396290000Sglebius * against runaway (that is, unterminated) argument lists. 1397290000Sglebius * 1398290000Sglebius * This function affects what a remote user can see with 1399290000Sglebius * 1400290000Sglebius * ntpq -c clockvar <server> 1401290000Sglebius * 1402290000Sglebius * Note that this also removes the wiped fields from any clockstats 1403290000Sglebius * log. Some NTP operators monitor their NMEA GPS using the change in 1404290000Sglebius * location in clockstats over time as as a proxy for the quality of 1405290000Sglebius * GPS reception and thereby time reported. 1406290000Sglebius * ------------------------------------------------------------------- 1407290000Sglebius */ 1408290000Sglebiusstatic void 1409290000Sglebiusfield_wipe( 1410290000Sglebius nmea_data * data, 1411290000Sglebius ... 1412290000Sglebius ) 1413290000Sglebius{ 1414290000Sglebius va_list va; /* vararg index list */ 1415290000Sglebius int fcnt; /* safeguard against runaway arglist */ 1416290000Sglebius int fidx; /* field to nuke, or -1 for checksum */ 1417290000Sglebius char * cp; /* overwrite destination */ 1418290000Sglebius 1419290000Sglebius fcnt = 8; 1420290000Sglebius cp = NULL; 1421290000Sglebius va_start(va, data); 1422290000Sglebius do { 1423290000Sglebius fidx = va_arg(va, int); 1424290000Sglebius if (fidx >= 0 && fidx <= NMEA_PROTO_FIELDS) { 1425290000Sglebius cp = field_parse(data, fidx); 1426290000Sglebius } else { 1427290000Sglebius cp = data->base + data->blen; 1428290000Sglebius if (data->blen >= 3 && cp[-3] == '*') 1429290000Sglebius cp -= 2; 1430290000Sglebius } 1431290000Sglebius for ( ; '\0' != *cp && '*' != *cp && ',' != *cp; cp++) 1432290000Sglebius if ('.' != *cp) 1433290000Sglebius *cp = '_'; 1434290000Sglebius } while (fcnt-- && fidx >= 0); 1435290000Sglebius va_end(va); 1436290000Sglebius} 1437290000Sglebius 1438290000Sglebius/* 1439290000Sglebius * ------------------------------------------------------------------- 1440290000Sglebius * PARSING HELPERS 1441290000Sglebius * ------------------------------------------------------------------- 1442290000Sglebius * 1443290000Sglebius * Check sync status 1444290000Sglebius * 1445290000Sglebius * If the character at the data field start matches the tag value, 1446290000Sglebius * return LEAP_NOWARNING and LEAP_NOTINSYNC otherwise. If the 'inverted' 1447290000Sglebius * flag is given, just the opposite value is returned. If there is no 1448290000Sglebius * data field (*cp points to the NUL byte) the result is LEAP_NOTINSYNC. 1449290000Sglebius * ------------------------------------------------------------------- 1450290000Sglebius */ 1451290000Sglebiusstatic u_char 1452290000Sglebiusparse_qual( 1453290000Sglebius nmea_data * rd, 1454290000Sglebius int idx, 1455290000Sglebius char tag, 1456290000Sglebius int inv 1457290000Sglebius ) 1458290000Sglebius{ 1459290000Sglebius static const u_char table[2] = 1460290000Sglebius { LEAP_NOTINSYNC, LEAP_NOWARNING }; 1461290000Sglebius char * dp; 1462290000Sglebius 1463290000Sglebius dp = field_parse(rd, idx); 1464290000Sglebius 1465290000Sglebius return table[ *dp && ((*dp == tag) == !inv) ]; 1466290000Sglebius} 1467290000Sglebius 1468290000Sglebius/* 1469290000Sglebius * ------------------------------------------------------------------- 1470290000Sglebius * Parse a time stamp in HHMMSS[.sss] format with error checking. 1471290000Sglebius * 1472290000Sglebius * returns 1 on success, 0 on failure 1473290000Sglebius * ------------------------------------------------------------------- 1474290000Sglebius */ 1475290000Sglebiusstatic int 1476290000Sglebiusparse_time( 1477290000Sglebius struct calendar * jd, /* result calendar pointer */ 1478290000Sglebius long * ns, /* storage for nsec fraction */ 1479290000Sglebius nmea_data * rd, 1480290000Sglebius int idx 1481290000Sglebius ) 1482290000Sglebius{ 1483290000Sglebius static const unsigned long weight[4] = { 1484290000Sglebius 0, 100000000, 10000000, 1000000 1485290000Sglebius }; 1486290000Sglebius 1487290000Sglebius int rc; 1488290000Sglebius u_int h; 1489290000Sglebius u_int m; 1490290000Sglebius u_int s; 1491290000Sglebius int p1; 1492290000Sglebius int p2; 1493290000Sglebius u_long f; 1494290000Sglebius char * dp; 1495290000Sglebius 1496290000Sglebius dp = field_parse(rd, idx); 1497290000Sglebius rc = sscanf(dp, "%2u%2u%2u%n.%3lu%n", &h, &m, &s, &p1, &f, &p2); 1498290000Sglebius if (rc < 3 || p1 != 6) { 1499290000Sglebius DPRINTF(1, ("nmea: invalid time code: '%.6s'\n", dp)); 1500290000Sglebius return FALSE; 150154359Sroberto } 1502290000Sglebius 1503290000Sglebius /* value sanity check */ 1504290000Sglebius if (h > 23 || m > 59 || s > 60) { 1505290000Sglebius DPRINTF(1, ("nmea: invalid time spec %02u:%02u:%02u\n", 1506290000Sglebius h, m, s)); 1507290000Sglebius return FALSE; 1508290000Sglebius } 150954359Sroberto 1510290000Sglebius jd->hour = (u_char)h; 1511290000Sglebius jd->minute = (u_char)m; 1512290000Sglebius jd->second = (u_char)s; 1513290000Sglebius /* if we have a fraction, scale it up to nanoseconds. */ 1514290000Sglebius if (rc == 4) 1515290000Sglebius *ns = f * weight[p2 - p1 - 1]; 1516290000Sglebius else 1517290000Sglebius *ns = 0; 1518290000Sglebius 1519290000Sglebius return TRUE; 1520290000Sglebius} 1521290000Sglebius 1522290000Sglebius/* 1523290000Sglebius * ------------------------------------------------------------------- 1524290000Sglebius * Parse a date string from an NMEA sentence. This could either be a 1525290000Sglebius * partial date in DDMMYY format in one field, or DD,MM,YYYY full date 1526290000Sglebius * spec spanning three fields. This function does some extensive error 1527290000Sglebius * checking to make sure the date string was consistent. 1528290000Sglebius * 1529290000Sglebius * returns 1 on success, 0 on failure 1530290000Sglebius * ------------------------------------------------------------------- 1531290000Sglebius */ 1532290000Sglebiusstatic int 1533290000Sglebiusparse_date( 1534290000Sglebius struct calendar * jd, /* result pointer */ 1535290000Sglebius nmea_data * rd, 1536290000Sglebius int idx, 1537290000Sglebius enum date_fmt fmt 1538290000Sglebius ) 1539290000Sglebius{ 1540290000Sglebius int rc; 1541290000Sglebius u_int y; 1542290000Sglebius u_int m; 1543290000Sglebius u_int d; 1544290000Sglebius int p; 1545290000Sglebius char * dp; 1546290000Sglebius 1547290000Sglebius dp = field_parse(rd, idx); 1548290000Sglebius switch (fmt) { 1549290000Sglebius 1550290000Sglebius case DATE_1_DDMMYY: 1551290000Sglebius rc = sscanf(dp, "%2u%2u%2u%n", &d, &m, &y, &p); 1552290000Sglebius if (rc != 3 || p != 6) { 1553290000Sglebius DPRINTF(1, ("nmea: invalid date code: '%.6s'\n", 1554290000Sglebius dp)); 1555290000Sglebius return FALSE; 155654359Sroberto } 1557290000Sglebius break; 1558290000Sglebius 1559290000Sglebius case DATE_3_DDMMYYYY: 1560290000Sglebius rc = sscanf(dp, "%2u,%2u,%4u%n", &d, &m, &y, &p); 1561290000Sglebius if (rc != 3 || p != 10) { 1562290000Sglebius DPRINTF(1, ("nmea: invalid date code: '%.10s'\n", 1563290000Sglebius dp)); 1564290000Sglebius return FALSE; 156554359Sroberto } 1566290000Sglebius break; 1567290000Sglebius 1568290000Sglebius default: 1569290000Sglebius DPRINTF(1, ("nmea: invalid parse format: %d\n", fmt)); 1570290000Sglebius return FALSE; 157154359Sroberto } 157254359Sroberto 1573290000Sglebius /* value sanity check */ 1574290000Sglebius if (d < 1 || d > 31 || m < 1 || m > 12) { 1575290000Sglebius DPRINTF(1, ("nmea: invalid date spec (YMD) %04u:%02u:%02u\n", 1576290000Sglebius y, m, d)); 1577290000Sglebius return FALSE; 157854359Sroberto } 1579290000Sglebius 1580290000Sglebius /* store results */ 1581290000Sglebius jd->monthday = (u_char)d; 1582290000Sglebius jd->month = (u_char)m; 1583290000Sglebius jd->year = (u_short)y; 158454359Sroberto 1585290000Sglebius return TRUE; 1586290000Sglebius} 158782498Sroberto 1588290000Sglebius/* 1589290000Sglebius * ------------------------------------------------------------------- 1590290000Sglebius * Parse GPS week time info from an NMEA sentence. This info contains 1591290000Sglebius * the GPS week number, the GPS time-of-week and the leap seconds GPS 1592290000Sglebius * to UTC. 1593290000Sglebius * 1594290000Sglebius * returns 1 on success, 0 on failure 1595290000Sglebius * ------------------------------------------------------------------- 1596290000Sglebius */ 1597290000Sglebiusstatic int 1598290000Sglebiusparse_weekdata( 1599290000Sglebius gps_weektm * wd, 1600290000Sglebius nmea_data * rd, 1601290000Sglebius int weekidx, 1602290000Sglebius int timeidx, 1603290000Sglebius int leapidx 1604290000Sglebius ) 1605290000Sglebius{ 1606290000Sglebius u_long secs; 1607290000Sglebius int fcnt; 1608290000Sglebius 1609290000Sglebius /* parse fields and count success */ 1610290000Sglebius fcnt = sscanf(field_parse(rd, weekidx), "%hu", &wd->wt_week); 1611290000Sglebius fcnt += sscanf(field_parse(rd, timeidx), "%lu", &secs); 1612290000Sglebius fcnt += sscanf(field_parse(rd, leapidx), "%hd", &wd->wt_leap); 1613290000Sglebius if (fcnt != 3 || wd->wt_week >= 1024 || secs >= 7*SECSPERDAY) { 1614290000Sglebius DPRINTF(1, ("nmea: parse_weekdata: invalid weektime spec\n")); 1615290000Sglebius return FALSE; 161654359Sroberto } 1617290000Sglebius wd->wt_time = (u_int32)secs; 161854359Sroberto 1619290000Sglebius return TRUE; 1620290000Sglebius} 162182498Sroberto 1622290000Sglebius/* 1623290000Sglebius * ------------------------------------------------------------------- 1624290000Sglebius * funny calendar-oriented stuff -- perhaps a bit hard to grok. 1625290000Sglebius * ------------------------------------------------------------------- 1626290000Sglebius * 1627290000Sglebius * Unfold a time-of-day (seconds since midnight) around the current 1628290000Sglebius * system time in a manner that guarantees an absolute difference of 1629290000Sglebius * less than 12hrs. 1630290000Sglebius * 1631290000Sglebius * This function is used for NMEA sentences that contain no date 1632290000Sglebius * information. This requires the system clock to be in +/-12hrs 1633290000Sglebius * around the true time, or the clock will synchronize the system 1day 1634290000Sglebius * off if not augmented with a time sources that also provide the 1635290000Sglebius * necessary date information. 1636290000Sglebius * 1637290000Sglebius * The function updates the calendar structure it also uses as 1638290000Sglebius * input to fetch the time from. 1639290000Sglebius * 1640290000Sglebius * returns 1 on success, 0 on failure 1641290000Sglebius * ------------------------------------------------------------------- 1642290000Sglebius */ 1643290000Sglebiusstatic int 1644290000Sglebiusunfold_day( 1645290000Sglebius struct calendar * jd, 1646290000Sglebius u_int32 rec_ui 1647290000Sglebius ) 1648290000Sglebius{ 1649290000Sglebius vint64 rec_qw; 1650290000Sglebius ntpcal_split rec_ds; 165182498Sroberto 165254359Sroberto /* 1653290000Sglebius * basically this is the peridiodic extension of the receive 1654290000Sglebius * time - 12hrs to the time-of-day with a period of 1 day. 1655290000Sglebius * But we would have to execute this in 64bit arithmetic, and we 1656290000Sglebius * cannot assume we can do this; therefore this is done 1657290000Sglebius * in split representation. 165854359Sroberto */ 1659290000Sglebius rec_qw = ntpcal_ntp_to_ntp(rec_ui - SECSPERDAY/2, NULL); 1660290000Sglebius rec_ds = ntpcal_daysplit(&rec_qw); 1661290000Sglebius rec_ds.lo = ntpcal_periodic_extend(rec_ds.lo, 1662290000Sglebius ntpcal_date_to_daysec(jd), 1663290000Sglebius SECSPERDAY); 1664290000Sglebius rec_ds.hi += ntpcal_daysec_to_date(jd, rec_ds.lo); 1665290000Sglebius return (ntpcal_rd_to_date(jd, rec_ds.hi + DAY_NTP_STARTS) >= 0); 1666290000Sglebius} 166754359Sroberto 1668290000Sglebius/* 1669290000Sglebius * ------------------------------------------------------------------- 1670290000Sglebius * A 2-digit year is expanded into full year spec around the year found 1671290000Sglebius * in 'jd->year'. This should be in +79/-19 years around the system time, 1672290000Sglebius * or the result will be off by 100 years. The assymetric behaviour was 1673290000Sglebius * chosen to enable inital sync for systems that do not have a 1674290000Sglebius * battery-backup clock and start with a date that is typically years in 1675290000Sglebius * the past. 1676290000Sglebius * 1677290000Sglebius * Since the GPS epoch starts at 1980-01-06, the resulting year will be 1678290000Sglebius * not be before 1980 in any case. 1679290000Sglebius * 1680290000Sglebius * returns 1 on success, 0 on failure 1681290000Sglebius * ------------------------------------------------------------------- 1682290000Sglebius */ 1683290000Sglebiusstatic int 1684290000Sglebiusunfold_century( 1685290000Sglebius struct calendar * jd, 1686290000Sglebius u_int32 rec_ui 1687290000Sglebius ) 1688290000Sglebius{ 1689290000Sglebius struct calendar rec; 1690290000Sglebius int32 baseyear; 169182498Sroberto 1692290000Sglebius ntpcal_ntp_to_date(&rec, rec_ui, NULL); 1693290000Sglebius baseyear = rec.year - 20; 1694290000Sglebius if (baseyear < g_gpsMinYear) 1695290000Sglebius baseyear = g_gpsMinYear; 1696290000Sglebius jd->year = (u_short)ntpcal_periodic_extend(baseyear, jd->year, 1697290000Sglebius 100); 169882498Sroberto 1699290000Sglebius return ((baseyear <= jd->year) && (baseyear + 100 > jd->year)); 170054359Sroberto} 170154359Sroberto 170254359Sroberto/* 1703290000Sglebius * ------------------------------------------------------------------- 1704290000Sglebius * A 2-digit year is expanded into a full year spec by correlation with 1705290000Sglebius * a GPS week number and the current leap second count. 170654359Sroberto * 1707290000Sglebius * The GPS week time scale counts weeks since Sunday, 1980-01-06, modulo 1708290000Sglebius * 1024 and seconds since start of the week. The GPS time scale is based 1709290000Sglebius * on international atomic time (TAI), so the leap second difference to 1710290000Sglebius * UTC is also needed for a proper conversion. 1711290000Sglebius * 1712290000Sglebius * A brute-force analysis (that is, test for every date) shows that a 1713290000Sglebius * wrong assignment of the century can not happen between the years 1900 1714290000Sglebius * to 2399 when comparing the week signatures for different 1715290000Sglebius * centuries. (I *think* that will not happen for 400*1024 years, but I 1716290000Sglebius * have no valid proof. -*-perlinger@ntp.org-*-) 1717290000Sglebius * 1718290000Sglebius * This function is bound to to work between years 1980 and 2399 1719290000Sglebius * (inclusive), which should suffice for now ;-) 1720290000Sglebius * 1721290000Sglebius * Note: This function needs a full date&time spec on input due to the 1722290000Sglebius * necessary leap second corrections! 1723290000Sglebius * 1724290000Sglebius * returns 1 on success, 0 on failure 1725290000Sglebius * ------------------------------------------------------------------- 172654359Sroberto */ 1727290000Sglebiusstatic int 1728290000Sglebiusgpsfix_century( 1729290000Sglebius struct calendar * jd, 1730290000Sglebius const gps_weektm * wd, 1731290000Sglebius u_short * century 1732290000Sglebius ) 173354359Sroberto{ 1734290000Sglebius int32 days; 1735290000Sglebius int32 doff; 1736290000Sglebius u_short week; 1737290000Sglebius u_short year; 1738290000Sglebius int loop; 173954359Sroberto 1740290000Sglebius /* Get day offset. Assumes that the input time is in range and 1741290000Sglebius * that the leap seconds do not shift more than +/-1 day. 1742290000Sglebius */ 1743290000Sglebius doff = ntpcal_date_to_daysec(jd) + wd->wt_leap; 1744290000Sglebius doff = (doff >= SECSPERDAY) - (doff < 0); 174554359Sroberto 174654359Sroberto /* 1747290000Sglebius * Loop over centuries to get a match, starting with the last 1748290000Sglebius * successful one. (Or with the 19th century if the cached value 1749290000Sglebius * is out of range...) 175054359Sroberto */ 1751290000Sglebius year = jd->year % 100; 1752290000Sglebius for (loop = 5; loop > 0; loop--,(*century)++) { 1753290000Sglebius if (*century < 19 || *century >= 24) 1754290000Sglebius *century = 19; 1755290000Sglebius /* Get days and week in GPS epoch */ 1756290000Sglebius jd->year = year + *century * 100; 1757290000Sglebius days = ntpcal_date_to_rd(jd) - DAY_GPS_STARTS + doff; 1758290000Sglebius week = (days / 7) % 1024; 1759290000Sglebius if (days >= 0 && wd->wt_week == week) 1760290000Sglebius return TRUE; /* matched... */ 1761290000Sglebius } 176254359Sroberto 1763290000Sglebius jd->year = year; 1764290000Sglebius return FALSE; /* match failed... */ 176554359Sroberto} 176654359Sroberto 176754359Sroberto/* 1768290000Sglebius * ------------------------------------------------------------------- 1769290000Sglebius * And now the final execise: Considering the fact that many (most?) 1770290000Sglebius * GPS receivers cannot handle a GPS epoch wrap well, we try to 1771290000Sglebius * compensate for that problem by unwrapping a GPS epoch around the 1772290000Sglebius * receive stamp. Another execise in periodic unfolding, of course, 1773290000Sglebius * but with enough points to take care of. 177454359Sroberto * 1775290000Sglebius * Note: The integral part of 'tofs' is intended to handle small(!) 1776290000Sglebius * systematic offsets, as -1 for handling $GPZDG, which gives the 1777290000Sglebius * following second. (sigh...) The absolute value shall be less than a 1778290000Sglebius * day (86400 seconds). 1779290000Sglebius * ------------------------------------------------------------------- 178054359Sroberto */ 1781290000Sglebiusstatic l_fp 1782290000Sglebiuseval_gps_time( 1783290000Sglebius struct peer * peer, /* for logging etc */ 1784290000Sglebius const struct calendar * gpst, /* GPS time stamp */ 1785290000Sglebius const struct timespec * tofs, /* GPS frac second & offset */ 1786290000Sglebius const l_fp * xrecv /* receive time stamp */ 178754359Sroberto ) 178854359Sroberto{ 1789290000Sglebius struct refclockproc * const pp = peer->procptr; 1790290000Sglebius nmea_unit * const up = (nmea_unit *)pp->unitptr; 179154359Sroberto 1792290000Sglebius l_fp retv; 1793290000Sglebius 1794290000Sglebius /* components of calculation */ 1795290000Sglebius int32_t rcv_sec, rcv_day; /* receive ToD and day */ 1796290000Sglebius int32_t gps_sec, gps_day; /* GPS ToD and day in NTP epoch */ 1797290000Sglebius int32_t adj_day, weeks; /* adjusted GPS day and week shift */ 1798290000Sglebius 1799290000Sglebius /* some temporaries to shuffle data */ 1800290000Sglebius vint64 vi64; 1801290000Sglebius ntpcal_split rs64; 1802290000Sglebius 1803290000Sglebius /* evaluate time stamp from receiver. */ 1804290000Sglebius gps_sec = ntpcal_date_to_daysec(gpst); 1805290000Sglebius gps_day = ntpcal_date_to_rd(gpst) - DAY_NTP_STARTS; 1806290000Sglebius 1807290000Sglebius /* merge in fractional offset */ 1808290000Sglebius retv = tspec_intv_to_lfp(*tofs); 1809290000Sglebius gps_sec += retv.l_i; 1810290000Sglebius 1811290000Sglebius /* If we fully trust the GPS receiver, just combine days and 1812290000Sglebius * seconds and be done. */ 1813290000Sglebius if (peer->ttl & NMEA_DATETRUST_MASK) { 1814290000Sglebius retv.l_ui = ntpcal_dayjoin(gps_day, gps_sec).D_s.lo; 1815290000Sglebius return retv; 181654359Sroberto } 1817290000Sglebius 1818290000Sglebius /* So we do not trust the GPS receiver to deliver a correct date 1819290000Sglebius * due to the GPS epoch changes. We map the date from the 1820290000Sglebius * receiver into the +/-512 week interval around the receive 1821290000Sglebius * time in that case. This would be a tad easier with 64bit 1822290000Sglebius * calculations, but again, we restrict the code to 32bit ops 1823290000Sglebius * when possible. */ 1824290000Sglebius 1825290000Sglebius /* - make sure the GPS fractional day is normalised 1826290000Sglebius * Applying the offset value might have put us slightly over the 1827290000Sglebius * edge of the allowed range for seconds-of-day. Doing a full 1828290000Sglebius * division with floor correction is overkill here; a simple 1829290000Sglebius * addition or subtraction step is sufficient. Using WHILE loops 1830290000Sglebius * gives the right result even if the offset exceeds one day, 1831290000Sglebius * which is NOT what it's intented for! */ 1832290000Sglebius while (gps_sec >= SECSPERDAY) { 1833290000Sglebius gps_sec -= SECSPERDAY; 1834290000Sglebius gps_day += 1; 1835290000Sglebius } 1836290000Sglebius while (gps_sec < 0) { 1837290000Sglebius gps_sec += SECSPERDAY; 1838290000Sglebius gps_day -= 1; 1839290000Sglebius } 1840290000Sglebius 1841290000Sglebius /* - get unfold base: day of full recv time - 512 weeks */ 1842290000Sglebius vi64 = ntpcal_ntp_to_ntp(xrecv->l_ui, NULL); 1843290000Sglebius rs64 = ntpcal_daysplit(&vi64); 1844290000Sglebius rcv_sec = rs64.lo; 1845290000Sglebius rcv_day = rs64.hi - 512 * 7; 1846290000Sglebius 1847290000Sglebius /* - take the fractional days into account 1848290000Sglebius * If the fractional day of the GPS time is smaller than the 1849290000Sglebius * fractional day of the receive time, we shift the base day for 1850290000Sglebius * the unfold by 1. */ 1851290000Sglebius if ( gps_sec < rcv_sec 1852290000Sglebius || (gps_sec == rcv_sec && retv.l_uf < xrecv->l_uf)) 1853290000Sglebius rcv_day += 1; 1854290000Sglebius 1855290000Sglebius /* - don't warp ahead of GPS invention! */ 1856290000Sglebius if (rcv_day < g_gpsMinBase) 1857290000Sglebius rcv_day = g_gpsMinBase; 1858290000Sglebius 1859290000Sglebius /* - let the magic happen: */ 1860290000Sglebius adj_day = ntpcal_periodic_extend(rcv_day, gps_day, 1024*7); 1861290000Sglebius 1862290000Sglebius /* - check if we should log a GPS epoch warp */ 1863290000Sglebius weeks = (adj_day - gps_day) / 7; 1864290000Sglebius if (weeks != up->epoch_warp) { 1865290000Sglebius up->epoch_warp = weeks; 1866290000Sglebius LOGIF(CLOCKINFO, (LOG_INFO, 1867290000Sglebius "%s Changed GPS epoch warp to %d weeks", 1868290000Sglebius refnumtoa(&peer->srcadr), weeks)); 1869290000Sglebius } 1870290000Sglebius 1871290000Sglebius /* - build result and be done */ 1872290000Sglebius retv.l_ui = ntpcal_dayjoin(adj_day, gps_sec).D_s.lo; 1873290000Sglebius return retv; 187454359Sroberto} 187554359Sroberto 1876290000Sglebius/* 1877290000Sglebius * =================================================================== 1878290000Sglebius * 1879290000Sglebius * NMEAD support 1880290000Sglebius * 1881290000Sglebius * original nmead support added by Jon Miner (cp_n18@yahoo.com) 1882290000Sglebius * 1883290000Sglebius * See http://home.hiwaay.net/~taylorc/gps/nmea-server/ 1884290000Sglebius * for information about nmead 1885290000Sglebius * 1886290000Sglebius * To use this, you need to create a link from /dev/gpsX to 1887290000Sglebius * the server:port where nmead is running. Something like this: 1888290000Sglebius * 1889290000Sglebius * ln -s server:port /dev/gps1 1890290000Sglebius * 1891290000Sglebius * Split into separate function by Juergen Perlinger 1892290000Sglebius * (perlinger-at-ntp-dot-org) 1893290000Sglebius * 1894290000Sglebius * =================================================================== 1895290000Sglebius */ 1896290000Sglebiusstatic int 1897290000Sglebiusnmead_open( 1898290000Sglebius const char * device 189954359Sroberto ) 190054359Sroberto{ 1901290000Sglebius int fd = -1; /* result file descriptor */ 1902290000Sglebius 1903290000Sglebius#ifdef HAVE_READLINK 1904290000Sglebius char host[80]; /* link target buffer */ 1905290000Sglebius char * port; /* port name or number */ 1906290000Sglebius int rc; /* result code (several)*/ 1907290000Sglebius int sh; /* socket handle */ 1908290000Sglebius struct addrinfo ai_hint; /* resolution hint */ 1909290000Sglebius struct addrinfo *ai_list; /* resolution result */ 1910290000Sglebius struct addrinfo *ai; /* result scan ptr */ 191154359Sroberto 1912290000Sglebius fd = -1; 1913290000Sglebius 1914290000Sglebius /* try to read as link, make sure no overflow occurs */ 1915290000Sglebius rc = readlink(device, host, sizeof(host)); 1916290000Sglebius if ((size_t)rc >= sizeof(host)) 1917290000Sglebius return fd; /* error / overflow / truncation */ 1918290000Sglebius host[rc] = '\0'; /* readlink does not place NUL */ 1919290000Sglebius 1920290000Sglebius /* get port */ 1921290000Sglebius port = strchr(host, ':'); 1922290000Sglebius if (!port) 1923290000Sglebius return fd; /* not 'host:port' syntax ? */ 1924290000Sglebius *port++ = '\0'; /* put in separator */ 1925290000Sglebius 1926290000Sglebius /* get address infos and try to open socket 1927290000Sglebius * 1928290000Sglebius * This getaddrinfo() is naughty in ntpd's nonblocking main 1929290000Sglebius * thread, but you have to go out of your wary to use this code 1930290000Sglebius * and typically the blocking is at startup where its impact is 1931290000Sglebius * reduced. The same holds for the 'connect()', as it is 1932290000Sglebius * blocking, too... 1933290000Sglebius */ 1934290000Sglebius ZERO(ai_hint); 1935290000Sglebius ai_hint.ai_protocol = IPPROTO_TCP; 1936290000Sglebius ai_hint.ai_socktype = SOCK_STREAM; 1937290000Sglebius if (getaddrinfo(host, port, &ai_hint, &ai_list)) 1938290000Sglebius return fd; 1939290000Sglebius 1940290000Sglebius for (ai = ai_list; ai && (fd == -1); ai = ai->ai_next) { 1941290000Sglebius sh = socket(ai->ai_family, ai->ai_socktype, 1942290000Sglebius ai->ai_protocol); 1943290000Sglebius if (INVALID_SOCKET == sh) 1944290000Sglebius continue; 1945290000Sglebius rc = connect(sh, ai->ai_addr, ai->ai_addrlen); 1946290000Sglebius if (-1 != rc) 1947290000Sglebius fd = sh; 1948290000Sglebius else 1949290000Sglebius close(sh); 195054359Sroberto } 1951290000Sglebius freeaddrinfo(ai_list); 1952290000Sglebius#else 1953290000Sglebius fd = -1; 1954290000Sglebius#endif 1955290000Sglebius 1956290000Sglebius return fd; 195754359Sroberto} 195854359Sroberto#else 1959290000SglebiusNONEMPTY_TRANSLATION_UNIT 1960290000Sglebius#endif /* REFCLOCK && CLOCK_NMEA */ 1961