/* offset is actual_ts - clock_ts */ static void chrony_send(struct gps_device_t *session, struct timedrift_t *td) { struct sock_sample sample; /* chrony expects tv-sec since Jan 1970 */ sample.pulse = 0; sample.leap = session->context->leap_notify; sample.magic = SOCK_MAGIC; /*@-type@*//* splint is confused about struct timespec */ TSTOTV(&sample.tv, &td->clock); /*@-compdef@*/ sample.offset = timespec_diff_ns(td->real, td->clock) / 1e9; /*@+compdef@*/ #ifdef __COVERITY__ sample._pad = 0; #endif /* __COVERITY__ */ /*@+type@*/ /*@-type@*/ /* splint is confused about struct timespec */ gpsd_report(&session->context->errout, LOG_RAW, "PPS chrony_send %lu.%09lu @ %lu.%09lu Offset: %0.9f\n", (unsigned long)td->real.tv_sec, (unsigned long)td->real.tv_nsec, (unsigned long)td->clock.tv_sec, (unsigned long)td->clock.tv_nsec, sample.offset); /*@+type@*/ (void)send(session->chronyfd, &sample, sizeof (sample), 0); }
/** * iodef_time_set_from_ntpstamp: * @time: Pointer to a #iodef_time_t object. * @buf: Pointer to a string containing an NTP timestamp. * * Fills the @time object with information provided within the @buf NTP timestamp. * * Returns: 0 on success, a negative value if an error occured. */ int iodef_time_set_from_ntpstamp(iodef_time_t *time, const char *buf) { l_fp ts; struct timeval tv; unsigned ts_mask = TS_MASK; unsigned ts_roundbit = TS_ROUNDBIT; libiodef_return_val_if_fail(time, libiodef_error(LIBIODEF_ERROR_ASSERTION)); libiodef_return_val_if_fail(buf, libiodef_error(LIBIODEF_ERROR_ASSERTION)); if ( sscanf(buf, "%x.%x", &ts.l_ui, &ts.l_uf) < 2 ) return -1; /* * This transformation is a reverse form of the one found in * iodef_get_ntp_timestamp() */ ts.l_ui -= JAN_1970; ts.l_uf -= ts_roundbit; ts.l_uf &= ts_mask; TSTOTV(&ts, &tv); time->sec = tv.tv_sec; time->usec = tv.tv_usec; time->gmt_offset = 0; return 0; }
void test_MicrosecondsRounding(void) { const l_fp input = {{50}, 3865471UL}; /* Should round to 50.0009 */ const struct timeval expected = {50, 900}; struct timeval actual; TSTOTV(&input, &actual); TEST_ASSERT_EQUAL(expected.tv_sec, actual.tv_sec); TEST_ASSERT_EQUAL(expected.tv_usec, actual.tv_usec); }
void test_Seconds(void) { const l_fp input = {{50}, 0}; /* 50.0 s */ const struct timeval expected = {50, 0}; struct timeval actual; TSTOTV(&input, &actual); TEST_ASSERT_EQUAL(expected.tv_sec, actual.tv_sec); TEST_ASSERT_EQUAL(expected.tv_usec, actual.tv_usec); }
void test_MicrosecondsExact(void) { const u_long HALF = 2147483648UL; const l_fp input = {{50}, HALF}; /* 50.5 s */ const struct timeval expected = {50, 500000}; struct timeval actual; TSTOTV(&input, &actual); TEST_ASSERT_EQUAL(expected.tv_sec, actual.tv_sec); TEST_ASSERT_EQUAL(expected.tv_usec, actual.tv_usec); }
/* offset is actual_ts - clock_ts */ static void chrony_send(struct gps_device_t *session, struct timedelta_t *td) { char real_str[TIMESPEC_LEN]; char clock_str[TIMESPEC_LEN]; struct timespec offset; struct sock_sample sample; struct tm tm; int leap_notify = session->context->leap_notify; /* * insist that leap seconds only happen in june and december * GPS emits leap pending for 3 months prior to insertion * NTP expects leap pending for only 1 month prior to insertion * Per http://bugs.ntp.org/1090 * * ITU-R TF.460-6, Section 2.1, says lappe seconds can be primarily * in Jun/Dec but may be in March or September */ (void)gmtime_r( &(td->real.tv_sec), &tm); if ( 5 != tm.tm_mon && 11 != tm.tm_mon ) { /* Not june, not December, no way */ leap_notify = LEAP_NOWARNING; } /* chrony expects tv-sec since Jan 1970 */ sample.pulse = 0; sample.leap = leap_notify; sample.magic = SOCK_MAGIC; /* chronyd wants a timeval, not a timspec, not to worry, it is * just the top of the second */ TSTOTV(&sample.tv, &td->clock); /* calculate the offset as a timespec to not lose precision */ TS_SUB( &offset, &td->real, &td->clock); /* if tv_sec greater than 2 then tv_nsec loses precision, but * not a big deal as slewing will be required */ sample.offset = TSTONS( &offset ); sample._pad = 0; timespec_str( &td->real, real_str, sizeof(real_str) ); timespec_str( &td->clock, clock_str, sizeof(clock_str) ); gpsd_log(&session->context->errout, LOG_RAW, "PPS chrony_send %s @ %s Offset: %0.9f\n", real_str, clock_str, sample.offset); (void)send(session->chronyfd, &sample, sizeof (sample), 0); }
void test_OffsetCalculationPositiveOffset(void) { struct pkt rpkt; rpkt.precision = -16; // 0,000015259 rpkt.rootdelay = HTONS_FP(DTOUFP(0.125)); rpkt.rootdisp = HTONS_FP(DTOUFP(0.25)); // Synch Distance: (0.125+0.25)/2.0 == 0.1875 l_fp reftime; get_systime(&reftime); HTONL_FP(&reftime, &rpkt.reftime); l_fp tmp; // T1 - Originate timestamp tmp.l_ui = 1000000000UL; tmp.l_uf = 0UL; HTONL_FP(&tmp, &rpkt.org); // T2 - Receive timestamp tmp.l_ui = 1000000001UL; tmp.l_uf = 2147483648UL; HTONL_FP(&tmp, &rpkt.rec); // T3 - Transmit timestamp tmp.l_ui = 1000000002UL; tmp.l_uf = 0UL; HTONL_FP(&tmp, &rpkt.xmt); // T4 - Destination timestamp as standard timeval tmp.l_ui = 1000000001UL; tmp.l_uf = 0UL; struct timeval dst; TSTOTV(&tmp, &dst); dst.tv_sec -= JAN_1970; double offset, precision, synch_distance; offset_calculation(&rpkt, LEN_PKT_NOMAC, &dst, &offset, &precision, &synch_distance); TEST_ASSERT_EQUAL_DOUBLE(1.25, offset); TEST_ASSERT_EQUAL_DOUBLE(1. / ULOGTOD(16), precision); // 1.1250150000000001 ? TEST_ASSERT_EQUAL_DOUBLE(1.125015, synch_distance); }
void test_OffsetCalculationNegativeOffset(void) { struct pkt rpkt; l_fp reftime, tmp; struct timeval dst; double offset, precision, synch_distance; rpkt.precision = -1; rpkt.rootdelay = HTONS_FP(DTOUFP(0.5)); rpkt.rootdisp = HTONS_FP(DTOUFP(0.5)); /* Synch Distance is (0.5+0.5)/2.0, or 0.5 */ get_systime(&reftime); HTONL_FP(&reftime, &rpkt.reftime); /* T1 - Originate timestamp */ tmp.l_ui = 1000000001UL; tmp.l_uf = 0UL; HTONL_FP(&tmp, &rpkt.org); /* T2 - Receive timestamp */ tmp.l_ui = 1000000000UL; tmp.l_uf = 2147483648UL; HTONL_FP(&tmp, &rpkt.rec); /*/ T3 - Transmit timestamp */ tmp.l_ui = 1000000001UL; tmp.l_uf = 2147483648UL; HTONL_FP(&tmp, &rpkt.xmt); /* T4 - Destination timestamp as standard timeval */ tmp.l_ui = 1000000003UL; tmp.l_uf = 0UL; TSTOTV(&tmp, &dst); dst.tv_sec -= JAN_1970; offset_calculation(&rpkt, LEN_PKT_NOMAC, &dst, &offset, &precision, &synch_distance); TEST_ASSERT_EQUAL_DOUBLE(-1, offset); TEST_ASSERT_EQUAL_DOUBLE(1. / ULOGTOD(1), precision); TEST_ASSERT_EQUAL_DOUBLE(1.3333483333333334, synch_distance); }
TEST_F(mainTest, OffsetCalculationNegativeOffset) { pkt rpkt; rpkt.precision = -1; rpkt.rootdelay = HTONS_FP(DTOUFP(0.5)); rpkt.rootdisp = HTONS_FP(DTOUFP(0.5)); // Synch Distance is (0.5+0.5)/2.0, or 0.5 l_fp reftime; get_systime(&reftime); HTONL_FP(&reftime, &rpkt.reftime); l_fp tmp; // T1 - Originate timestamp tmp.l_ui = 1000000001UL; tmp.l_uf = 0UL; HTONL_FP(&tmp, &rpkt.org); // T2 - Receive timestamp tmp.l_ui = 1000000000UL; tmp.l_uf = 2147483648UL; HTONL_FP(&tmp, &rpkt.rec); // T3 - Transmit timestamp tmp.l_ui = 1000000001UL; tmp.l_uf = 2147483648UL; HTONL_FP(&tmp, &rpkt.xmt); // T4 - Destination timestamp as standard timeval tmp.l_ui = 1000000003UL; tmp.l_uf = 0UL; timeval dst; TSTOTV(&tmp, &dst); dst.tv_sec -= JAN_1970; double offset, precision, synch_distance; offset_calculation(&rpkt, LEN_PKT_NOMAC, &dst, &offset, &precision, &synch_distance); EXPECT_DOUBLE_EQ(-1, offset); EXPECT_DOUBLE_EQ(1. / ULOGTOD(1), precision); EXPECT_DOUBLE_EQ(1.3333483333333334, synch_distance); }
/*@-mustfreefresh -type@ -unrecog*/ static /*@null@*/ void *gpsd_ppsmonitor(void *arg) { struct gps_device_t *session = (struct gps_device_t *)arg; struct timeval tv; struct timespec ts; #if defined(TIOCMIWAIT) int cycle, duration, state = 0, laststate = -1, unchanged = 0; struct timeval pulse[2] = { {0, 0}, {0, 0} }; #endif /* TIOCMIWAIT */ #if defined(HAVE_SYS_TIMEPPS_H) int kpps_edge = 0; /* 0 = clear edge, 1 = assert edge */ int cycle_kpps, duration_kpps; struct timespec pulse_kpps[2] = { {0, 0}, {0, 0} }; struct timespec tv_kpps; pps_info_t pi; #endif /* for chrony SOCK interface, which allows nSec timekeeping */ #define SOCK_MAGIC 0x534f434b struct sock_sample { struct timeval tv; double offset; int pulse; int leap; int _pad; /* unused */ int magic; /* must be SOCK_MAGIC */ } sample; /* chrony must be started first as chrony insists on creating the socket */ /* open the chrony socket */ int chronyfd = -1; char chrony_path[PATH_MAX]; gpsd_report(LOG_PROG, "PPS Create Thread gpsd_ppsmonitor\n"); /* wait for the device to go active - makes this safe to call early */ while (session->gpsdata.gps_fd == -1) { /* should probably remove this once code is verified */ gpsd_report(LOG_PROG, "PPS thread awaiting device activation\n"); (void)sleep(1); } /* Activates PPS support for RS-232 or USB devices only. */ if (!(session->sourcetype == source_rs232 || session->sourcetype == source_usb)) { gpsd_report(LOG_PROG, "PPS thread deactivationde. Not RS-232 or USB device.\n"); (void)ntpshm_free(session->context, session->shmTimeP); session->shmTimeP = -1; return NULL; } if ( 0 == getuid() ) { /* this case will fire on command-line devices; * they're opened before priv-dropping. Matters because * only root can use /var/run. */ (void)snprintf(chrony_path, sizeof (chrony_path), "/var/run/chrony.%s.sock", basename(session->gpsdata.dev.path)); } else { (void)snprintf(chrony_path, sizeof (chrony_path), "/tmp/chrony.%s.sock", basename(session->gpsdata.dev.path)); } if (access(chrony_path, F_OK) != 0) { gpsd_report(LOG_PROG, "PPS chrony socket %s doesn't exist\n", chrony_path); } else { chronyfd = netlib_localsocket(chrony_path, SOCK_DGRAM); if (chronyfd < 0) gpsd_report(LOG_PROG, "PPS can not connect chrony socket: %s\n", chrony_path); else gpsd_report(LOG_RAW, "PPS using chrony socket: %s\n", chrony_path); } /* end chrony */ #if defined(HAVE_SYS_TIMEPPS_H) /* some operations in init_kernel_pps() require root privs */ (void)init_kernel_pps( session ); if ( 0 <= session->kernelpps_handle ) { gpsd_report(LOG_WARN, "KPPS kernel PPS will be used\n"); } memset( (void *)&pi, 0, sizeof(pps_info_t)); #endif /* root privileges are not required after this point */ /* * Wait for status change on any handshake line. The only assumption here * is that no GPS lights up more than one of these pins. By waiting on * all of them we remove a configuration switch. */ while (!gpsd_ppsmonitor_stop) { bool ok = false; char *log = NULL; char *log1 = NULL; #if defined(TIOCMIWAIT) #define PPS_LINE_TIOC (TIOCM_CD|TIOCM_CAR|TIOCM_RI|TIOCM_CTS) if (ioctl(session->gpsdata.gps_fd, TIOCMIWAIT, PPS_LINE_TIOC) != 0) { gpsd_report(LOG_ERROR, "PPS ioctl(TIOCMIWAIT) failed: %d %.40s\n" , errno, strerror(errno)); break; } #endif /* TIOCMIWAIT */ /*@-noeffect@*/ #ifdef HAVE_CLOCK_GETTIME /* using clock_gettime() here, that is nSec, * not uSec like gettimeofday */ if ( 0 > clock_gettime(CLOCK_REALTIME, &ts) ) { /* uh, oh, can not get time! */ gpsd_report(LOG_ERROR, "PPS clock_gettime() failed\n"); break; } TSTOTV( &tv, &ts); #else if ( 0 > gettimeofday(&tv, NULL) ) { /* uh, oh, can not get time! */ gpsd_report(LOG_ERROR, "PPS gettimeofday() failed\n"); break; } TVTOTS( &ts, &tv); #endif /*@+noeffect@*/ #if defined(HAVE_SYS_TIMEPPS_H) if ( 0 <= session->kernelpps_handle ) { struct timespec kernelpps_tv; /* on a quad core 2.4GHz Xeon this removes about 20uS of * latency, and about +/-5uS of jitter over the other method */ memset( (void *)&kernelpps_tv, 0, sizeof(kernelpps_tv)); if ( 0 > time_pps_fetch(session->kernelpps_handle, PPS_TSFMT_TSPEC , &pi, &kernelpps_tv)) { gpsd_report(LOG_ERROR, "KPPS kernel PPS failed\n"); } else { // find the last edge if ( pi.assert_timestamp.tv_sec > pi.clear_timestamp.tv_sec ) { kpps_edge = 1; tv_kpps = pi.assert_timestamp; } else if ( pi.assert_timestamp.tv_sec < pi.clear_timestamp.tv_sec ) { kpps_edge = 0; tv_kpps = pi.clear_timestamp; } else if ( pi.assert_timestamp.tv_nsec > pi.clear_timestamp.tv_nsec ) { kpps_edge = 1; tv_kpps = pi.assert_timestamp; } else { kpps_edge = 0; tv_kpps = pi.clear_timestamp; } gpsd_report(LOG_PROG, "KPPS assert %ld.%09ld, sequence: %ld - " "clear %ld.%09ld, sequence: %ld\n", pi.assert_timestamp.tv_sec, pi.assert_timestamp.tv_nsec, pi.assert_sequence, pi.clear_timestamp.tv_sec, pi.clear_timestamp.tv_nsec, pi.clear_sequence); gpsd_report(LOG_PROG, "KPPS data: using %s\n", kpps_edge ? "assert" : "clear"); #define timediff_kpps(x, y) (int)((x.tv_sec-y.tv_sec)*1000000+((x.tv_nsec-y.tv_nsec)/1000)) cycle_kpps = timediff_kpps(tv_kpps, pulse_kpps[kpps_edge]); duration_kpps = timediff_kpps(tv_kpps, pulse_kpps[(int)(kpps_edge == 0)]); if ( 3000000 < duration_kpps ) { // invisible pulse duration_kpps = 0; } #undef timediff_kpps gpsd_report(LOG_INF, "KPPS cycle: %7d, duration: %7d @ %lu.%09lu\n", cycle_kpps, duration_kpps, (unsigned long)tv_kpps.tv_sec, (unsigned long)tv_kpps.tv_nsec); pulse_kpps[kpps_edge] = tv_kpps; ok = true; log = "KPPS"; } } #endif /* HAVE_SYS_TIMEPPS_H */ #if defined(TIOCMIWAIT) ok = false; log = NULL; /*@ +ignoresigns */ if (ioctl(session->gpsdata.gps_fd, TIOCMGET, &state) != 0) { gpsd_report(LOG_ERROR, "PPS ioctl(TIOCMGET) failed\n"); break; } /*@ -ignoresigns */ state = (int)((state & PPS_LINE_TIOC) != 0); /*@ +boolint @*/ #define timediff(x, y) (int)((x.tv_sec-y.tv_sec)*1000000+x.tv_usec-y.tv_usec) cycle = timediff(tv, pulse[state]); duration = timediff(tv, pulse[(int)(state == 0)]); #undef timediff /*@ -boolint @*/ if (state == laststate) { /* some pulses may be so short that state never changes */ if (999000 < cycle && 1001000 > cycle) { duration = 0; unchanged = 0; gpsd_report(LOG_RAW, "PPS pps-detect on %s invisible pulse\n", session->gpsdata.dev.path); } else if (++unchanged == 10) { unchanged = 1; gpsd_report(LOG_WARN, "PPS TIOCMIWAIT returns unchanged state, ppsmonitor sleeps 10\n"); (void)sleep(10); } } else { gpsd_report(LOG_RAW, "PPS pps-detect on %s changed to %d\n", session->gpsdata.dev.path, state); laststate = state; unchanged = 0; } pulse[state] = tv; if (unchanged) { // strange, try again continue; } gpsd_report(LOG_INF, "PPS cycle: %7d, duration: %7d @ %lu.%06lu\n", cycle, duration, (unsigned long)tv.tv_sec, (unsigned long)tv.tv_usec); /* only listen to PPS after 4 consecutive fixes, otherwise time * will be inaccurate. * Not sure yet how to handle uBlox UBX_MODE_TMONLY * Do not use ship_to_ntp here since it is synced to packets * and this thread is asynchonous to packets */ if ( 3 < session->fixcnt ) { /* * The PPS pulse is normally a short pulse with a frequency of * 1 Hz, and the UTC second is defined by the front edge. But we * don't know the polarity of the pulse (different receivers * emit different polarities). The duration variable is used to * determine which way the pulse is going. The code assumes * that the UTC second is changing when the signal has not * been changing for at least 800ms, i.e. it assumes the duty * cycle is at most 20%. * * Some GPSes instead output a square wave that is 0.5 Hz and each * edge denotes the start of a second. * * Some GPSes, like the Globalsat MR-350P, output a 1uS pulse. * The pulse is so short that TIOCMIWAIT sees a state change * but by the time TIOCMGET is called the pulse is gone. * * A few stupid GPSes, like the Furuno GPSClock, output a 1.0 Hz * square wave where the leading edge is the start of a second * * 5Hz GPS (Garmin 18-5Hz) pulses at 5Hz. Set the pulse length to * 40ms which gives a 160ms pulse before going high. * */ log = "Unknown error"; if (199000 > cycle) { // too short to even be a 5Hz pulse log = "Too short for 5Hz\n"; } else if (201000 > cycle) { /* 5Hz cycle */ /* looks like 5hz PPS pulse */ if (100000 > duration) { /* BUG: how does the code know to tell ntpd * which 1/5 of a second to use?? */ ok = true; log = "5Hz PPS pulse\n"; } } else if (999000 > cycle) { log = "Too long for 5Hz, too short for 1Hz\n"; } else if (1001000 > cycle) { /* looks like PPS pulse or square wave */ if (0 == duration) { ok = true; log = "invisible pulse\n"; } else if (499000 > duration) { /* end of the short "half" of the cycle */ /* aka the trailing edge */ log = "1Hz trailing edge\n"; } else if (501000 > duration) { /* looks like 1.0 Hz square wave, ignore trailing edge */ if (state == 1) { ok = true; log = "square\n"; } } else { /* end of the long "half" of the cycle */ /* aka the leading edge */ ok = true; log = "1Hz leading edge\n"; } } else if (1999000 > cycle) { log = "Too long for 1Hz, too short for 2Hz\n"; } else if (2001000 > cycle) { /* looks like 0.5 Hz square wave */ if (999000 > duration) { log = "0.5 Hz square too short duration\n"; } else if (1001000 > duration) { ok = true; log = "0.5 Hz square wave\n"; } else { log = "0.5 Hz square too long duration\n"; } } else { log = "Too long for 0.5Hz\n"; } } else { /* not a good fix, but a test for an otherwise good PPS * would go here */ log = "no fix.\n"; } #endif /* TIOCMIWAIT */ if (ok) { gpsd_report(LOG_RAW, "PPS edge accepted %.100s", log); /* chrony expects tv-sec since Jan 1970 */ /* FIXME!! offset is double of the error from local time */ sample.pulse = 0; sample.leap = session->context->leap_notify; sample.magic = SOCK_MAGIC; #if defined(HAVE_SYS_TIMEPPS_H) if ( 0 <= session->kernelpps_handle) { /* pick the right edge */ if ( kpps_edge ) { ts = pi.assert_timestamp; /* structure copy */ } else { ts = pi.clear_timestamp; /* structure copy */ } TSTOTV( &sample.tv, &ts); } else #endif { sample.tv = tv; /* structure copy */ } /* FIXME!! this is wrong if signal is 5Hz or 10Hz instead of PPS */ /* careful, Unix time to nSec is more precision than a double */ sample.offset = 1 + session->last_fixtime - ts.tv_sec; sample.offset -= ts.tv_nsec / 1e9; /* was: defined(ONCORE_ENABLE) && defined(BINARY_ENABLE) */ #ifdef __UNUSED__ /*@-noeffect@*/ if (session->device_type == &oncore_binary) { int pulse_delay_ns = session->driver.oncore.pps_offset_ns; sample.offset += (double)pulse_delay_ns / 1000000000; ts.tv_nsec -= pulse_delay_ns; TS_NORM( &ts ); } /*@+noeffect@*/ #endif TSTOTV( &tv, &ts ); if (session->ship_to_ntpd) { log1 = "accepted"; if ( 0 <= chronyfd ) { log1 = "accepted chrony sock"; (void)send(chronyfd, &sample, sizeof (sample), 0); } (void)ntpshm_pps(session, &tv); } else { log1 = "skipped ship_to_ntp=0"; } gpsd_report(LOG_RAW, "PPS edge %.20s %lu.%06lu offset %.9f\n", log1, (unsigned long)sample.tv.tv_sec, (unsigned long)sample.tv.tv_usec, sample.offset); if (session->context->pps_hook != NULL) session->context->pps_hook(session, &tv); } else { gpsd_report(LOG_RAW, "PPS edge rejected %.100s", log); } } #if defined(HAVE_SYS_TIMEPPS_H) if (session->kernelpps_handle > 0) { gpsd_report(LOG_PROG, "PPS descriptor cleaned up\n"); time_pps_destroy(session->kernelpps_handle); } #endif if (chronyfd != -1) (void)close(chronyfd); gpsd_report(LOG_PROG, "PPS gpsd_ppsmonitor exited.\n"); return NULL; }