/* 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); }
/* gpsd_ppsmonitor() * * the core loop of the PPS thread. * All else is initialization, cleanup or subroutine */ static void *gpsd_ppsmonitor(void *arg) { char ts_str1[TIMESPEC_LEN], ts_str2[TIMESPEC_LEN]; struct inner_context_t inner_context = *((struct inner_context_t *)arg); volatile struct pps_thread_t *thread_context = inner_context.pps_thread; /* the GPS time and system clock timme, to the nSec, * when the last fix received * using a double would cause loss of precision */ volatile struct timedelta_t last_fixtime = {{0, 0}, {0, 0}}; struct timespec clock_ts = {0, 0}; time_t last_second_used = 0; long cycle = 0, duration = 0; /* state is the last state of the tty control signals */ int state = 0; /* count of how many cycles unchanged data */ int unchanged = 0; /* state_last is previous state */ int state_last = 0; /* edge, used as index into pulse to find previous edges */ int edge = 0; /* 0 = clear edge, 1 = assert edge */ #if defined(TIOCMIWAIT) int edge_tio = 0; long cycle_tio = 0; long duration_tio = 0; int state_tio = 0; int state_last_tio = 0; struct timespec clock_ts_tio = {0, 0}; /* pulse stores the time of the last two edges */ struct timespec pulse_tio[2] = { {0, 0}, {0, 0} }; #endif /* TIOCMIWAIT */ #if defined(HAVE_SYS_TIMEPPS_H) long cycle_kpps = 0, duration_kpps = 0; /* kpps_pulse stores the time of the last two edges */ struct timespec pulse_kpps[2] = { {0, 0}, {0, 0} }; #endif /* defined(HAVE_SYS_TIMEPPS_H) */ bool not_a_tty = false; /* before the loop, figure out how we can detect edges: * TIOMCIWAIT, which is linux specifix * RFC2783, a.k.a kernel PPS (KPPS) * or if KPPS is deficient a combination of the two */ if ( isatty(thread_context->devicefd) == 0 ) { thread_context->log_hook(thread_context, THREAD_INF, "KPPS:%s gps_fd:%d not a tty\n", thread_context->devicename, thread_context->devicefd); /* why do we care the device is a tty? so as not to ioctl(TIO..) * /dev/pps0 is not a tty and we need to use it */ not_a_tty = true; } /* if no TIOCMIWAIT, we hope to have PPS_CANWAIT */ if ( not_a_tty && !inner_context.pps_canwait ) { /* for now, no way to wait for an edge, in the future maybe figure out * a sleep */ } /* * this is the main loop, exit and never any further PPS processing. * * Four stages to the loop, * an unwanted condition at any point and the loop restarts * an error condition and we exit for all time. * * Stage One: wait for the next edge. * If we have KPPS * If we have PPS_CANWAIT * use KPPS and PPS_CANWAIT - this is the most accurate * else * use KPPS and TIOMCIWAIT together - this is pretty accurate * else If we have TIOMCIWAIT * use TIOMCIWAIT - this is the least accurate * else * give up * * Success is we have a good edge, otherwise loop some more * * On a successul stage one, we know this about the exact moment * of current pulse: * GPS (real) time * system (clock) time * edge type: Assert (rising) or Clear (falling) * * From the above 3 items, we can compute: * cycle length - elapsed time from the previous edge of the same type * pulse length (duration) - elapsed time from the previous edge * (the previous edge would be the opposite type) * * Stage Two: Categorize the current edge * Decide if we have 0.5Hz, 1Hz, 5 Hz cycle time * knowing cycle time determine if we have the leading or trailing edge * restart the loop if the edge looks dodgy * * Stage Three: Calculate * Calculate the offset (difference) between the system time * and the GPS time at the pulse moment * restart the loop if the offset looks dodgy * * Stage Four: Tell ntpd, chronyd, or gpsmon what we learned * a few more sanity checks * call the report hook with our PPS report */ while (thread_context->report_hook != NULL) { bool ok = false; char *log = NULL; char *edge_str = ""; if (++unchanged == 10) { /* last ten edges no good, stop spinning, just wait 10 seconds */ unchanged = 0; thread_context->log_hook(thread_context, THREAD_WARN, "PPS:%s unchanged state, ppsmonitor sleeps 10\n", thread_context->devicename); (void)sleep(10); } /* Stage One; wait for the next edge */ #if defined(TIOCMIWAIT) if ( !not_a_tty && !inner_context.pps_canwait ) { int ret; /* we are a tty, so can TIOCMIWAIT */ /* we have no PPS_CANWAIT, so must TIOCMIWAIT */ ret = get_edge_tiocmiwait( thread_context, &clock_ts_tio, &state_tio, &last_fixtime ); if ( 0 != ret ) { thread_context->log_hook(thread_context, THREAD_PROG, "PPS:%s die: TIOCMIWAIT Error\n", thread_context->devicename); break; } edge_tio = (state_tio > state_last_tio) ? 1 : 0; /* three things now known about the current edge: * clock_ts - time of the edge * state - the serial line input states * edge - rising edge (1), falling edge (0) or invisble edge (0) */ /* calculate cycle and duration from previous edges */ cycle_tio = timespec_diff_ns(clock_ts_tio, pulse_tio[edge_tio]); cycle_tio /= 1000; /* nsec to usec */ duration_tio = timespec_diff_ns(clock_ts_tio, pulse_tio[edge_tio ? 0 : 1])/1000; /* save this edge so we know next cycle time */ pulse_tio[edge_tio] = clock_ts_tio; /* use this data */ ok = true; clock_ts = clock_ts_tio; state = edge_tio; edge = edge_tio; edge_str = edge ? "Assert" : "Clear"; cycle = cycle_tio; duration = duration_tio; timespec_str( &clock_ts, ts_str1, sizeof(ts_str1) ); thread_context->log_hook(thread_context, THREAD_PROG, "TPPS:%s %.10s cycle: %d, duration: %d @ %s\n", thread_context->devicename, edge_str, cycle, duration, ts_str1); } #endif /* TIOCMIWAIT */ /* ok and log used by KPPS and TIOCMIWAIT */ log = NULL; #if defined(HAVE_SYS_TIMEPPS_H) if ( 0 <= inner_context.kernelpps_handle ) { int ret; int edge_kpps = 0; /* 0 = clear edge, 1 = assert edge */ /* time of the last edge */ struct timespec clock_ts_kpps = {0, 0}; /* time of the edge before the last edge */ struct timespec prev_clock_ts = {0, 0}; /* direction of next to last edge 1 = assert, 0 = clear */ int prev_edge = 0; /* get last and previsou edges, in order * optionally wait for goood data */ ret = get_edge_rfc2783(&inner_context, &prev_clock_ts, &prev_edge, &clock_ts_kpps, &edge_kpps, &last_fixtime); if ( -1 == ret ) { /* error, so break */ thread_context->log_hook(thread_context, THREAD_ERROR, "PPS:%s die: RFC2783 Error\n", thread_context->devicename); break; } if ( 1 == ret ) { /* no edge found, so continue */ /* maybe use TIOCMIWAIT edge instead?? */ continue; } /* for now, as we have been doing all of gpsd 3.x, just *use the last edge, not the previous edge */ /* compute time from previous saved similar edge */ cycle_kpps = timespec_diff_ns(clock_ts_kpps, pulse_kpps[edge_kpps]); cycle_kpps /= 1000; /* compute time from previous saved dis-similar edge */ duration_kpps = timespec_diff_ns(clock_ts_kpps, prev_clock_ts)/1000; /* save for later */ pulse_kpps[edge_kpps] = clock_ts_kpps; pulse_kpps[edge_kpps ? 0 : 1] = prev_clock_ts; /* sanity checks are later */ /* use this data */ state = edge_kpps; edge = edge_kpps; edge_str = edge ? "Assert" : "Clear"; clock_ts = clock_ts_kpps; cycle = cycle_kpps; duration = duration_kpps; timespec_str( &clock_ts_kpps, ts_str1, sizeof(ts_str1) ); thread_context->log_hook(thread_context, THREAD_PROG, "KPPS:%s %.10s cycle: %7d, duration: %7d @ %s\n", thread_context->devicename, edge_str, cycle_kpps, duration_kpps, ts_str1); } #endif /* defined(HAVE_SYS_TIMEPPS_H) */ if ( not_a_tty && !inner_context.pps_canwait ) { /* uh, oh, no TIOMCIWAIT, nor RFC2783, die */ thread_context->log_hook(thread_context, THREAD_WARN, "PPS:%s die: no TIOMCIWAIT, nor RFC2783 CANWAIT\n", thread_context->devicename); break; } /* * End of Stge One * we now know this about the exact moment of current pulse: * GPS (real) time * system (clock) time * edge type: Assert (rising) or Clear (falling) * * we have computed: * cycle length * pulse length (duration) */ /* * Stage Two: Categorize the current edge * Decide if we have 0.5Hz, 1Hz, 5 Hz cycle time * determine if we have the leading or trailing edge */ /* FIXME! this block duplicates a lot of the next block * of cycle detetion code */ if (state != state_last) { thread_context->log_hook(thread_context, THREAD_RAW, "PPS:%s %.10s pps-detect changed to %d\n", thread_context->devicename, edge_str, state); unchanged = 0; } else if ( (180000 < cycle && 220000 > cycle) /* 5Hz */ || (900000 < cycle && 1100000 > cycle) /* 1Hz */ || (1800000 < cycle && 2200000 > cycle) ) { /* 2Hz */ /* some pulses may be so short that state never changes * and some RFC2783 only can detect one edge */ duration = 0; unchanged = 0; thread_context->log_hook(thread_context, THREAD_RAW, "PPS:%s %.10s pps-detect invisible pulse\n", thread_context->devicename, edge_str); } /* else, unchannged state, and weird cycle time */ state_last = state; timespec_str( &clock_ts, ts_str1, sizeof(ts_str1) ); thread_context->log_hook(thread_context, THREAD_PROG, "PPS:%s %.10s cycle: %7d, duration: %7d @ %s\n", thread_context->devicename, edge_str, cycle, duration, ts_str1); if (unchanged) { // strange, try again continue; } /* * 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. When the duration * is less than 1/2 the cycle we are on the trailing edge. * * 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. gpsd * calls that an invisible pulse. * * 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 * gpsd can only guess the correct edge. * * 5Hz GPS (Garmin 18-5Hz) pulses at 5Hz. Set the pulse length to * 40ms which gives a 160ms pulse before going high. * * You may think that PPS is very accurate, so the cycle time * valid window should be very small. This is not the case, * The Rasberry Pi clock is very coarse when it starts and chronyd * may be doing a fast slew. chronyd by default will slew up * to 8.334%! So the cycle time as measured by the system clock * may be almost +/- 9%. Therefore, gpsd uses a 10% window. * Don't worry, ntpd and chronyd will do further validation. */ log = "Unknown error"; if ( 0 > cycle ) { log = "Rejecting negative cycle\n"; } else 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 (900000 > cycle) { /* Yes, 10% window. The Rasberry Pi clock is very coarse * when it starts and chronyd may be doing a fast slew. * chronyd by default will slew up to 8.334% ! * Don't worry, ntpd and chronyd will do further sanitizing.*/ log = "Too long for 5Hz, too short for 1Hz\n"; } else if (1100000 > cycle) { /* Yes, 10% window. */ /* 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 (edge == 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"; } /* end of Stage two * we now know what type of PPS pulse, and if we have the * leading edge */ /* Stage Three: Calculate * Calculate the offset (difference) between the system time * and the GPS time at the pulse moment */ /* * If there has not yet been any valid in-band time stashed * from the GPS when the PPS event was asserted, we can do * nothing further. gpsd can not tell what second this pulse is * in reference to. * * Some GPSes like Garmin always send a PPS, valid or not. * Other GPSes like some uBlox may only send PPS when time is valid. * It is common to get PPS, and no fixtime, while autobauding. */ if (last_fixtime.real.tv_sec == 0) { /* probably should log computed offset just for grins here */ ok = false; log = "missing last_fixtime\n"; } else if ( ok && last_second_used >= last_fixtime.real.tv_sec ) { /* uh, oh, this second already handled */ ok = false; log = "this second already handled\n"; } if ( !ok ) { /* can not use this pulse, reject and retry */ thread_context->log_hook(thread_context, THREAD_PROG, "PPS:%s %.10s rejected %.100s", thread_context->devicename, edge_str, log); continue; } /* we have validated a goood cycle, mark it */ unchanged = 0; /* offset is the skew from expected to observed pulse time */ struct timespec offset; /* offset as a printable string */ char offset_str[TIMESPEC_LEN]; /* delay after last fix */ struct timespec delay; /* delay as a printable string */ char delay_str[TIMESPEC_LEN]; char *log1 = ""; /* ppstimes.real is the time we think the pulse represents */ struct timedelta_t ppstimes; thread_context->log_hook(thread_context, THREAD_RAW, "PPS:%s %.10s categorized %.100s", thread_context->devicename, edge_str, log); /* FIXME! The GR-601W at 38,400 or faster can send the * serial fix before the interrupt event carrying the PPS * line assertion by about 10 mSec! */ /* * We get the time of the last fix recorded before the PPS came in, * which is for the previous cycle. Only works for integral cycle * times, but more than 1Hz is pointless. */ ppstimes.real.tv_sec = (time_t)last_fixtime.real.tv_sec + 1; ppstimes.real.tv_nsec = 0; /* need to be fixed for 5Hz */ ppstimes.clock = clock_ts; TS_SUB( &offset, &ppstimes.real, &ppstimes.clock); TS_SUB( &delay, &ppstimes.clock, &last_fixtime.clock); timespec_str( &delay, delay_str, sizeof(delay_str) ); /* end Stage Three: now known about the exact edge moment: * UTC time of PPS edge * offset of system time to PS time */ /* Stage Four: Tell ntpd, chronyd, or gpsmon what we learned * a few more sanity checks * call the report hook with our PPS report */ if ( 0> delay.tv_sec || 0 > delay.tv_nsec ) { thread_context->log_hook(thread_context, THREAD_RAW, "PPS:%s %.10s system clock went backwards: %.20s\n", thread_context->devicename, edge_str, delay_str); log1 = "system clock went backwards"; } else if ( ( 2 < delay.tv_sec) || ( 1 == delay.tv_sec && 100000000 > delay.tv_nsec ) ) { /* system clock could be slewing so allow 1.1 sec delay */ thread_context->log_hook(thread_context, THREAD_RAW, "PPS:%s %.10s no current GPS seconds: %.20s\n", thread_context->devicename, edge_str, delay_str); log1 = "timestamp out of range"; } else { last_second_used = last_fixtime.real.tv_sec; if (thread_context->report_hook != NULL) log1 = thread_context->report_hook(thread_context, &ppstimes); else log1 = "no report hook"; thread_lock(thread_context); thread_context->pps_out = ppstimes; thread_context->ppsout_count++; thread_unlock(thread_context); timespec_str( &ppstimes.clock, ts_str1, sizeof(ts_str1) ); timespec_str( &ppstimes.real, ts_str2, sizeof(ts_str2) ); thread_context->log_hook(thread_context, THREAD_INF, "PPS:%s %.10s hooks called clock: %s real: %s: %.20s\n", thread_context->devicename, edge_str, ts_str1, ts_str2, log1); } timespec_str( &clock_ts, ts_str1, sizeof(ts_str1) ); timespec_str( &offset, offset_str, sizeof(offset_str) ); thread_context->log_hook(thread_context, THREAD_PROG, "PPS:%s %.10s %.30s @ %s offset %.20s\n", thread_context->devicename, edge_str, log1, ts_str1, offset_str); /* end Stage four, end of the loop, do it again */ } #if defined(HAVE_SYS_TIMEPPS_H) if (inner_context.kernelpps_handle > 0) { thread_context->log_hook(thread_context, THREAD_PROG, "PPS:%s descriptor cleaned up\n", thread_context->devicename); (void)time_pps_destroy(inner_context.kernelpps_handle); } #endif thread_context->log_hook(thread_context, THREAD_PROG, "PPS:%s gpsd_ppsmonitor exited.\n", thread_context->devicename); return NULL; }