154359Sroberto/* 254359Sroberto * refclock_nmea.c - clock driver for an NMEA GPS CLOCK 354359Sroberto * Michael Petry Jun 20, 1994 454359Sroberto * based on refclock_heathn.c 5285612Sdelphij * 6285612Sdelphij * Updated to add support for Accord GPS Clock 7285612Sdelphij * Venu Gopal Dec 05, 2007 8285612Sdelphij * neo.venu@gmail.com, venugopal_d@pgad.gov.in 9285612Sdelphij * 10285612Sdelphij * Updated to process 'time1' fudge factor 11285612Sdelphij * Venu Gopal May 05, 2008 12285612Sdelphij * 13285612Sdelphij * Converted to common PPSAPI code, separate PPS fudge time1 14285612Sdelphij * from serial timecode fudge time2. 15285612Sdelphij * Dave Hart July 1, 2009 16285612Sdelphij * hart@ntp.org, davehart@davehart.com 1754359Sroberto */ 18285612Sdelphij 1954359Sroberto#ifdef HAVE_CONFIG_H 2054359Sroberto#include <config.h> 2154359Sroberto#endif 2254359Sroberto 23285612Sdelphij#include "ntp_types.h" 24285612Sdelphij 2554359Sroberto#if defined(REFCLOCK) && defined(CLOCK_NMEA) 2654359Sroberto 27285612Sdelphij#define NMEA_WRITE_SUPPORT 0 /* no write support at the moment */ 28285612Sdelphij 29285612Sdelphij#include <sys/stat.h> 30182007Sroberto#include <stdio.h> 31182007Sroberto#include <ctype.h> 32285612Sdelphij#ifdef HAVE_SYS_SOCKET_H 33285612Sdelphij#include <sys/socket.h> 34285612Sdelphij#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" 41285612Sdelphij#include "ntp_calendar.h" 42285612Sdelphij#include "timespecops.h" 4354359Sroberto 4482498Sroberto#ifdef HAVE_PPSAPI 45182007Sroberto# include "ppsapi_timepps.h" 46285612Sdelphij# include "refclock_atom.h" 4782498Sroberto#endif /* HAVE_PPSAPI */ 4882498Sroberto 49200576Sroberto 5054359Sroberto/* 51285612Sdelphij * This driver supports NMEA-compatible GPS receivers 5254359Sroberto * 53285612Sdelphij * Prototype was refclock_trak.c, Thanks a lot. 5454359Sroberto * 5554359Sroberto * The receiver used spits out the NMEA sentences for boat navigation. 56285612Sdelphij * 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) 74285612Sdelphij * bit 3 - enables ZDA (8) - Standard Time & Date 75285612Sdelphij * bit 3 - enables ZDG (8) - Accord GPS Clock's custom sentence with GPS time 76285612Sdelphij * very close to standard ZDA 77285612Sdelphij * 78285612Sdelphij * Multiple sentences may be selected except when ZDG/ZDA is selected. 79285612Sdelphij * 80285612Sdelphij * bit 4/5/6 - selects the baudrate for serial port : 81285612Sdelphij * 0 for 4800 (default) 82285612Sdelphij * 1 for 9600 83285612Sdelphij * 2 for 19200 84285612Sdelphij * 3 for 38400 85285612Sdelphij * 4 for 57600 86285612Sdelphij * 5 for 115200 8754359Sroberto */ 88285612Sdelphij#define NMEA_MESSAGE_MASK 0x0000FF0FU 89285612Sdelphij#define NMEA_BAUDRATE_MASK 0x00000070U 90285612Sdelphij#define NMEA_BAUDRATE_SHIFT 4 9154359Sroberto 92285612Sdelphij#define NMEA_DELAYMEAS_MASK 0x80 93285612Sdelphij#define NMEA_EXTLOG_MASK 0x00010000U 94285612Sdelphij#define NMEA_DATETRUST_MASK 0x02000000U 95285612Sdelphij 96285612Sdelphij#define NMEA_PROTO_IDLEN 5 /* tag name must be at least 5 chars */ 97285612Sdelphij#define NMEA_PROTO_MINLEN 6 /* min chars in sentence, excluding CS */ 98285612Sdelphij#define NMEA_PROTO_MAXLEN 80 /* max chars in sentence, excluding CS */ 99285612Sdelphij#define NMEA_PROTO_FIELDS 32 /* not official; limit on fields per record */ 100285612Sdelphij 10154359Sroberto/* 102285612Sdelphij * We check the timecode format and decode its contents. We only care 103285612Sdelphij * about a few of them, the most important being the $GPRMC format: 104285612Sdelphij * 105285612Sdelphij * $GPRMC,hhmmss,a,fddmm.xx,n,dddmmm.xx,w,zz.z,yyy.,ddmmyy,dd,v*CC 106285612Sdelphij * 107285612Sdelphij * mode (0,1,2,3) selects sentence ANY/ALL, RMC, GGA, GLL, ZDA 108285612Sdelphij * $GPGLL,3513.8385,S,14900.7851,E,232420.594,A*21 109285612Sdelphij * $GPGGA,232420.59,3513.8385,S,14900.7851,E,1,05,3.4,00519,M,,,,*3F 110285612Sdelphij * $GPRMC,232418.19,A,3513.8386,S,14900.7853,E,00.0,000.0,121199,12.,E*77 111285612Sdelphij * 112285612Sdelphij * Defining GPZDA to support Standard Time & Date 113285612Sdelphij * sentence. The sentence has the following format 114285612Sdelphij * 115285612Sdelphij * $--ZDA,HHMMSS.SS,DD,MM,YYYY,TH,TM,*CS<CR><LF> 116285612Sdelphij * 117285612Sdelphij * Apart from the familiar fields, 118285612Sdelphij * 'TH' Time zone Hours 119285612Sdelphij * 'TM' Time zone Minutes 120285612Sdelphij * 121285612Sdelphij * Defining GPZDG to support Accord GPS Clock's custom NMEA 122285612Sdelphij * sentence. The sentence has the following format 123285612Sdelphij * 124285612Sdelphij * $GPZDG,HHMMSS.S,DD,MM,YYYY,AA.BB,V*CS<CR><LF> 125285612Sdelphij * 126285612Sdelphij * It contains the GPS timestamp valid for next PPS pulse. 127285612Sdelphij * Apart from the familiar fields, 128285612Sdelphij * 'AA.BB' denotes the signal strength( should be < 05.00 ) 129285612Sdelphij * 'V' denotes the GPS sync status : 130285612Sdelphij * '0' indicates INVALID time, 131285612Sdelphij * '1' indicates accuracy of +/-20 ms 132285612Sdelphij * '2' indicates accuracy of +/-100 ns 133285612Sdelphij * 134285612Sdelphij * Defining PGRMF for Garmin GPS Fix Data 135285612Sdelphij * $PGRMF,WN,WS,DATE,TIME,LS,LAT,LAT_DIR,LON,LON_DIR,MODE,FIX,SPD,DIR,PDOP,TDOP 136285612Sdelphij * WN -- GPS week number (weeks since 1980-01-06, mod 1024) 137285612Sdelphij * WS -- GPS seconds in week 138285612Sdelphij * LS -- GPS leap seconds, accumulated ( UTC + LS == GPS ) 139285612Sdelphij * FIX -- Fix type: 0=nofix, 1=2D, 2=3D 140285612Sdelphij * DATE/TIME are standard date/time strings in UTC time scale 141285612Sdelphij * 142285612Sdelphij * The GPS time can be used to get the full century for the truncated 143285612Sdelphij * date spec. 144285612Sdelphij */ 145285612Sdelphij 146285612Sdelphij/* 14754359Sroberto * Definitions 14854359Sroberto */ 149285612Sdelphij#define DEVICE "/dev/gps%d" /* GPS serial device */ 150285612Sdelphij#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 */ 156285612Sdelphij#ifndef O_NOCTTY 157285612Sdelphij#define M_NOCTTY 0 158285612Sdelphij#else 159285612Sdelphij#define M_NOCTTY O_NOCTTY 160285612Sdelphij#endif 161285612Sdelphij#ifndef O_NONBLOCK 162285612Sdelphij#define M_NONBLOCK 0 163285612Sdelphij#else 164285612Sdelphij#define M_NONBLOCK O_NONBLOCK 165285612Sdelphij#endif 166285612Sdelphij#define PPSOPENMODE (O_RDWR | M_NOCTTY | M_NONBLOCK) 16754359Sroberto 168285612Sdelphij/* NMEA sentence array indexes for those we use */ 169285612Sdelphij#define NMEA_GPRMC 0 /* recommended min. nav. */ 170285612Sdelphij#define NMEA_GPGGA 1 /* fix and quality */ 171285612Sdelphij#define NMEA_GPGLL 2 /* geo. lat/long */ 172285612Sdelphij#define NMEA_GPZDA 3 /* date/time */ 173285612Sdelphij/* 174285612Sdelphij * $GPZDG is a proprietary sentence that violates the spec, by not 175285612Sdelphij * using $P and an assigned company identifier to prefix the sentence 176285612Sdelphij * identifier. When used with this driver, the system needs to be 177285612Sdelphij * isolated from other NTP networks, as it operates in GPS time, not 178285612Sdelphij * UTC as is much more common. GPS time is >15 seconds different from 179285612Sdelphij * UTC due to not respecting leap seconds since 1970 or so. Other 180285612Sdelphij * than the different timebase, $GPZDG is similar to $GPZDA. 181285612Sdelphij */ 182285612Sdelphij#define NMEA_GPZDG 4 183285612Sdelphij#define NMEA_PGRMF 5 184285612Sdelphij#define NMEA_ARRAY_SIZE (NMEA_PGRMF + 1) 18554359Sroberto 18654359Sroberto/* 187285612Sdelphij * Sentence selection mode bits 18854359Sroberto */ 189285612Sdelphij#define USE_GPRMC 0x00000001u 190285612Sdelphij#define USE_GPGGA 0x00000002u 191285612Sdelphij#define USE_GPGLL 0x00000004u 192285612Sdelphij#define USE_GPZDA 0x00000008u 193285612Sdelphij#define USE_PGRMF 0x00000100u 19454359Sroberto 195285612Sdelphij/* mapping from sentence index to controlling mode bit */ 196285612Sdelphijstatic const u_int32 sentence_mode[NMEA_ARRAY_SIZE] = 197285612Sdelphij{ 198285612Sdelphij USE_GPRMC, 199285612Sdelphij USE_GPGGA, 200285612Sdelphij USE_GPGLL, 201285612Sdelphij USE_GPZDA, 202285612Sdelphij USE_GPZDA, 203285612Sdelphij USE_PGRMF 204285612Sdelphij}; 205285612Sdelphij 206285612Sdelphij/* date formats we support */ 207285612Sdelphijenum date_fmt { 208285612Sdelphij DATE_1_DDMMYY, /* use 1 field with 2-digit year */ 209285612Sdelphij DATE_3_DDMMYYYY /* use 3 fields with 4-digit year */ 210285612Sdelphij}; 211285612Sdelphij 212285612Sdelphij/* results for 'field_init()' 213285612Sdelphij * 214285612Sdelphij * Note: If a checksum is present, the checksum test must pass OK or the 215285612Sdelphij * sentence is tagged invalid. 216285612Sdelphij */ 217285612Sdelphij#define CHECK_EMPTY -1 /* no data */ 218285612Sdelphij#define CHECK_INVALID 0 /* not a valid NMEA sentence */ 219285612Sdelphij#define CHECK_VALID 1 /* valid but without checksum */ 220285612Sdelphij#define CHECK_CSVALID 2 /* valid with checksum OK */ 221285612Sdelphij 22254359Sroberto/* 22354359Sroberto * Unit control structure 22454359Sroberto */ 225285612Sdelphijtypedef struct { 22682498Sroberto#ifdef HAVE_PPSAPI 227285612Sdelphij struct refclock_atom atom; /* PPSAPI structure */ 228285612Sdelphij int ppsapi_fd; /* fd used with PPSAPI */ 229285612Sdelphij u_char ppsapi_tried; /* attempt PPSAPI once */ 230285612Sdelphij u_char ppsapi_lit; /* time_pps_create() worked */ 231285612Sdelphij u_char ppsapi_gate; /* system is on PPS */ 23282498Sroberto#endif /* HAVE_PPSAPI */ 233285612Sdelphij u_char gps_time; /* use GPS time, not UTC */ 234285612Sdelphij u_short century_cache; /* cached current century */ 235285612Sdelphij l_fp last_reftime; /* last processed reference stamp */ 236285612Sdelphij short epoch_warp; /* last epoch warp, for logging */ 237285612Sdelphij /* tally stats, reset each poll cycle */ 238285612Sdelphij struct 239285612Sdelphij { 240285612Sdelphij u_int total; 241285612Sdelphij u_int accepted; 242285612Sdelphij u_int rejected; /* GPS said not enough signal */ 243285612Sdelphij u_int malformed; /* Bad checksum, invalid date or time */ 244285612Sdelphij u_int filtered; /* mode bits, not GPZDG, same second */ 245285612Sdelphij u_int pps_used; 246285612Sdelphij } 247285612Sdelphij tally; 248285612Sdelphij /* per sentence checksum seen flag */ 249285612Sdelphij u_char cksum_type[NMEA_ARRAY_SIZE]; 250285612Sdelphij} nmea_unit; 25154359Sroberto 25254359Sroberto/* 253285612Sdelphij * helper for faster field access 254285612Sdelphij */ 255285612Sdelphijtypedef struct { 256285612Sdelphij char *base; /* buffer base */ 257285612Sdelphij char *cptr; /* current field ptr */ 258285612Sdelphij int blen; /* buffer length */ 259285612Sdelphij int cidx; /* current field index */ 260285612Sdelphij} nmea_data; 261285612Sdelphij 262285612Sdelphij/* 263285612Sdelphij * NMEA gps week/time information 264285612Sdelphij * This record contains the number of weeks since 1980-01-06 modulo 265285612Sdelphij * 1024, the seconds elapsed since start of the week, and the number of 266285612Sdelphij * leap seconds that are the difference between GPS and UTC time scale. 267285612Sdelphij */ 268285612Sdelphijtypedef struct { 269285612Sdelphij u_int32 wt_time; /* seconds since weekstart */ 270285612Sdelphij u_short wt_week; /* week number */ 271285612Sdelphij short wt_leap; /* leap seconds */ 272285612Sdelphij} gps_weektm; 273285612Sdelphij 274285612Sdelphij/* 275285612Sdelphij * The GPS week time scale starts on Sunday, 1980-01-06. We need the 276285612Sdelphij * rata die number of this day. 277285612Sdelphij */ 278285612Sdelphij#ifndef DAY_GPS_STARTS 279285612Sdelphij#define DAY_GPS_STARTS 722820 280285612Sdelphij#endif 281285612Sdelphij 282285612Sdelphij/* 28354359Sroberto * Function prototypes 28454359Sroberto */ 285285612Sdelphijstatic void nmea_init (void); 286285612Sdelphijstatic int nmea_start (int, struct peer *); 287285612Sdelphijstatic void nmea_shutdown (int, struct peer *); 288285612Sdelphijstatic void nmea_receive (struct recvbuf *); 289285612Sdelphijstatic void nmea_poll (int, struct peer *); 29082498Sroberto#ifdef HAVE_PPSAPI 291285612Sdelphijstatic void nmea_control (int, const struct refclockstat *, 292285612Sdelphij struct refclockstat *, struct peer *); 293285612Sdelphij#define NMEA_CONTROL nmea_control 294285612Sdelphij#else 295285612Sdelphij#define NMEA_CONTROL noentry 29682498Sroberto#endif /* HAVE_PPSAPI */ 297285612Sdelphijstatic void nmea_timer (int, struct peer *); 29854359Sroberto 299285612Sdelphij/* parsing helpers */ 300285612Sdelphijstatic int field_init (nmea_data * data, char * cp, int len); 301285612Sdelphijstatic char * field_parse (nmea_data * data, int fn); 302285612Sdelphijstatic void field_wipe (nmea_data * data, ...); 303285612Sdelphijstatic u_char parse_qual (nmea_data * data, int idx, 304285612Sdelphij char tag, int inv); 305285612Sdelphijstatic int parse_time (struct calendar * jd, long * nsec, 306285612Sdelphij nmea_data *, int idx); 307285612Sdelphijstatic int parse_date (struct calendar *jd, nmea_data*, 308285612Sdelphij int idx, enum date_fmt fmt); 309285612Sdelphijstatic int parse_weekdata (gps_weektm *, nmea_data *, 310285612Sdelphij int weekidx, int timeidx, int leapidx); 311285612Sdelphij/* calendar / date helpers */ 312285612Sdelphijstatic int unfold_day (struct calendar * jd, u_int32 rec_ui); 313285612Sdelphijstatic int unfold_century (struct calendar * jd, u_int32 rec_ui); 314285612Sdelphijstatic int gpsfix_century (struct calendar * jd, const gps_weektm * wd, 315285612Sdelphij u_short * ccentury); 316285612Sdelphijstatic l_fp eval_gps_time (struct peer * peer, const struct calendar * gpst, 317285612Sdelphij const struct timespec * gpso, const l_fp * xrecv); 318285612Sdelphij 319285612Sdelphijstatic int nmead_open (const char * device); 320285612Sdelphijstatic void save_ltc (struct refclockproc * const, const char * const, 321285612Sdelphij size_t); 322285612Sdelphij 32354359Sroberto/* 324285612Sdelphij * If we want the driver to ouput sentences, too: re-enable the send 325285612Sdelphij * support functions by defining NMEA_WRITE_SUPPORT to non-zero... 326285612Sdelphij */ 327285612Sdelphij#if NMEA_WRITE_SUPPORT 328285612Sdelphij 329285612Sdelphijstatic void gps_send(int, const char *, struct peer *); 330285612Sdelphij# ifdef SYS_WINNT 331285612Sdelphij# undef write /* ports/winnt/include/config.h: #define write _write */ 332285612Sdelphijextern int async_write(int, const void *, unsigned int); 333285612Sdelphij# define write(fd, data, octets) async_write(fd, data, octets) 334285612Sdelphij# endif /* SYS_WINNT */ 335285612Sdelphij 336285612Sdelphij#endif /* NMEA_WRITE_SUPPORT */ 337285612Sdelphij 338285612Sdelphijstatic int32_t g_gpsMinBase; 339285612Sdelphijstatic int32_t g_gpsMinYear; 340285612Sdelphij 341285612Sdelphij/* 342285612Sdelphij * ------------------------------------------------------------------- 34354359Sroberto * Transfer vector 344285612Sdelphij * ------------------------------------------------------------------- 34554359Sroberto */ 346285612Sdelphijstruct refclock refclock_nmea = { 34754359Sroberto nmea_start, /* start up driver */ 348285612Sdelphij nmea_shutdown, /* shut down driver */ 34954359Sroberto nmea_poll, /* transmit poll message */ 350285612Sdelphij NMEA_CONTROL, /* fudge control */ 351285612Sdelphij nmea_init, /* initialize driver */ 35254359Sroberto noentry, /* buginfo */ 353285612Sdelphij nmea_timer /* called once per second */ 35454359Sroberto}; 35554359Sroberto 35654359Sroberto/* 357285612Sdelphij * ------------------------------------------------------------------- 358285612Sdelphij * nmea_init - initialise data 359285612Sdelphij * 360285612Sdelphij * calculates a few runtime constants that cannot be made compile time 361285612Sdelphij * constants. 362285612Sdelphij * ------------------------------------------------------------------- 363285612Sdelphij */ 364285612Sdelphijstatic void 365285612Sdelphijnmea_init(void) 366285612Sdelphij{ 367285612Sdelphij struct calendar date; 368285612Sdelphij 369285612Sdelphij /* - calculate min. base value for GPS epoch & century unfolding 370285612Sdelphij * This assumes that the build system was roughly in sync with 371285612Sdelphij * the world, and that really synchronising to a time before the 372285612Sdelphij * program was created would be unsafe or insane. If the build 373285612Sdelphij * date cannot be stablished, at least use the start of GPS 374285612Sdelphij * (1980-01-06) as minimum, because GPS can surely NOT 375285612Sdelphij * synchronise beyond it's own big bang. We add a little safety 376285612Sdelphij * margin for the fuzziness of the build date, which is in an 377285612Sdelphij * undefined time zone. */ 378285612Sdelphij if (ntpcal_get_build_date(&date)) 379285612Sdelphij g_gpsMinBase = ntpcal_date_to_rd(&date) - 2; 380285612Sdelphij else 381285612Sdelphij g_gpsMinBase = 0; 382285612Sdelphij 383285612Sdelphij if (g_gpsMinBase < DAY_GPS_STARTS) 384285612Sdelphij g_gpsMinBase = DAY_GPS_STARTS; 385285612Sdelphij 386285612Sdelphij ntpcal_rd_to_date(&date, g_gpsMinBase); 387285612Sdelphij g_gpsMinYear = date.year; 388285612Sdelphij g_gpsMinBase -= DAY_NTP_STARTS; 389285612Sdelphij} 390285612Sdelphij 391285612Sdelphij/* 392285612Sdelphij * ------------------------------------------------------------------- 39354359Sroberto * nmea_start - open the GPS devices and initialize data for processing 394285612Sdelphij * 395285612Sdelphij * return 0 on error, 1 on success. Even on error the peer structures 396285612Sdelphij * must be in a state that permits 'nmea_shutdown()' to clean up all 397285612Sdelphij * resources, because it will be called immediately to do so. 398285612Sdelphij * ------------------------------------------------------------------- 39954359Sroberto */ 40054359Srobertostatic int 40154359Srobertonmea_start( 402285612Sdelphij int unit, 403285612Sdelphij struct peer * peer 40454359Sroberto ) 40554359Sroberto{ 406285612Sdelphij struct refclockproc * const pp = peer->procptr; 407285612Sdelphij nmea_unit * const up = emalloc_zero(sizeof(*up)); 408285612Sdelphij char device[20]; 409285612Sdelphij size_t devlen; 410285612Sdelphij u_int32 rate; 411285612Sdelphij int baudrate; 412285612Sdelphij const char * baudtext; 41354359Sroberto 41456746Sroberto 415285612Sdelphij /* Get baudrate choice from mode byte bits 4/5/6 */ 416285612Sdelphij rate = (peer->ttl & NMEA_BAUDRATE_MASK) >> NMEA_BAUDRATE_SHIFT; 41754359Sroberto 418285612Sdelphij switch (rate) { 419285612Sdelphij case 0: 420285612Sdelphij baudrate = SPEED232; 421285612Sdelphij baudtext = "4800"; 422285612Sdelphij break; 423285612Sdelphij case 1: 424285612Sdelphij baudrate = B9600; 425285612Sdelphij baudtext = "9600"; 426285612Sdelphij break; 427285612Sdelphij case 2: 428285612Sdelphij baudrate = B19200; 429285612Sdelphij baudtext = "19200"; 430285612Sdelphij break; 431285612Sdelphij case 3: 432285612Sdelphij baudrate = B38400; 433285612Sdelphij baudtext = "38400"; 434285612Sdelphij break; 435285612Sdelphij#ifdef B57600 436285612Sdelphij case 4: 437285612Sdelphij baudrate = B57600; 438285612Sdelphij baudtext = "57600"; 439285612Sdelphij break; 440182007Sroberto#endif 441285612Sdelphij#ifdef B115200 442285612Sdelphij case 5: 443285612Sdelphij baudrate = B115200; 444285612Sdelphij baudtext = "115200"; 445285612Sdelphij break; 446285612Sdelphij#endif 447285612Sdelphij default: 448285612Sdelphij baudrate = SPEED232; 449285612Sdelphij baudtext = "4800 (fallback)"; 450285612Sdelphij break; 451285612Sdelphij } 452182007Sroberto 453285612Sdelphij /* Allocate and initialize unit structure */ 454285612Sdelphij pp->unitptr = (caddr_t)up; 455285612Sdelphij pp->io.fd = -1; 45654359Sroberto pp->io.clock_recv = nmea_receive; 457285612Sdelphij pp->io.srcclock = peer; 45854359Sroberto pp->io.datalen = 0; 459285612Sdelphij /* force change detection on first valid message */ 460285612Sdelphij memset(&up->last_reftime, 0xFF, sizeof(up->last_reftime)); 461285612Sdelphij /* force checksum on GPRMC, see below */ 462285612Sdelphij up->cksum_type[NMEA_GPRMC] = CHECK_CSVALID; 463285612Sdelphij#ifdef HAVE_PPSAPI 464285612Sdelphij up->ppsapi_fd = -1; 465285612Sdelphij#endif 466285612Sdelphij ZERO(up->tally); 46754359Sroberto 468285612Sdelphij /* Initialize miscellaneous variables */ 46982498Sroberto peer->precision = PRECISION; 47054359Sroberto pp->clockdesc = DESCRIPTION; 471285612Sdelphij memcpy(&pp->refid, REFID, 4); 47254359Sroberto 473285612Sdelphij /* Open serial port. Use CLK line discipline, if available. */ 474285612Sdelphij devlen = snprintf(device, sizeof(device), DEVICE, unit); 475285612Sdelphij if (devlen >= sizeof(device)) { 476285612Sdelphij msyslog(LOG_ERR, "%s clock device name too long", 477285612Sdelphij refnumtoa(&peer->srcadr)); 478285612Sdelphij return FALSE; /* buffer overflow */ 47982498Sroberto } 480285612Sdelphij pp->io.fd = refclock_open(device, baudrate, LDISC_CLK); 481285612Sdelphij if (0 >= pp->io.fd) { 482285612Sdelphij pp->io.fd = nmead_open(device); 483285612Sdelphij if (-1 == pp->io.fd) 484285612Sdelphij return FALSE; 485285612Sdelphij } 486285612Sdelphij LOGIF(CLOCKINFO, (LOG_NOTICE, "%s serial %s open at %s bps", 487285612Sdelphij refnumtoa(&peer->srcadr), device, baudtext)); 488285612Sdelphij 489285612Sdelphij /* succeed if this clock can be added */ 490285612Sdelphij return io_addclock(&pp->io) != 0; 49154359Sroberto} 49254359Sroberto 493285612Sdelphij 49454359Sroberto/* 495285612Sdelphij * ------------------------------------------------------------------- 49654359Sroberto * nmea_shutdown - shut down a GPS clock 497285612Sdelphij * 498285612Sdelphij * NOTE this routine is called after nmea_start() returns failure, 499285612Sdelphij * as well as during a normal shutdown due to ntpq :config unpeer. 500285612Sdelphij * ------------------------------------------------------------------- 50154359Sroberto */ 50254359Srobertostatic void 50354359Srobertonmea_shutdown( 504285612Sdelphij int unit, 505285612Sdelphij struct peer * peer 50654359Sroberto ) 50754359Sroberto{ 508285612Sdelphij struct refclockproc * const pp = peer->procptr; 509285612Sdelphij nmea_unit * const up = (nmea_unit *)pp->unitptr; 51054359Sroberto 511285612Sdelphij UNUSED_ARG(unit); 512285612Sdelphij 513285612Sdelphij if (up != NULL) { 51482498Sroberto#ifdef HAVE_PPSAPI 515285612Sdelphij if (up->ppsapi_lit) 516285612Sdelphij time_pps_destroy(up->atom.handle); 517285612Sdelphij if (up->ppsapi_tried && up->ppsapi_fd != pp->io.fd) 518285612Sdelphij close(up->ppsapi_fd); 519285612Sdelphij#endif 520285612Sdelphij free(up); 521285612Sdelphij } 522285612Sdelphij pp->unitptr = (caddr_t)NULL; 523285612Sdelphij if (-1 != pp->io.fd) 524285612Sdelphij io_closeclock(&pp->io); 525285612Sdelphij pp->io.fd = -1; 52654359Sroberto} 52754359Sroberto 52854359Sroberto/* 529285612Sdelphij * ------------------------------------------------------------------- 530285612Sdelphij * nmea_control - configure fudge params 531285612Sdelphij * ------------------------------------------------------------------- 53282498Sroberto */ 533285612Sdelphij#ifdef HAVE_PPSAPI 53482498Srobertostatic void 53582498Srobertonmea_control( 536285612Sdelphij int unit, 537285612Sdelphij const struct refclockstat * in_st, 538285612Sdelphij struct refclockstat * out_st, 539285612Sdelphij struct peer * peer 54082498Sroberto ) 54182498Sroberto{ 542285612Sdelphij struct refclockproc * const pp = peer->procptr; 543285612Sdelphij nmea_unit * const up = (nmea_unit *)pp->unitptr; 54482498Sroberto 545285612Sdelphij char device[32]; 546285612Sdelphij size_t devlen; 547285612Sdelphij 548285612Sdelphij UNUSED_ARG(in_st); 549285612Sdelphij UNUSED_ARG(out_st); 550285612Sdelphij 551285612Sdelphij /* 552285612Sdelphij * PPS control 553285612Sdelphij * 554285612Sdelphij * If /dev/gpspps$UNIT can be opened that will be used for 555285612Sdelphij * PPSAPI. Otherwise, the GPS serial device /dev/gps$UNIT 556285612Sdelphij * already opened is used for PPSAPI as well. (This might not 557285612Sdelphij * work, in which case the PPS API remains unavailable...) 558285612Sdelphij */ 559285612Sdelphij 560285612Sdelphij /* Light up the PPSAPI interface if not yet attempted. */ 561285612Sdelphij if ((CLK_FLAG1 & pp->sloppyclockflag) && !up->ppsapi_tried) { 562285612Sdelphij up->ppsapi_tried = TRUE; 563285612Sdelphij devlen = snprintf(device, sizeof(device), PPSDEV, unit); 564285612Sdelphij if (devlen < sizeof(device)) { 565285612Sdelphij up->ppsapi_fd = open(device, PPSOPENMODE, 566285612Sdelphij S_IRUSR | S_IWUSR); 567285612Sdelphij } else { 568285612Sdelphij up->ppsapi_fd = -1; 569285612Sdelphij msyslog(LOG_ERR, "%s PPS device name too long", 570285612Sdelphij refnumtoa(&peer->srcadr)); 571285612Sdelphij } 572285612Sdelphij if (-1 == up->ppsapi_fd) 573285612Sdelphij up->ppsapi_fd = pp->io.fd; 574285612Sdelphij if (refclock_ppsapi(up->ppsapi_fd, &up->atom)) { 575285612Sdelphij /* use the PPS API for our own purposes now. */ 576285612Sdelphij up->ppsapi_lit = refclock_params( 577285612Sdelphij pp->sloppyclockflag, &up->atom); 578285612Sdelphij if (!up->ppsapi_lit) { 579285612Sdelphij /* failed to configure, drop PPS unit */ 580285612Sdelphij time_pps_destroy(up->atom.handle); 581285612Sdelphij msyslog(LOG_WARNING, 582285612Sdelphij "%s set PPSAPI params fails", 583285612Sdelphij refnumtoa(&peer->srcadr)); 584285612Sdelphij } 585285612Sdelphij /* note: the PPS I/O handle remains valid until 586285612Sdelphij * flag1 is cleared or the clock is shut down. 587285612Sdelphij */ 588285612Sdelphij } else { 589285612Sdelphij msyslog(LOG_WARNING, 590285612Sdelphij "%s flag1 1 but PPSAPI fails", 591285612Sdelphij refnumtoa(&peer->srcadr)); 592285612Sdelphij } 593285612Sdelphij } 594285612Sdelphij 595285612Sdelphij /* shut down PPS API if activated */ 596285612Sdelphij if (!(CLK_FLAG1 & pp->sloppyclockflag) && up->ppsapi_tried) { 597285612Sdelphij /* shutdown PPS API */ 598285612Sdelphij if (up->ppsapi_lit) 599285612Sdelphij time_pps_destroy(up->atom.handle); 600285612Sdelphij up->atom.handle = 0; 601285612Sdelphij /* close/drop PPS fd */ 602285612Sdelphij if (up->ppsapi_fd != pp->io.fd) 603285612Sdelphij close(up->ppsapi_fd); 604285612Sdelphij up->ppsapi_fd = -1; 605285612Sdelphij 606285612Sdelphij /* clear markers and peer items */ 607285612Sdelphij up->ppsapi_gate = FALSE; 608285612Sdelphij up->ppsapi_lit = FALSE; 609285612Sdelphij up->ppsapi_tried = FALSE; 610285612Sdelphij 611285612Sdelphij peer->flags &= ~FLAG_PPS; 612285612Sdelphij peer->precision = PRECISION; 613285612Sdelphij } 61482498Sroberto} 615285612Sdelphij#endif /* HAVE_PPSAPI */ 61682498Sroberto 61782498Sroberto/* 618285612Sdelphij * ------------------------------------------------------------------- 619285612Sdelphij * nmea_timer - called once per second 620285612Sdelphij * this only polls (older?) Oncore devices now 621285612Sdelphij * 622285612Sdelphij * Usually 'nmea_receive()' can get a timestamp every second, but at 623285612Sdelphij * least one Motorola unit needs prompting each time. Doing so in 624285612Sdelphij * 'nmea_poll()' gives only one sample per poll cycle, which actually 625285612Sdelphij * defeats the purpose of the median filter. Polling once per second 626285612Sdelphij * seems a much better idea. 627285612Sdelphij * ------------------------------------------------------------------- 62882498Sroberto */ 629285612Sdelphijstatic void 630285612Sdelphijnmea_timer( 631285612Sdelphij int unit, 632285612Sdelphij struct peer * peer 63382498Sroberto ) 63482498Sroberto{ 635285612Sdelphij#if NMEA_WRITE_SUPPORT 636285612Sdelphij 637285612Sdelphij struct refclockproc * const pp = peer->procptr; 63882498Sroberto 639285612Sdelphij UNUSED_ARG(unit); 64082498Sroberto 641285612Sdelphij if (-1 != pp->io.fd) /* any mode bits to evaluate here? */ 642285612Sdelphij gps_send(pp->io.fd, "$PMOTG,RMC,0000*1D\r\n", peer); 643285612Sdelphij#else 644285612Sdelphij 645285612Sdelphij UNUSED_ARG(unit); 646285612Sdelphij UNUSED_ARG(peer); 647285612Sdelphij 648285612Sdelphij#endif /* NMEA_WRITE_SUPPORT */ 64982498Sroberto} 65082498Sroberto 651285612Sdelphij#ifdef HAVE_PPSAPI 65282498Sroberto/* 653285612Sdelphij * ------------------------------------------------------------------- 654285612Sdelphij * refclock_ppsrelate(...) -- correlate with PPS edge 65582498Sroberto * 656285612Sdelphij * This function is used to correlate a receive time stamp and a 657285612Sdelphij * reference time with a PPS edge time stamp. It applies the necessary 658285612Sdelphij * fudges (fudge1 for PPS, fudge2 for receive time) and then tries to 659285612Sdelphij * move the receive time stamp to the corresponding edge. This can warp 660285612Sdelphij * into future, if a transmission delay of more than 500ms is not 661285612Sdelphij * compensated with a corresponding fudge time2 value, because then the 662285612Sdelphij * next PPS edge is nearer than the last. (Similiar to what the PPS ATOM 663285612Sdelphij * driver does, but we deal with full time stamps here, not just phase 664285612Sdelphij * shift information.) Likewise, a negative fudge time2 value must be 665285612Sdelphij * used if the reference time stamp correlates with the *following* PPS 666285612Sdelphij * pulse. 667285612Sdelphij * 668285612Sdelphij * Note that the receive time fudge value only needs to move the receive 669285612Sdelphij * stamp near a PPS edge but that close proximity is not required; 670285612Sdelphij * +/-100ms precision should be enough. But since the fudge value will 671285612Sdelphij * probably also be used to compensate the transmission delay when no 672285612Sdelphij * PPS edge can be related to the time stamp, it's best to get it as 673285612Sdelphij * close as possible. 674285612Sdelphij * 675285612Sdelphij * It should also be noted that the typical use case is matching to the 676285612Sdelphij * preceeding edge, as most units relate their sentences to the current 677285612Sdelphij * second. 678285612Sdelphij * 679285612Sdelphij * The function returns PPS_RELATE_NONE (0) if no PPS edge correlation 680285612Sdelphij * can be fixed; PPS_RELATE_EDGE (1) when a PPS edge could be fixed, but 681285612Sdelphij * the distance to the reference time stamp is too big (exceeds 682285612Sdelphij * +/-400ms) and the ATOM driver PLL cannot be used to fix the phase; 683285612Sdelphij * and PPS_RELATE_PHASE (2) when the ATOM driver PLL code can be used. 684285612Sdelphij * 685285612Sdelphij * On output, the receive time stamp is replaced with the corresponding 686285612Sdelphij * PPS edge time if a fix could be made; the PPS fudge is updated to 687285612Sdelphij * reflect the proper fudge time to apply. (This implies that 688285612Sdelphij * 'refclock_process_offset()' must be used!) 689285612Sdelphij * ------------------------------------------------------------------- 69082498Sroberto */ 691285612Sdelphij#define PPS_RELATE_NONE 0 /* no pps correlation possible */ 692285612Sdelphij#define PPS_RELATE_EDGE 1 /* recv time fixed, no phase lock */ 693285612Sdelphij#define PPS_RELATE_PHASE 2 /* recv time fixed, phase lock ok */ 694285612Sdelphij 69582498Srobertostatic int 696285612Sdelphijrefclock_ppsrelate( 697285612Sdelphij const struct refclockproc * pp , /* for sanity */ 698285612Sdelphij const struct refclock_atom * ap , /* for PPS io */ 699285612Sdelphij const l_fp * reftime , 700285612Sdelphij l_fp * rd_stamp, /* i/o read stamp */ 701285612Sdelphij double pp_fudge, /* pps fudge */ 702285612Sdelphij double * rd_fudge /* i/o read fudge */ 70382498Sroberto ) 70482498Sroberto{ 705285612Sdelphij pps_info_t pps_info; 706285612Sdelphij struct timespec timeout; 707285612Sdelphij l_fp pp_stamp, pp_delta; 708285612Sdelphij double delta, idelta; 70982498Sroberto 710285612Sdelphij if (pp->leap == LEAP_NOTINSYNC) 711285612Sdelphij return PPS_RELATE_NONE; /* clock is insane, no chance */ 71282498Sroberto 713285612Sdelphij ZERO(timeout); 714285612Sdelphij ZERO(pps_info); 715285612Sdelphij if (time_pps_fetch(ap->handle, PPS_TSFMT_TSPEC, 716285612Sdelphij &pps_info, &timeout) < 0) 717285612Sdelphij return PPS_RELATE_NONE; /* can't get time stamps */ 718285612Sdelphij 719285612Sdelphij /* get last active PPS edge before receive */ 720285612Sdelphij if (ap->pps_params.mode & PPS_CAPTUREASSERT) 721285612Sdelphij timeout = pps_info.assert_timestamp; 722285612Sdelphij else if (ap->pps_params.mode & PPS_CAPTURECLEAR) 723285612Sdelphij timeout = pps_info.clear_timestamp; 724285612Sdelphij else 725285612Sdelphij return PPS_RELATE_NONE; /* WHICH edge, please?!? */ 726285612Sdelphij 727285612Sdelphij /* get delta between receive time and PPS time */ 728285612Sdelphij pp_stamp = tspec_stamp_to_lfp(timeout); 729285612Sdelphij pp_delta = *rd_stamp; 730285612Sdelphij L_SUB(&pp_delta, &pp_stamp); 731285612Sdelphij LFPTOD(&pp_delta, delta); 732285612Sdelphij delta += pp_fudge - *rd_fudge; 733285612Sdelphij if (fabs(delta) > 1.5) 734285612Sdelphij return PPS_RELATE_NONE; /* PPS timeout control */ 735285612Sdelphij 736285612Sdelphij /* eventually warp edges, check phase */ 737285612Sdelphij idelta = floor(delta + 0.5); 738285612Sdelphij pp_fudge -= idelta; 739285612Sdelphij delta -= idelta; 740285612Sdelphij if (fabs(delta) > 0.45) 741285612Sdelphij return PPS_RELATE_NONE; /* dead band control */ 742285612Sdelphij 743285612Sdelphij /* we actually have a PPS edge to relate with! */ 744285612Sdelphij *rd_stamp = pp_stamp; 745285612Sdelphij *rd_fudge = pp_fudge; 746285612Sdelphij 747285612Sdelphij /* if whole system out-of-sync, do not try to PLL */ 748285612Sdelphij if (sys_leap == LEAP_NOTINSYNC) 749285612Sdelphij return PPS_RELATE_EDGE; /* cannot PLL with atom code */ 750285612Sdelphij 751285612Sdelphij /* check against reftime if ATOM PLL can be used */ 752285612Sdelphij pp_delta = *reftime; 753285612Sdelphij L_SUB(&pp_delta, &pp_stamp); 754285612Sdelphij LFPTOD(&pp_delta, delta); 755285612Sdelphij delta += pp_fudge; 756285612Sdelphij if (fabs(delta) > 0.45) 757285612Sdelphij return PPS_RELATE_EDGE; /* cannot PLL with atom code */ 758285612Sdelphij 759285612Sdelphij /* all checks passed, gets an AAA rating here! */ 760285612Sdelphij return PPS_RELATE_PHASE; /* can PLL with atom code */ 76182498Sroberto} 762285612Sdelphij#endif /* HAVE_PPSAPI */ 76382498Sroberto 76482498Sroberto/* 765285612Sdelphij * ------------------------------------------------------------------- 76654359Sroberto * nmea_receive - receive data from the serial interface 767285612Sdelphij * 768285612Sdelphij * This is the workhorse for NMEA data evaluation: 769285612Sdelphij * 770285612Sdelphij * + it checks all NMEA data, and rejects sentences that are not valid 771285612Sdelphij * NMEA sentences 772285612Sdelphij * + it checks whether a sentence is known and to be used 773285612Sdelphij * + it parses the time and date data from the NMEA data string and 774285612Sdelphij * augments the missing bits. (century in dat, whole date, ...) 775285612Sdelphij * + it rejects data that is not from the first accepted sentence in a 776285612Sdelphij * burst 777285612Sdelphij * + it eventually replaces the receive time with the PPS edge time. 778285612Sdelphij * + it feeds the data to the internal processing stages. 779285612Sdelphij * ------------------------------------------------------------------- 78054359Sroberto */ 78154359Srobertostatic void 78254359Srobertonmea_receive( 783285612Sdelphij struct recvbuf * rbufp 78454359Sroberto ) 78554359Sroberto{ 786285612Sdelphij /* declare & init control structure ptrs */ 787285612Sdelphij struct peer * const peer = rbufp->recv_peer; 788285612Sdelphij struct refclockproc * const pp = peer->procptr; 789285612Sdelphij nmea_unit * const up = (nmea_unit*)pp->unitptr; 790285612Sdelphij 79182498Sroberto /* Use these variables to hold data until we decide its worth keeping */ 792285612Sdelphij nmea_data rdata; 793285612Sdelphij char rd_lastcode[BMAX]; 794285612Sdelphij l_fp rd_timestamp, rd_reftime; 795285612Sdelphij int rd_lencode; 796285612Sdelphij double rd_fudge; 79754359Sroberto 798285612Sdelphij /* working stuff */ 799285612Sdelphij struct calendar date; /* to keep & convert the time stamp */ 800285612Sdelphij struct timespec tofs; /* offset to full-second reftime */ 801285612Sdelphij gps_weektm gpsw; /* week time storage */ 802285612Sdelphij /* results of sentence/date/time parsing */ 803285612Sdelphij u_char sentence; /* sentence tag */ 804285612Sdelphij int checkres; 805285612Sdelphij char * cp; 806285612Sdelphij int rc_date; 807285612Sdelphij int rc_time; 80854359Sroberto 809285612Sdelphij /* make sure data has defined pristine state */ 810285612Sdelphij ZERO(tofs); 811285612Sdelphij ZERO(date); 812285612Sdelphij ZERO(gpsw); 813289997Sglebius 814285612Sdelphij /* 815285612Sdelphij * Read the timecode and timestamp, then initialise field 816285612Sdelphij * processing. The <CR><LF> at the NMEA line end is translated 817285612Sdelphij * to <LF><LF> by the terminal input routines on most systems, 818285612Sdelphij * and this gives us one spurious empty read per record which we 819285612Sdelphij * better ignore silently. 82054359Sroberto */ 821285612Sdelphij rd_lencode = refclock_gtlin(rbufp, rd_lastcode, 822285612Sdelphij sizeof(rd_lastcode), &rd_timestamp); 823285612Sdelphij checkres = field_init(&rdata, rd_lastcode, rd_lencode); 824285612Sdelphij switch (checkres) { 82554359Sroberto 826285612Sdelphij case CHECK_INVALID: 827285612Sdelphij DPRINTF(1, ("%s invalid data: '%s'\n", 828285612Sdelphij refnumtoa(&peer->srcadr), rd_lastcode)); 829285612Sdelphij refclock_report(peer, CEVNT_BADREPLY); 830285612Sdelphij return; 83154359Sroberto 832285612Sdelphij case CHECK_EMPTY: 833285612Sdelphij return; 834285612Sdelphij 835285612Sdelphij default: 836285612Sdelphij DPRINTF(1, ("%s gpsread: %d '%s'\n", 837285612Sdelphij refnumtoa(&peer->srcadr), rd_lencode, 838285612Sdelphij rd_lastcode)); 839285612Sdelphij break; 840285612Sdelphij } 841285612Sdelphij up->tally.total++; 842285612Sdelphij 843285612Sdelphij /* 844285612Sdelphij * --> below this point we have a valid NMEA sentence <-- 845285612Sdelphij * 846285612Sdelphij * Check sentence name. Skip first 2 chars (talker ID) in most 847285612Sdelphij * cases, to allow for $GLGGA and $GPGGA etc. Since the name 848285612Sdelphij * field has at least 5 chars we can simply shift the field 849285612Sdelphij * start. 85054359Sroberto */ 851285612Sdelphij cp = field_parse(&rdata, 0); 852285612Sdelphij if (strncmp(cp + 2, "RMC,", 4) == 0) 853285612Sdelphij sentence = NMEA_GPRMC; 854285612Sdelphij else if (strncmp(cp + 2, "GGA,", 4) == 0) 855285612Sdelphij sentence = NMEA_GPGGA; 856285612Sdelphij else if (strncmp(cp + 2, "GLL,", 4) == 0) 857285612Sdelphij sentence = NMEA_GPGLL; 858285612Sdelphij else if (strncmp(cp + 2, "ZDA,", 4) == 0) 859285612Sdelphij sentence = NMEA_GPZDA; 860285612Sdelphij else if (strncmp(cp + 2, "ZDG,", 4) == 0) 861285612Sdelphij sentence = NMEA_GPZDG; 862285612Sdelphij else if (strncmp(cp, "PGRMF,", 6) == 0) 863285612Sdelphij sentence = NMEA_PGRMF; 86454359Sroberto else 865285612Sdelphij return; /* not something we know about */ 86654359Sroberto 867285612Sdelphij /* Eventually output delay measurement now. */ 868285612Sdelphij if (peer->ttl & NMEA_DELAYMEAS_MASK) { 869285612Sdelphij mprintf_clock_stats(&peer->srcadr, "delay %0.6f %.*s", 870285612Sdelphij ldexp(rd_timestamp.l_uf, -32), 871285612Sdelphij (int)(strchr(rd_lastcode, ',') - rd_lastcode), 872285612Sdelphij rd_lastcode); 873285612Sdelphij } 874285612Sdelphij 87582498Sroberto /* See if I want to process this message type */ 876285612Sdelphij if ((peer->ttl & NMEA_MESSAGE_MASK) && 877285612Sdelphij !(peer->ttl & sentence_mode[sentence])) { 878285612Sdelphij up->tally.filtered++; 87982498Sroberto return; 880285612Sdelphij } 88182498Sroberto 882285612Sdelphij /* 883285612Sdelphij * make sure it came in clean 884285612Sdelphij * 885285612Sdelphij * Apparently, older NMEA specifications (which are expensive) 886285612Sdelphij * did not require the checksum for all sentences. $GPMRC is 887285612Sdelphij * the only one so far identified which has always been required 888285612Sdelphij * to include a checksum. 889285612Sdelphij * 890285612Sdelphij * Today, most NMEA GPS receivers checksum every sentence. To 891285612Sdelphij * preserve its error-detection capabilities with modern GPSes 892285612Sdelphij * while allowing operation without checksums on all but $GPMRC, 893285612Sdelphij * we keep track of whether we've ever seen a valid checksum on 894285612Sdelphij * a given sentence, and if so, reject future instances without 895285612Sdelphij * checksum. ('up->cksum_type[NMEA_GPRMC]' is set in 896285612Sdelphij * 'nmea_start()' to enforce checksums for $GPRMC right from the 897285612Sdelphij * start.) 898285612Sdelphij */ 899285612Sdelphij if (up->cksum_type[sentence] <= (u_char)checkres) { 900285612Sdelphij up->cksum_type[sentence] = (u_char)checkres; 901285612Sdelphij } else { 902285612Sdelphij DPRINTF(1, ("%s checksum missing: '%s'\n", 903285612Sdelphij refnumtoa(&peer->srcadr), rd_lastcode)); 904285612Sdelphij refclock_report(peer, CEVNT_BADREPLY); 905285612Sdelphij up->tally.malformed++; 906285612Sdelphij return; 907285612Sdelphij } 90882498Sroberto 909285612Sdelphij /* 910285612Sdelphij * $GPZDG provides GPS time not UTC, and the two mix poorly. 911285612Sdelphij * Once have processed a $GPZDG, do not process any further UTC 912285612Sdelphij * sentences (all but $GPZDG currently). 913285612Sdelphij */ 914285612Sdelphij if (up->gps_time && NMEA_GPZDG != sentence) { 915285612Sdelphij up->tally.filtered++; 916285612Sdelphij return; 917285612Sdelphij } 91882498Sroberto 919285612Sdelphij DPRINTF(1, ("%s processing %d bytes, timecode '%s'\n", 920285612Sdelphij refnumtoa(&peer->srcadr), rd_lencode, rd_lastcode)); 92182498Sroberto 922285612Sdelphij /* 923285612Sdelphij * Grab fields depending on clock string type and possibly wipe 924285612Sdelphij * sensitive data from the last timecode. 925285612Sdelphij */ 926285612Sdelphij switch (sentence) { 92782498Sroberto 928285612Sdelphij case NMEA_GPRMC: 929285612Sdelphij /* Check quality byte, fetch data & time */ 930285612Sdelphij rc_time = parse_time(&date, &tofs.tv_nsec, &rdata, 1); 931285612Sdelphij pp->leap = parse_qual(&rdata, 2, 'A', 0); 932285612Sdelphij rc_date = parse_date(&date, &rdata, 9, DATE_1_DDMMYY) 933285612Sdelphij && unfold_century(&date, rd_timestamp.l_ui); 934285612Sdelphij if (CLK_FLAG4 & pp->sloppyclockflag) 935285612Sdelphij field_wipe(&rdata, 3, 4, 5, 6, -1); 936285612Sdelphij break; 93782498Sroberto 938285612Sdelphij case NMEA_GPGGA: 939285612Sdelphij /* Check quality byte, fetch time only */ 940285612Sdelphij rc_time = parse_time(&date, &tofs.tv_nsec, &rdata, 1); 941285612Sdelphij pp->leap = parse_qual(&rdata, 6, '0', 1); 942285612Sdelphij rc_date = unfold_day(&date, rd_timestamp.l_ui); 943285612Sdelphij if (CLK_FLAG4 & pp->sloppyclockflag) 944285612Sdelphij field_wipe(&rdata, 2, 4, -1); 94582498Sroberto break; 94682498Sroberto 947285612Sdelphij case NMEA_GPGLL: 948285612Sdelphij /* Check quality byte, fetch time only */ 949285612Sdelphij rc_time = parse_time(&date, &tofs.tv_nsec, &rdata, 5); 950285612Sdelphij pp->leap = parse_qual(&rdata, 6, 'A', 0); 951285612Sdelphij rc_date = unfold_day(&date, rd_timestamp.l_ui); 952285612Sdelphij if (CLK_FLAG4 & pp->sloppyclockflag) 953285612Sdelphij field_wipe(&rdata, 1, 3, -1); 954285612Sdelphij break; 955285612Sdelphij 956285612Sdelphij case NMEA_GPZDA: 957285612Sdelphij /* No quality. Assume best, fetch time & full date */ 958285612Sdelphij pp->leap = LEAP_NOWARNING; 959285612Sdelphij rc_time = parse_time(&date, &tofs.tv_nsec, &rdata, 1); 960285612Sdelphij rc_date = parse_date(&date, &rdata, 2, DATE_3_DDMMYYYY); 961285612Sdelphij break; 96282498Sroberto 963285612Sdelphij case NMEA_GPZDG: 964285612Sdelphij /* Check quality byte, fetch time & full date */ 965285612Sdelphij rc_time = parse_time(&date, &tofs.tv_nsec, &rdata, 1); 966285612Sdelphij rc_date = parse_date(&date, &rdata, 2, DATE_3_DDMMYYYY); 967285612Sdelphij pp->leap = parse_qual(&rdata, 4, '0', 1); 968285612Sdelphij tofs.tv_sec = -1; /* GPZDG is following second */ 96982498Sroberto break; 97082498Sroberto 971285612Sdelphij case NMEA_PGRMF: 972285612Sdelphij /* get date, time, qualifier and GPS weektime. We need 973285612Sdelphij * date and time-of-day for the century fix, so we read 974285612Sdelphij * them first. 97582498Sroberto */ 976285612Sdelphij rc_date = parse_weekdata(&gpsw, &rdata, 1, 2, 5) 977285612Sdelphij && parse_date(&date, &rdata, 3, DATE_1_DDMMYY); 978285612Sdelphij rc_time = parse_time(&date, &tofs.tv_nsec, &rdata, 4); 979285612Sdelphij pp->leap = parse_qual(&rdata, 11, '0', 1); 980285612Sdelphij rc_date = rc_date 981285612Sdelphij && gpsfix_century(&date, &gpsw, &up->century_cache); 982285612Sdelphij if (CLK_FLAG4 & pp->sloppyclockflag) 983285612Sdelphij field_wipe(&rdata, 6, 8, -1); 98482498Sroberto break; 985285612Sdelphij 986285612Sdelphij default: 987285612Sdelphij INVARIANT(0); /* Coverity 97123 */ 988285612Sdelphij return; 989285612Sdelphij } 99082498Sroberto 991285612Sdelphij /* Check sanity of time-of-day. */ 992285612Sdelphij if (rc_time == 0) { /* no time or conversion error? */ 993285612Sdelphij checkres = CEVNT_BADTIME; 994285612Sdelphij up->tally.malformed++; 995285612Sdelphij } 996285612Sdelphij /* Check sanity of date. */ 997285612Sdelphij else if (rc_date == 0) {/* no date or conversion error? */ 998285612Sdelphij checkres = CEVNT_BADDATE; 999285612Sdelphij up->tally.malformed++; 1000285612Sdelphij } 1001285612Sdelphij /* check clock sanity; [bug 2143] */ 1002285612Sdelphij else if (pp->leap == LEAP_NOTINSYNC) { /* no good status? */ 1003285612Sdelphij checkres = CEVNT_BADREPLY; 1004285612Sdelphij up->tally.rejected++; 1005285612Sdelphij } 1006285612Sdelphij else 1007285612Sdelphij checkres = -1; 100882498Sroberto 1009285612Sdelphij if (checkres != -1) { 1010285612Sdelphij save_ltc(pp, rd_lastcode, rd_lencode); 1011285612Sdelphij refclock_report(peer, checkres); 101282498Sroberto return; 1013285612Sdelphij } 101482498Sroberto 1015285612Sdelphij DPRINTF(1, ("%s effective timecode: %04u-%02u-%02u %02d:%02d:%02d\n", 1016285612Sdelphij refnumtoa(&peer->srcadr), 1017285612Sdelphij date.year, date.month, date.monthday, 1018285612Sdelphij date.hour, date.minute, date.second)); 1019285612Sdelphij 1020285612Sdelphij /* Check if we must enter GPS time mode; log so if we do */ 1021285612Sdelphij if (!up->gps_time && (sentence == NMEA_GPZDG)) { 1022285612Sdelphij msyslog(LOG_INFO, "%s using GPS time as if it were UTC", 1023285612Sdelphij refnumtoa(&peer->srcadr)); 1024285612Sdelphij up->gps_time = 1; 102582498Sroberto } 1026285612Sdelphij 1027285612Sdelphij /* 1028285612Sdelphij * Get the reference time stamp from the calendar buffer. 1029285612Sdelphij * Process the new sample in the median filter and determine the 1030285612Sdelphij * timecode timestamp, but only if the PPS is not in control. 1031285612Sdelphij * Discard sentence if reference time did not change. 1032285612Sdelphij */ 1033285612Sdelphij rd_reftime = eval_gps_time(peer, &date, &tofs, &rd_timestamp); 1034285612Sdelphij if (L_ISEQU(&up->last_reftime, &rd_reftime)) { 1035285612Sdelphij /* Do not touch pp->a_lastcode on purpose! */ 1036285612Sdelphij up->tally.filtered++; 1037285612Sdelphij return; 1038285612Sdelphij } 1039285612Sdelphij up->last_reftime = rd_reftime; 1040285612Sdelphij rd_fudge = pp->fudgetime2; 104182498Sroberto 1042285612Sdelphij DPRINTF(1, ("%s using '%s'\n", 1043285612Sdelphij refnumtoa(&peer->srcadr), rd_lastcode)); 104454359Sroberto 1045285612Sdelphij /* Data will be accepted. Update stats & log data. */ 1046285612Sdelphij up->tally.accepted++; 1047285612Sdelphij save_ltc(pp, rd_lastcode, rd_lencode); 1048285612Sdelphij pp->lastrec = rd_timestamp; 1049285612Sdelphij 1050285612Sdelphij#ifdef HAVE_PPSAPI 1051285612Sdelphij /* 1052285612Sdelphij * If we have PPS running, we try to associate the sentence 1053285612Sdelphij * with the last active edge of the PPS signal. 1054285612Sdelphij */ 1055285612Sdelphij if (up->ppsapi_lit) 1056285612Sdelphij switch (refclock_ppsrelate( 1057285612Sdelphij pp, &up->atom, &rd_reftime, &rd_timestamp, 1058285612Sdelphij pp->fudgetime1, &rd_fudge)) 1059285612Sdelphij { 1060285612Sdelphij case PPS_RELATE_PHASE: 1061285612Sdelphij up->ppsapi_gate = TRUE; 1062285612Sdelphij peer->precision = PPS_PRECISION; 1063285612Sdelphij peer->flags |= FLAG_PPS; 1064285612Sdelphij DPRINTF(2, ("%s PPS_RELATE_PHASE\n", 1065285612Sdelphij refnumtoa(&peer->srcadr))); 1066285612Sdelphij up->tally.pps_used++; 1067285612Sdelphij break; 1068285612Sdelphij 1069285612Sdelphij case PPS_RELATE_EDGE: 1070285612Sdelphij up->ppsapi_gate = TRUE; 1071285612Sdelphij peer->precision = PPS_PRECISION; 1072285612Sdelphij DPRINTF(2, ("%s PPS_RELATE_EDGE\n", 1073285612Sdelphij refnumtoa(&peer->srcadr))); 1074285612Sdelphij break; 1075285612Sdelphij 1076285612Sdelphij case PPS_RELATE_NONE: 1077285612Sdelphij default: 1078285612Sdelphij /* 1079285612Sdelphij * Resetting precision and PPS flag is done in 1080285612Sdelphij * 'nmea_poll', since it might be a glitch. But 1081285612Sdelphij * at the end of the poll cycle we know... 1082285612Sdelphij */ 1083285612Sdelphij DPRINTF(2, ("%s PPS_RELATE_NONE\n", 1084285612Sdelphij refnumtoa(&peer->srcadr))); 1085285612Sdelphij break; 108654359Sroberto } 1087285612Sdelphij#endif /* HAVE_PPSAPI */ 108854359Sroberto 1089285612Sdelphij refclock_process_offset(pp, rd_reftime, rd_timestamp, rd_fudge); 1090285612Sdelphij} 109182498Sroberto 1092285612Sdelphij 1093285612Sdelphij/* 1094285612Sdelphij * ------------------------------------------------------------------- 1095285612Sdelphij * nmea_poll - called by the transmit procedure 1096285612Sdelphij * 1097285612Sdelphij * Does the necessary bookkeeping stuff to keep the reported state of 1098285612Sdelphij * the clock in sync with reality. 1099285612Sdelphij * 1100285612Sdelphij * We go to great pains to avoid changing state here, since there may 1101285612Sdelphij * be more than one eavesdropper receiving the same timecode. 1102285612Sdelphij * ------------------------------------------------------------------- 1103285612Sdelphij */ 1104285612Sdelphijstatic void 1105285612Sdelphijnmea_poll( 1106285612Sdelphij int unit, 1107285612Sdelphij struct peer * peer 1108285612Sdelphij ) 1109285612Sdelphij{ 1110285612Sdelphij struct refclockproc * const pp = peer->procptr; 1111285612Sdelphij nmea_unit * const up = (nmea_unit *)pp->unitptr; 1112285612Sdelphij 111382498Sroberto /* 1114285612Sdelphij * Process median filter samples. If none received, declare a 1115285612Sdelphij * timeout and keep going. 111682498Sroberto */ 1117285612Sdelphij#ifdef HAVE_PPSAPI 1118285612Sdelphij /* 1119285612Sdelphij * If we don't have PPS pulses and time stamps, turn PPS down 1120285612Sdelphij * for now. 1121285612Sdelphij */ 1122285612Sdelphij if (!up->ppsapi_gate) { 1123285612Sdelphij peer->flags &= ~FLAG_PPS; 1124285612Sdelphij peer->precision = PRECISION; 1125285612Sdelphij } else { 1126285612Sdelphij up->ppsapi_gate = FALSE; 1127285612Sdelphij } 1128285612Sdelphij#endif /* HAVE_PPSAPI */ 1129285612Sdelphij 1130285612Sdelphij /* 1131285612Sdelphij * If the median filter is empty, claim a timeout. Else process 1132285612Sdelphij * the input data and keep the stats going. 1133285612Sdelphij */ 1134285612Sdelphij if (pp->coderecv == pp->codeproc) { 1135285612Sdelphij refclock_report(peer, CEVNT_TIMEOUT); 1136285612Sdelphij } else { 1137285612Sdelphij pp->polls++; 1138285612Sdelphij pp->lastref = pp->lastrec; 1139285612Sdelphij refclock_receive(peer); 1140285612Sdelphij } 1141285612Sdelphij 1142285612Sdelphij /* 1143285612Sdelphij * If extended logging is required, write the tally stats to the 1144285612Sdelphij * clockstats file; otherwise just do a normal clock stats 1145285612Sdelphij * record. Clear the tally stats anyway. 114682498Sroberto */ 1147285612Sdelphij if (peer->ttl & NMEA_EXTLOG_MASK) { 1148285612Sdelphij /* Log & reset counters with extended logging */ 1149285612Sdelphij const char *nmea = pp->a_lastcode; 1150285612Sdelphij if (*nmea == '\0') nmea = "(none)"; 1151285612Sdelphij mprintf_clock_stats( 1152285612Sdelphij &peer->srcadr, "%s %u %u %u %u %u %u", 1153285612Sdelphij nmea, 1154285612Sdelphij up->tally.total, up->tally.accepted, 1155285612Sdelphij up->tally.rejected, up->tally.malformed, 1156285612Sdelphij up->tally.filtered, up->tally.pps_used); 1157285612Sdelphij } else { 1158285612Sdelphij record_clock_stats(&peer->srcadr, pp->a_lastcode); 115982498Sroberto } 1160285612Sdelphij ZERO(up->tally); 1161285612Sdelphij} 116282498Sroberto 1163285612Sdelphij/* 1164285612Sdelphij * ------------------------------------------------------------------- 1165285612Sdelphij * Save the last timecode string, making sure it's properly truncated 1166285612Sdelphij * if necessary and NUL terminated in any case. 1167285612Sdelphij */ 1168285612Sdelphijstatic void 1169285612Sdelphijsave_ltc( 1170285612Sdelphij struct refclockproc * const pp, 1171285612Sdelphij const char * const tc, 1172285612Sdelphij size_t len 1173285612Sdelphij ) 1174285612Sdelphij{ 1175285612Sdelphij if (len >= sizeof(pp->a_lastcode)) 1176285612Sdelphij len = sizeof(pp->a_lastcode) - 1; 1177285612Sdelphij pp->lencode = (u_short)len; 1178285612Sdelphij memcpy(pp->a_lastcode, tc, len); 1179285612Sdelphij pp->a_lastcode[len] = '\0'; 1180285612Sdelphij} 1181285612Sdelphij 1182285612Sdelphij 1183285612Sdelphij#if NMEA_WRITE_SUPPORT 1184285612Sdelphij/* 1185285612Sdelphij * ------------------------------------------------------------------- 1186285612Sdelphij * gps_send(fd, cmd, peer) Sends a command to the GPS receiver. 1187285612Sdelphij * as in gps_send(fd, "rqts,u", peer); 1188285612Sdelphij * 1189285612Sdelphij * If 'cmd' starts with a '$' it is assumed that this command is in raw 1190285612Sdelphij * format, that is, starts with '$', ends with '<cr><lf>' and that any 1191285612Sdelphij * checksum is correctly provided; the command will be send 'as is' in 1192285612Sdelphij * that case. Otherwise the function will create the necessary frame 1193285612Sdelphij * (start char, chksum, final CRLF) on the fly. 1194285612Sdelphij * 1195285612Sdelphij * We don't currently send any data, but would like to send RTCM SC104 1196285612Sdelphij * messages for differential positioning. It should also give us better 1197285612Sdelphij * time. Without a PPS output, we're Just fooling ourselves because of 1198285612Sdelphij * the serial code paths 1199285612Sdelphij * ------------------------------------------------------------------- 1200285612Sdelphij */ 1201285612Sdelphijstatic void 1202285612Sdelphijgps_send( 1203285612Sdelphij int fd, 1204285612Sdelphij const char * cmd, 1205285612Sdelphij struct peer * peer 1206285612Sdelphij ) 1207285612Sdelphij{ 1208285612Sdelphij /* $...*xy<CR><LF><NUL> add 7 */ 1209285612Sdelphij char buf[NMEA_PROTO_MAXLEN + 7]; 1210285612Sdelphij int len; 1211285612Sdelphij u_char dcs; 1212285612Sdelphij const u_char *beg, *end; 1213285612Sdelphij 1214285612Sdelphij if (*cmd != '$') { 1215285612Sdelphij /* get checksum and length */ 1216285612Sdelphij beg = end = (const u_char*)cmd; 1217285612Sdelphij dcs = 0; 1218285612Sdelphij while (*end >= ' ' && *end != '*') 1219285612Sdelphij dcs ^= *end++; 1220285612Sdelphij len = end - beg; 1221285612Sdelphij /* format into output buffer with overflow check */ 1222285612Sdelphij len = snprintf(buf, sizeof(buf), "$%.*s*%02X\r\n", 1223285612Sdelphij len, beg, dcs); 1224285612Sdelphij if ((size_t)len >= sizeof(buf)) { 1225285612Sdelphij DPRINTF(1, ("%s gps_send: buffer overflow for command '%s'\n", 1226285612Sdelphij refnumtoa(&peer->srcadr), cmd)); 1227285612Sdelphij return; /* game over player 1 */ 1228285612Sdelphij } 1229285612Sdelphij cmd = buf; 1230285612Sdelphij } else { 1231285612Sdelphij len = strlen(cmd); 123254359Sroberto } 123354359Sroberto 1234285612Sdelphij DPRINTF(1, ("%s gps_send: '%.*s'\n", refnumtoa(&peer->srcadr), 1235285612Sdelphij len - 2, cmd)); 123682498Sroberto 1237285612Sdelphij /* send out the whole stuff */ 1238285612Sdelphij if (write(fd, cmd, len) == -1) 1239285612Sdelphij refclock_report(peer, CEVNT_FAULT); 1240285612Sdelphij} 1241285612Sdelphij#endif /* NMEA_WRITE_SUPPORT */ 1242285612Sdelphij 1243285612Sdelphij/* 1244285612Sdelphij * ------------------------------------------------------------------- 1245285612Sdelphij * helpers for faster field splitting 1246285612Sdelphij * ------------------------------------------------------------------- 1247285612Sdelphij * 1248285612Sdelphij * set up a field record, check syntax and verify checksum 1249285612Sdelphij * 1250285612Sdelphij * format is $XXXXX,1,2,3,4*ML 1251285612Sdelphij * 1252285612Sdelphij * 8-bit XOR of characters between $ and * noninclusive is transmitted 1253285612Sdelphij * in last two chars M and L holding most and least significant nibbles 1254285612Sdelphij * in hex representation such as: 1255285612Sdelphij * 1256285612Sdelphij * $GPGLL,5057.970,N,00146.110,E,142451,A*27 1257285612Sdelphij * $GPVTG,089.0,T,,,15.2,N,,*7F 1258285612Sdelphij * 1259285612Sdelphij * Some other constraints: 1260285612Sdelphij * + The field name must at least 5 upcase characters or digits and must 1261285612Sdelphij * start with a character. 1262285612Sdelphij * + The checksum (if present) must be uppercase hex digits. 1263285612Sdelphij * + The length of a sentence is limited to 80 characters (not including 1264285612Sdelphij * the final CR/LF nor the checksum, but including the leading '$') 1265285612Sdelphij * 1266285612Sdelphij * Return values: 1267285612Sdelphij * + CHECK_INVALID 1268285612Sdelphij * The data does not form a valid NMEA sentence or a checksum error 1269285612Sdelphij * occurred. 1270285612Sdelphij * + CHECK_VALID 1271285612Sdelphij * The data is a valid NMEA sentence but contains no checksum. 1272285612Sdelphij * + CHECK_CSVALID 1273285612Sdelphij * The data is a valid NMEA sentence and passed the checksum test. 1274285612Sdelphij * ------------------------------------------------------------------- 1275285612Sdelphij */ 1276285612Sdelphijstatic int 1277285612Sdelphijfield_init( 1278285612Sdelphij nmea_data * data, /* context structure */ 1279285612Sdelphij char * cptr, /* start of raw data */ 1280285612Sdelphij int dlen /* data len, not counting trailing NUL */ 1281285612Sdelphij ) 1282285612Sdelphij{ 1283285612Sdelphij u_char cs_l; /* checksum local computed */ 1284285612Sdelphij u_char cs_r; /* checksum remote given */ 1285285612Sdelphij char * eptr; /* buffer end end pointer */ 1286285612Sdelphij char tmp; /* char buffer */ 1287285612Sdelphij 1288285612Sdelphij cs_l = 0; 1289285612Sdelphij cs_r = 0; 1290285612Sdelphij /* some basic input constraints */ 1291285612Sdelphij if (dlen < 0) 1292285612Sdelphij dlen = 0; 1293285612Sdelphij eptr = cptr + dlen; 1294285612Sdelphij *eptr = '\0'; 1295285612Sdelphij 1296285612Sdelphij /* load data context */ 1297285612Sdelphij data->base = cptr; 1298285612Sdelphij data->cptr = cptr; 1299285612Sdelphij data->cidx = 0; 1300285612Sdelphij data->blen = dlen; 1301285612Sdelphij 1302285612Sdelphij /* syntax check follows here. check allowed character 1303285612Sdelphij * sequences, updating the local computed checksum as we go. 1304285612Sdelphij * 1305285612Sdelphij * regex equiv: '^\$[A-Z][A-Z0-9]{4,}[^*]*(\*[0-9A-F]{2})?$' 130654359Sroberto */ 1307285612Sdelphij 1308285612Sdelphij /* -*- start character: '^\$' */ 1309285612Sdelphij if (*cptr == '\0') 1310285612Sdelphij return CHECK_EMPTY; 1311285612Sdelphij if (*cptr++ != '$') 1312285612Sdelphij return CHECK_INVALID; 1313285612Sdelphij 1314285612Sdelphij /* -*- advance context beyond start character */ 1315285612Sdelphij data->base++; 1316285612Sdelphij data->cptr++; 1317285612Sdelphij data->blen--; 1318285612Sdelphij 1319285612Sdelphij /* -*- field name: '[A-Z][A-Z0-9]{4,},' */ 1320285612Sdelphij if (*cptr < 'A' || *cptr > 'Z') 1321285612Sdelphij return CHECK_INVALID; 1322285612Sdelphij cs_l ^= *cptr++; 1323285612Sdelphij while ((*cptr >= 'A' && *cptr <= 'Z') || 1324285612Sdelphij (*cptr >= '0' && *cptr <= '9') ) 1325285612Sdelphij cs_l ^= *cptr++; 1326285612Sdelphij if (*cptr != ',' || (cptr - data->base) < NMEA_PROTO_IDLEN) 1327285612Sdelphij return CHECK_INVALID; 1328285612Sdelphij cs_l ^= *cptr++; 1329285612Sdelphij 1330285612Sdelphij /* -*- data: '[^*]*' */ 1331285612Sdelphij while (*cptr && *cptr != '*') 1332285612Sdelphij cs_l ^= *cptr++; 1333285612Sdelphij 1334285612Sdelphij /* -*- checksum field: (\*[0-9A-F]{2})?$ */ 1335285612Sdelphij if (*cptr == '\0') 1336285612Sdelphij return CHECK_VALID; 1337285612Sdelphij if (*cptr != '*' || cptr != eptr - 3 || 1338285612Sdelphij (cptr - data->base) >= NMEA_PROTO_MAXLEN) 1339285612Sdelphij return CHECK_INVALID; 1340285612Sdelphij 1341285612Sdelphij for (cptr++; (tmp = *cptr) != '\0'; cptr++) { 1342285612Sdelphij if (tmp >= '0' && tmp <= '9') 1343285612Sdelphij cs_r = (cs_r << 4) + (tmp - '0'); 1344285612Sdelphij else if (tmp >= 'A' && tmp <= 'F') 1345285612Sdelphij cs_r = (cs_r << 4) + (tmp - 'A' + 10); 1346285612Sdelphij else 1347285612Sdelphij break; 134854359Sroberto } 1349285612Sdelphij 1350285612Sdelphij /* -*- make sure we are at end of string and csum matches */ 1351285612Sdelphij if (cptr != eptr || cs_l != cs_r) 1352285612Sdelphij return CHECK_INVALID; 1353285612Sdelphij 1354285612Sdelphij return CHECK_CSVALID; 1355285612Sdelphij} 1356285612Sdelphij 1357285612Sdelphij/* 1358285612Sdelphij * ------------------------------------------------------------------- 1359285612Sdelphij * fetch a data field by index, zero being the name field. If this 1360285612Sdelphij * function is called repeatedly with increasing indices, the total load 1361285612Sdelphij * is O(n), n being the length of the string; if it is called with 1362285612Sdelphij * decreasing indices, the total load is O(n^2). Try not to go backwards 1363285612Sdelphij * too often. 1364285612Sdelphij * ------------------------------------------------------------------- 1365285612Sdelphij */ 1366285612Sdelphijstatic char * 1367285612Sdelphijfield_parse( 1368285612Sdelphij nmea_data * data, 1369285612Sdelphij int fn 1370285612Sdelphij ) 1371285612Sdelphij{ 1372285612Sdelphij char tmp; 1373285612Sdelphij 1374285612Sdelphij if (fn < data->cidx) { 1375285612Sdelphij data->cidx = 0; 1376285612Sdelphij data->cptr = data->base; 137782498Sroberto } 1378285612Sdelphij while ((fn > data->cidx) && (tmp = *data->cptr) != '\0') { 1379285612Sdelphij data->cidx += (tmp == ','); 1380285612Sdelphij data->cptr++; 1381285612Sdelphij } 1382285612Sdelphij return data->cptr; 1383285612Sdelphij} 138454359Sroberto 1385285612Sdelphij/* 1386285612Sdelphij * ------------------------------------------------------------------- 1387285612Sdelphij * Wipe (that is, overwrite with '_') data fields and the checksum in 1388285612Sdelphij * the last timecode. The list of field indices is given as integers 1389285612Sdelphij * in a varargs list, preferrably in ascending order, in any case 1390285612Sdelphij * terminated by a negative field index. 1391285612Sdelphij * 1392285612Sdelphij * A maximum number of 8 fields can be overwritten at once to guard 1393285612Sdelphij * against runaway (that is, unterminated) argument lists. 1394285612Sdelphij * 1395285612Sdelphij * This function affects what a remote user can see with 1396285612Sdelphij * 1397285612Sdelphij * ntpq -c clockvar <server> 1398285612Sdelphij * 1399285612Sdelphij * Note that this also removes the wiped fields from any clockstats 1400285612Sdelphij * log. Some NTP operators monitor their NMEA GPS using the change in 1401285612Sdelphij * location in clockstats over time as as a proxy for the quality of 1402285612Sdelphij * GPS reception and thereby time reported. 1403285612Sdelphij * ------------------------------------------------------------------- 1404285612Sdelphij */ 1405285612Sdelphijstatic void 1406285612Sdelphijfield_wipe( 1407285612Sdelphij nmea_data * data, 1408285612Sdelphij ... 1409285612Sdelphij ) 1410285612Sdelphij{ 1411285612Sdelphij va_list va; /* vararg index list */ 1412285612Sdelphij int fcnt; /* safeguard against runaway arglist */ 1413285612Sdelphij int fidx; /* field to nuke, or -1 for checksum */ 1414285612Sdelphij char * cp; /* overwrite destination */ 1415285612Sdelphij 1416285612Sdelphij fcnt = 8; 1417285612Sdelphij cp = NULL; 1418285612Sdelphij va_start(va, data); 1419285612Sdelphij do { 1420285612Sdelphij fidx = va_arg(va, int); 1421285612Sdelphij if (fidx >= 0 && fidx <= NMEA_PROTO_FIELDS) { 1422285612Sdelphij cp = field_parse(data, fidx); 1423285612Sdelphij } else { 1424285612Sdelphij cp = data->base + data->blen; 1425285612Sdelphij if (data->blen >= 3 && cp[-3] == '*') 1426285612Sdelphij cp -= 2; 1427285612Sdelphij } 1428285612Sdelphij for ( ; '\0' != *cp && '*' != *cp && ',' != *cp; cp++) 1429285612Sdelphij if ('.' != *cp) 1430285612Sdelphij *cp = '_'; 1431285612Sdelphij } while (fcnt-- && fidx >= 0); 1432285612Sdelphij va_end(va); 1433285612Sdelphij} 1434285612Sdelphij 1435285612Sdelphij/* 1436285612Sdelphij * ------------------------------------------------------------------- 1437285612Sdelphij * PARSING HELPERS 1438285612Sdelphij * ------------------------------------------------------------------- 1439285612Sdelphij * 1440285612Sdelphij * Check sync status 1441285612Sdelphij * 1442285612Sdelphij * If the character at the data field start matches the tag value, 1443285612Sdelphij * return LEAP_NOWARNING and LEAP_NOTINSYNC otherwise. If the 'inverted' 1444285612Sdelphij * flag is given, just the opposite value is returned. If there is no 1445285612Sdelphij * data field (*cp points to the NUL byte) the result is LEAP_NOTINSYNC. 1446285612Sdelphij * ------------------------------------------------------------------- 1447285612Sdelphij */ 1448285612Sdelphijstatic u_char 1449285612Sdelphijparse_qual( 1450285612Sdelphij nmea_data * rd, 1451285612Sdelphij int idx, 1452285612Sdelphij char tag, 1453285612Sdelphij int inv 1454285612Sdelphij ) 1455285612Sdelphij{ 1456285612Sdelphij static const u_char table[2] = 1457285612Sdelphij { LEAP_NOTINSYNC, LEAP_NOWARNING }; 1458285612Sdelphij char * dp; 1459285612Sdelphij 1460285612Sdelphij dp = field_parse(rd, idx); 1461285612Sdelphij 1462285612Sdelphij return table[ *dp && ((*dp == tag) == !inv) ]; 1463285612Sdelphij} 1464285612Sdelphij 1465285612Sdelphij/* 1466285612Sdelphij * ------------------------------------------------------------------- 1467285612Sdelphij * Parse a time stamp in HHMMSS[.sss] format with error checking. 1468285612Sdelphij * 1469285612Sdelphij * returns 1 on success, 0 on failure 1470285612Sdelphij * ------------------------------------------------------------------- 1471285612Sdelphij */ 1472285612Sdelphijstatic int 1473285612Sdelphijparse_time( 1474285612Sdelphij struct calendar * jd, /* result calendar pointer */ 1475285612Sdelphij long * ns, /* storage for nsec fraction */ 1476285612Sdelphij nmea_data * rd, 1477285612Sdelphij int idx 1478285612Sdelphij ) 1479285612Sdelphij{ 1480285612Sdelphij static const unsigned long weight[4] = { 1481285612Sdelphij 0, 100000000, 10000000, 1000000 1482285612Sdelphij }; 1483285612Sdelphij 1484285612Sdelphij int rc; 1485285612Sdelphij u_int h; 1486285612Sdelphij u_int m; 1487285612Sdelphij u_int s; 1488285612Sdelphij int p1; 1489285612Sdelphij int p2; 1490285612Sdelphij u_long f; 1491285612Sdelphij char * dp; 1492285612Sdelphij 1493285612Sdelphij dp = field_parse(rd, idx); 1494285612Sdelphij rc = sscanf(dp, "%2u%2u%2u%n.%3lu%n", &h, &m, &s, &p1, &f, &p2); 1495285612Sdelphij if (rc < 3 || p1 != 6) { 1496285612Sdelphij DPRINTF(1, ("nmea: invalid time code: '%.6s'\n", dp)); 1497285612Sdelphij return FALSE; 149854359Sroberto } 1499285612Sdelphij 1500285612Sdelphij /* value sanity check */ 1501285612Sdelphij if (h > 23 || m > 59 || s > 60) { 1502285612Sdelphij DPRINTF(1, ("nmea: invalid time spec %02u:%02u:%02u\n", 1503285612Sdelphij h, m, s)); 1504285612Sdelphij return FALSE; 1505285612Sdelphij } 150654359Sroberto 1507285612Sdelphij jd->hour = (u_char)h; 1508285612Sdelphij jd->minute = (u_char)m; 1509285612Sdelphij jd->second = (u_char)s; 1510285612Sdelphij /* if we have a fraction, scale it up to nanoseconds. */ 1511285612Sdelphij if (rc == 4) 1512285612Sdelphij *ns = f * weight[p2 - p1 - 1]; 1513285612Sdelphij else 1514285612Sdelphij *ns = 0; 1515285612Sdelphij 1516285612Sdelphij return TRUE; 1517285612Sdelphij} 1518285612Sdelphij 1519285612Sdelphij/* 1520285612Sdelphij * ------------------------------------------------------------------- 1521285612Sdelphij * Parse a date string from an NMEA sentence. This could either be a 1522285612Sdelphij * partial date in DDMMYY format in one field, or DD,MM,YYYY full date 1523285612Sdelphij * spec spanning three fields. This function does some extensive error 1524285612Sdelphij * checking to make sure the date string was consistent. 1525285612Sdelphij * 1526285612Sdelphij * returns 1 on success, 0 on failure 1527285612Sdelphij * ------------------------------------------------------------------- 1528285612Sdelphij */ 1529285612Sdelphijstatic int 1530285612Sdelphijparse_date( 1531285612Sdelphij struct calendar * jd, /* result pointer */ 1532285612Sdelphij nmea_data * rd, 1533285612Sdelphij int idx, 1534285612Sdelphij enum date_fmt fmt 1535285612Sdelphij ) 1536285612Sdelphij{ 1537285612Sdelphij int rc; 1538285612Sdelphij u_int y; 1539285612Sdelphij u_int m; 1540285612Sdelphij u_int d; 1541285612Sdelphij int p; 1542285612Sdelphij char * dp; 1543285612Sdelphij 1544285612Sdelphij dp = field_parse(rd, idx); 1545285612Sdelphij switch (fmt) { 1546285612Sdelphij 1547285612Sdelphij case DATE_1_DDMMYY: 1548285612Sdelphij rc = sscanf(dp, "%2u%2u%2u%n", &d, &m, &y, &p); 1549285612Sdelphij if (rc != 3 || p != 6) { 1550285612Sdelphij DPRINTF(1, ("nmea: invalid date code: '%.6s'\n", 1551285612Sdelphij dp)); 1552285612Sdelphij return FALSE; 155354359Sroberto } 1554285612Sdelphij break; 1555285612Sdelphij 1556285612Sdelphij case DATE_3_DDMMYYYY: 1557285612Sdelphij rc = sscanf(dp, "%2u,%2u,%4u%n", &d, &m, &y, &p); 1558285612Sdelphij if (rc != 3 || p != 10) { 1559285612Sdelphij DPRINTF(1, ("nmea: invalid date code: '%.10s'\n", 1560285612Sdelphij dp)); 1561285612Sdelphij return FALSE; 156254359Sroberto } 1563285612Sdelphij break; 1564285612Sdelphij 1565285612Sdelphij default: 1566285612Sdelphij DPRINTF(1, ("nmea: invalid parse format: %d\n", fmt)); 1567285612Sdelphij return FALSE; 156854359Sroberto } 156954359Sroberto 1570285612Sdelphij /* value sanity check */ 1571285612Sdelphij if (d < 1 || d > 31 || m < 1 || m > 12) { 1572285612Sdelphij DPRINTF(1, ("nmea: invalid date spec (YMD) %04u:%02u:%02u\n", 1573285612Sdelphij y, m, d)); 1574285612Sdelphij return FALSE; 157554359Sroberto } 1576285612Sdelphij 1577285612Sdelphij /* store results */ 1578285612Sdelphij jd->monthday = (u_char)d; 1579285612Sdelphij jd->month = (u_char)m; 1580285612Sdelphij jd->year = (u_short)y; 158154359Sroberto 1582285612Sdelphij return TRUE; 1583285612Sdelphij} 158482498Sroberto 1585285612Sdelphij/* 1586285612Sdelphij * ------------------------------------------------------------------- 1587285612Sdelphij * Parse GPS week time info from an NMEA sentence. This info contains 1588285612Sdelphij * the GPS week number, the GPS time-of-week and the leap seconds GPS 1589285612Sdelphij * to UTC. 1590285612Sdelphij * 1591285612Sdelphij * returns 1 on success, 0 on failure 1592285612Sdelphij * ------------------------------------------------------------------- 1593285612Sdelphij */ 1594285612Sdelphijstatic int 1595285612Sdelphijparse_weekdata( 1596285612Sdelphij gps_weektm * wd, 1597285612Sdelphij nmea_data * rd, 1598285612Sdelphij int weekidx, 1599285612Sdelphij int timeidx, 1600285612Sdelphij int leapidx 1601285612Sdelphij ) 1602285612Sdelphij{ 1603285612Sdelphij u_long secs; 1604285612Sdelphij int fcnt; 1605285612Sdelphij 1606285612Sdelphij /* parse fields and count success */ 1607285612Sdelphij fcnt = sscanf(field_parse(rd, weekidx), "%hu", &wd->wt_week); 1608285612Sdelphij fcnt += sscanf(field_parse(rd, timeidx), "%lu", &secs); 1609285612Sdelphij fcnt += sscanf(field_parse(rd, leapidx), "%hd", &wd->wt_leap); 1610285612Sdelphij if (fcnt != 3 || wd->wt_week >= 1024 || secs >= 7*SECSPERDAY) { 1611285612Sdelphij DPRINTF(1, ("nmea: parse_weekdata: invalid weektime spec\n")); 1612285612Sdelphij return FALSE; 161354359Sroberto } 1614285612Sdelphij wd->wt_time = (u_int32)secs; 161554359Sroberto 1616285612Sdelphij return TRUE; 1617285612Sdelphij} 161882498Sroberto 1619285612Sdelphij/* 1620285612Sdelphij * ------------------------------------------------------------------- 1621285612Sdelphij * funny calendar-oriented stuff -- perhaps a bit hard to grok. 1622285612Sdelphij * ------------------------------------------------------------------- 1623285612Sdelphij * 1624285612Sdelphij * Unfold a time-of-day (seconds since midnight) around the current 1625285612Sdelphij * system time in a manner that guarantees an absolute difference of 1626285612Sdelphij * less than 12hrs. 1627285612Sdelphij * 1628285612Sdelphij * This function is used for NMEA sentences that contain no date 1629285612Sdelphij * information. This requires the system clock to be in +/-12hrs 1630285612Sdelphij * around the true time, or the clock will synchronize the system 1day 1631285612Sdelphij * off if not augmented with a time sources that also provide the 1632285612Sdelphij * necessary date information. 1633285612Sdelphij * 1634285612Sdelphij * The function updates the calendar structure it also uses as 1635285612Sdelphij * input to fetch the time from. 1636285612Sdelphij * 1637285612Sdelphij * returns 1 on success, 0 on failure 1638285612Sdelphij * ------------------------------------------------------------------- 1639285612Sdelphij */ 1640285612Sdelphijstatic int 1641285612Sdelphijunfold_day( 1642285612Sdelphij struct calendar * jd, 1643285612Sdelphij u_int32 rec_ui 1644285612Sdelphij ) 1645285612Sdelphij{ 1646285612Sdelphij vint64 rec_qw; 1647285612Sdelphij ntpcal_split rec_ds; 164882498Sroberto 164954359Sroberto /* 1650285612Sdelphij * basically this is the peridiodic extension of the receive 1651285612Sdelphij * time - 12hrs to the time-of-day with a period of 1 day. 1652285612Sdelphij * But we would have to execute this in 64bit arithmetic, and we 1653285612Sdelphij * cannot assume we can do this; therefore this is done 1654285612Sdelphij * in split representation. 165554359Sroberto */ 1656285612Sdelphij rec_qw = ntpcal_ntp_to_ntp(rec_ui - SECSPERDAY/2, NULL); 1657285612Sdelphij rec_ds = ntpcal_daysplit(&rec_qw); 1658285612Sdelphij rec_ds.lo = ntpcal_periodic_extend(rec_ds.lo, 1659285612Sdelphij ntpcal_date_to_daysec(jd), 1660285612Sdelphij SECSPERDAY); 1661285612Sdelphij rec_ds.hi += ntpcal_daysec_to_date(jd, rec_ds.lo); 1662285612Sdelphij return (ntpcal_rd_to_date(jd, rec_ds.hi + DAY_NTP_STARTS) >= 0); 1663285612Sdelphij} 166454359Sroberto 1665285612Sdelphij/* 1666285612Sdelphij * ------------------------------------------------------------------- 1667285612Sdelphij * A 2-digit year is expanded into full year spec around the year found 1668285612Sdelphij * in 'jd->year'. This should be in +79/-19 years around the system time, 1669285612Sdelphij * or the result will be off by 100 years. The assymetric behaviour was 1670285612Sdelphij * chosen to enable inital sync for systems that do not have a 1671285612Sdelphij * battery-backup clock and start with a date that is typically years in 1672285612Sdelphij * the past. 1673285612Sdelphij * 1674285612Sdelphij * Since the GPS epoch starts at 1980-01-06, the resulting year will be 1675285612Sdelphij * not be before 1980 in any case. 1676285612Sdelphij * 1677285612Sdelphij * returns 1 on success, 0 on failure 1678285612Sdelphij * ------------------------------------------------------------------- 1679285612Sdelphij */ 1680285612Sdelphijstatic int 1681285612Sdelphijunfold_century( 1682285612Sdelphij struct calendar * jd, 1683285612Sdelphij u_int32 rec_ui 1684285612Sdelphij ) 1685285612Sdelphij{ 1686285612Sdelphij struct calendar rec; 1687285612Sdelphij int32 baseyear; 168882498Sroberto 1689285612Sdelphij ntpcal_ntp_to_date(&rec, rec_ui, NULL); 1690285612Sdelphij baseyear = rec.year - 20; 1691285612Sdelphij if (baseyear < g_gpsMinYear) 1692285612Sdelphij baseyear = g_gpsMinYear; 1693285612Sdelphij jd->year = (u_short)ntpcal_periodic_extend(baseyear, jd->year, 1694285612Sdelphij 100); 169582498Sroberto 1696285612Sdelphij return ((baseyear <= jd->year) && (baseyear + 100 > jd->year)); 169754359Sroberto} 169854359Sroberto 169954359Sroberto/* 1700285612Sdelphij * ------------------------------------------------------------------- 1701285612Sdelphij * A 2-digit year is expanded into a full year spec by correlation with 1702285612Sdelphij * a GPS week number and the current leap second count. 170354359Sroberto * 1704285612Sdelphij * The GPS week time scale counts weeks since Sunday, 1980-01-06, modulo 1705285612Sdelphij * 1024 and seconds since start of the week. The GPS time scale is based 1706285612Sdelphij * on international atomic time (TAI), so the leap second difference to 1707285612Sdelphij * UTC is also needed for a proper conversion. 1708285612Sdelphij * 1709285612Sdelphij * A brute-force analysis (that is, test for every date) shows that a 1710285612Sdelphij * wrong assignment of the century can not happen between the years 1900 1711285612Sdelphij * to 2399 when comparing the week signatures for different 1712285612Sdelphij * centuries. (I *think* that will not happen for 400*1024 years, but I 1713285612Sdelphij * have no valid proof. -*-perlinger@ntp.org-*-) 1714285612Sdelphij * 1715285612Sdelphij * This function is bound to to work between years 1980 and 2399 1716285612Sdelphij * (inclusive), which should suffice for now ;-) 1717285612Sdelphij * 1718285612Sdelphij * Note: This function needs a full date&time spec on input due to the 1719285612Sdelphij * necessary leap second corrections! 1720285612Sdelphij * 1721285612Sdelphij * returns 1 on success, 0 on failure 1722285612Sdelphij * ------------------------------------------------------------------- 172354359Sroberto */ 1724285612Sdelphijstatic int 1725285612Sdelphijgpsfix_century( 1726285612Sdelphij struct calendar * jd, 1727285612Sdelphij const gps_weektm * wd, 1728285612Sdelphij u_short * century 1729285612Sdelphij ) 173054359Sroberto{ 1731285612Sdelphij int32 days; 1732285612Sdelphij int32 doff; 1733285612Sdelphij u_short week; 1734285612Sdelphij u_short year; 1735285612Sdelphij int loop; 173654359Sroberto 1737285612Sdelphij /* Get day offset. Assumes that the input time is in range and 1738285612Sdelphij * that the leap seconds do not shift more than +/-1 day. 1739285612Sdelphij */ 1740285612Sdelphij doff = ntpcal_date_to_daysec(jd) + wd->wt_leap; 1741285612Sdelphij doff = (doff >= SECSPERDAY) - (doff < 0); 174254359Sroberto 174354359Sroberto /* 1744285612Sdelphij * Loop over centuries to get a match, starting with the last 1745285612Sdelphij * successful one. (Or with the 19th century if the cached value 1746285612Sdelphij * is out of range...) 174754359Sroberto */ 1748285612Sdelphij year = jd->year % 100; 1749285612Sdelphij for (loop = 5; loop > 0; loop--,(*century)++) { 1750285612Sdelphij if (*century < 19 || *century >= 24) 1751285612Sdelphij *century = 19; 1752285612Sdelphij /* Get days and week in GPS epoch */ 1753285612Sdelphij jd->year = year + *century * 100; 1754285612Sdelphij days = ntpcal_date_to_rd(jd) - DAY_GPS_STARTS + doff; 1755285612Sdelphij week = (days / 7) % 1024; 1756285612Sdelphij if (days >= 0 && wd->wt_week == week) 1757285612Sdelphij return TRUE; /* matched... */ 1758285612Sdelphij } 175954359Sroberto 1760285612Sdelphij jd->year = year; 1761285612Sdelphij return FALSE; /* match failed... */ 176254359Sroberto} 176354359Sroberto 176454359Sroberto/* 1765285612Sdelphij * ------------------------------------------------------------------- 1766285612Sdelphij * And now the final execise: Considering the fact that many (most?) 1767285612Sdelphij * GPS receivers cannot handle a GPS epoch wrap well, we try to 1768285612Sdelphij * compensate for that problem by unwrapping a GPS epoch around the 1769285612Sdelphij * receive stamp. Another execise in periodic unfolding, of course, 1770285612Sdelphij * but with enough points to take care of. 177154359Sroberto * 1772285612Sdelphij * Note: The integral part of 'tofs' is intended to handle small(!) 1773285612Sdelphij * systematic offsets, as -1 for handling $GPZDG, which gives the 1774285612Sdelphij * following second. (sigh...) The absolute value shall be less than a 1775285612Sdelphij * day (86400 seconds). 1776285612Sdelphij * ------------------------------------------------------------------- 177754359Sroberto */ 1778285612Sdelphijstatic l_fp 1779285612Sdelphijeval_gps_time( 1780285612Sdelphij struct peer * peer, /* for logging etc */ 1781285612Sdelphij const struct calendar * gpst, /* GPS time stamp */ 1782285612Sdelphij const struct timespec * tofs, /* GPS frac second & offset */ 1783285612Sdelphij const l_fp * xrecv /* receive time stamp */ 178454359Sroberto ) 178554359Sroberto{ 1786285612Sdelphij struct refclockproc * const pp = peer->procptr; 1787285612Sdelphij nmea_unit * const up = (nmea_unit *)pp->unitptr; 178854359Sroberto 1789285612Sdelphij l_fp retv; 1790285612Sdelphij 1791285612Sdelphij /* components of calculation */ 1792285612Sdelphij int32_t rcv_sec, rcv_day; /* receive ToD and day */ 1793285612Sdelphij int32_t gps_sec, gps_day; /* GPS ToD and day in NTP epoch */ 1794285612Sdelphij int32_t adj_day, weeks; /* adjusted GPS day and week shift */ 1795285612Sdelphij 1796285612Sdelphij /* some temporaries to shuffle data */ 1797285612Sdelphij vint64 vi64; 1798285612Sdelphij ntpcal_split rs64; 1799285612Sdelphij 1800285612Sdelphij /* evaluate time stamp from receiver. */ 1801285612Sdelphij gps_sec = ntpcal_date_to_daysec(gpst); 1802285612Sdelphij gps_day = ntpcal_date_to_rd(gpst) - DAY_NTP_STARTS; 1803285612Sdelphij 1804285612Sdelphij /* merge in fractional offset */ 1805285612Sdelphij retv = tspec_intv_to_lfp(*tofs); 1806285612Sdelphij gps_sec += retv.l_i; 1807285612Sdelphij 1808285612Sdelphij /* If we fully trust the GPS receiver, just combine days and 1809285612Sdelphij * seconds and be done. */ 1810285612Sdelphij if (peer->ttl & NMEA_DATETRUST_MASK) { 1811285612Sdelphij retv.l_ui = ntpcal_dayjoin(gps_day, gps_sec).D_s.lo; 1812285612Sdelphij return retv; 181354359Sroberto } 1814285612Sdelphij 1815285612Sdelphij /* So we do not trust the GPS receiver to deliver a correct date 1816285612Sdelphij * due to the GPS epoch changes. We map the date from the 1817285612Sdelphij * receiver into the +/-512 week interval around the receive 1818285612Sdelphij * time in that case. This would be a tad easier with 64bit 1819285612Sdelphij * calculations, but again, we restrict the code to 32bit ops 1820285612Sdelphij * when possible. */ 1821285612Sdelphij 1822285612Sdelphij /* - make sure the GPS fractional day is normalised 1823285612Sdelphij * Applying the offset value might have put us slightly over the 1824285612Sdelphij * edge of the allowed range for seconds-of-day. Doing a full 1825285612Sdelphij * division with floor correction is overkill here; a simple 1826285612Sdelphij * addition or subtraction step is sufficient. Using WHILE loops 1827285612Sdelphij * gives the right result even if the offset exceeds one day, 1828285612Sdelphij * which is NOT what it's intented for! */ 1829285612Sdelphij while (gps_sec >= SECSPERDAY) { 1830285612Sdelphij gps_sec -= SECSPERDAY; 1831285612Sdelphij gps_day += 1; 1832285612Sdelphij } 1833285612Sdelphij while (gps_sec < 0) { 1834285612Sdelphij gps_sec += SECSPERDAY; 1835285612Sdelphij gps_day -= 1; 1836285612Sdelphij } 1837285612Sdelphij 1838285612Sdelphij /* - get unfold base: day of full recv time - 512 weeks */ 1839285612Sdelphij vi64 = ntpcal_ntp_to_ntp(xrecv->l_ui, NULL); 1840285612Sdelphij rs64 = ntpcal_daysplit(&vi64); 1841285612Sdelphij rcv_sec = rs64.lo; 1842285612Sdelphij rcv_day = rs64.hi - 512 * 7; 1843285612Sdelphij 1844285612Sdelphij /* - take the fractional days into account 1845285612Sdelphij * If the fractional day of the GPS time is smaller than the 1846285612Sdelphij * fractional day of the receive time, we shift the base day for 1847285612Sdelphij * the unfold by 1. */ 1848285612Sdelphij if ( gps_sec < rcv_sec 1849285612Sdelphij || (gps_sec == rcv_sec && retv.l_uf < xrecv->l_uf)) 1850285612Sdelphij rcv_day += 1; 1851285612Sdelphij 1852285612Sdelphij /* - don't warp ahead of GPS invention! */ 1853285612Sdelphij if (rcv_day < g_gpsMinBase) 1854285612Sdelphij rcv_day = g_gpsMinBase; 1855285612Sdelphij 1856285612Sdelphij /* - let the magic happen: */ 1857285612Sdelphij adj_day = ntpcal_periodic_extend(rcv_day, gps_day, 1024*7); 1858285612Sdelphij 1859285612Sdelphij /* - check if we should log a GPS epoch warp */ 1860285612Sdelphij weeks = (adj_day - gps_day) / 7; 1861285612Sdelphij if (weeks != up->epoch_warp) { 1862285612Sdelphij up->epoch_warp = weeks; 1863285612Sdelphij LOGIF(CLOCKINFO, (LOG_INFO, 1864285612Sdelphij "%s Changed GPS epoch warp to %d weeks", 1865285612Sdelphij refnumtoa(&peer->srcadr), weeks)); 1866285612Sdelphij } 1867285612Sdelphij 1868285612Sdelphij /* - build result and be done */ 1869285612Sdelphij retv.l_ui = ntpcal_dayjoin(adj_day, gps_sec).D_s.lo; 1870285612Sdelphij return retv; 187154359Sroberto} 187254359Sroberto 1873285612Sdelphij/* 1874285612Sdelphij * =================================================================== 1875285612Sdelphij * 1876285612Sdelphij * NMEAD support 1877285612Sdelphij * 1878285612Sdelphij * original nmead support added by Jon Miner (cp_n18@yahoo.com) 1879285612Sdelphij * 1880285612Sdelphij * See http://home.hiwaay.net/~taylorc/gps/nmea-server/ 1881285612Sdelphij * for information about nmead 1882285612Sdelphij * 1883285612Sdelphij * To use this, you need to create a link from /dev/gpsX to 1884285612Sdelphij * the server:port where nmead is running. Something like this: 1885285612Sdelphij * 1886285612Sdelphij * ln -s server:port /dev/gps1 1887285612Sdelphij * 1888285612Sdelphij * Split into separate function by Juergen Perlinger 1889285612Sdelphij * (perlinger-at-ntp-dot-org) 1890285612Sdelphij * 1891285612Sdelphij * =================================================================== 1892285612Sdelphij */ 1893285612Sdelphijstatic int 1894285612Sdelphijnmead_open( 1895285612Sdelphij const char * device 189654359Sroberto ) 189754359Sroberto{ 1898285612Sdelphij int fd = -1; /* result file descriptor */ 1899285612Sdelphij 1900285612Sdelphij#ifdef HAVE_READLINK 1901285612Sdelphij char host[80]; /* link target buffer */ 1902285612Sdelphij char * port; /* port name or number */ 1903285612Sdelphij int rc; /* result code (several)*/ 1904285612Sdelphij int sh; /* socket handle */ 1905285612Sdelphij struct addrinfo ai_hint; /* resolution hint */ 1906285612Sdelphij struct addrinfo *ai_list; /* resolution result */ 1907285612Sdelphij struct addrinfo *ai; /* result scan ptr */ 190854359Sroberto 1909285612Sdelphij fd = -1; 1910285612Sdelphij 1911285612Sdelphij /* try to read as link, make sure no overflow occurs */ 1912285612Sdelphij rc = readlink(device, host, sizeof(host)); 1913285612Sdelphij if ((size_t)rc >= sizeof(host)) 1914285612Sdelphij return fd; /* error / overflow / truncation */ 1915285612Sdelphij host[rc] = '\0'; /* readlink does not place NUL */ 1916285612Sdelphij 1917285612Sdelphij /* get port */ 1918285612Sdelphij port = strchr(host, ':'); 1919285612Sdelphij if (!port) 1920285612Sdelphij return fd; /* not 'host:port' syntax ? */ 1921285612Sdelphij *port++ = '\0'; /* put in separator */ 1922285612Sdelphij 1923285612Sdelphij /* get address infos and try to open socket 1924285612Sdelphij * 1925285612Sdelphij * This getaddrinfo() is naughty in ntpd's nonblocking main 1926285612Sdelphij * thread, but you have to go out of your wary to use this code 1927285612Sdelphij * and typically the blocking is at startup where its impact is 1928285612Sdelphij * reduced. The same holds for the 'connect()', as it is 1929285612Sdelphij * blocking, too... 1930285612Sdelphij */ 1931285612Sdelphij ZERO(ai_hint); 1932285612Sdelphij ai_hint.ai_protocol = IPPROTO_TCP; 1933285612Sdelphij ai_hint.ai_socktype = SOCK_STREAM; 1934285612Sdelphij if (getaddrinfo(host, port, &ai_hint, &ai_list)) 1935285612Sdelphij return fd; 1936285612Sdelphij 1937285612Sdelphij for (ai = ai_list; ai && (fd == -1); ai = ai->ai_next) { 1938285612Sdelphij sh = socket(ai->ai_family, ai->ai_socktype, 1939285612Sdelphij ai->ai_protocol); 1940285612Sdelphij if (INVALID_SOCKET == sh) 1941285612Sdelphij continue; 1942285612Sdelphij rc = connect(sh, ai->ai_addr, ai->ai_addrlen); 1943285612Sdelphij if (-1 != rc) 1944285612Sdelphij fd = sh; 1945285612Sdelphij else 1946285612Sdelphij close(sh); 194754359Sroberto } 1948285612Sdelphij freeaddrinfo(ai_list); 1949285612Sdelphij#else 1950285612Sdelphij fd = -1; 1951285612Sdelphij#endif 1952285612Sdelphij 1953285612Sdelphij return fd; 195454359Sroberto} 195554359Sroberto#else 1956285612SdelphijNONEMPTY_TRANSLATION_UNIT 1957285612Sdelphij#endif /* REFCLOCK && CLOCK_NMEA */ 1958