/* 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); }
int ntpshm_put(struct gps_device_t *session, volatile struct shmTime *shmseg, struct timedelta_t *td) /* put a received fix time into shared memory for NTP */ { char real_str[TIMESPEC_LEN]; char clock_str[TIMESPEC_LEN]; /* Any NMEA will be about -1 or -2. Garmin GPS-18/USB is around -6 or -7. */ int precision = -20; /* default precision, 1 micro sec */ if (shmseg == NULL) { gpsd_log(&session->context->errout, LOG_RAW, "NTP:PPS: missing shm\n"); return 0; } #ifdef PPS_ENABLE if (shmseg == session->shm_pps) { /* precision is a floor so do not make it tight */ if ( source_usb == session->sourcetype ) { /* if PPS over USB, then precision = -20, 1 micro sec */ precision = -20; } else { /* likely PPS over serial, precision = -30, 1 nano sec */ precision = -30; } } #endif /* PPS_ENABLE */ ntp_write(shmseg, td, precision, session->context->leap_notify); timespec_str( &td->real, real_str, sizeof(real_str) ); timespec_str( &td->clock, clock_str, sizeof(clock_str) ); gpsd_log(&session->context->errout, LOG_PROG, "NTP: ntpshm_put(%s,%s) %s @ %s\n", session->gpsdata.dev.path, precision, real_str, clock_str); return 1; }
/* wait for, and get, last two edges using RFC2783 * return -1 for error * 0 for OK * 1 no edge found, continue * * on a quad core 2.4GHz Xeon using KPPS timestamp instead of plain * PPS timestamp removes about 20uS of latency, and about +/-5uS * of jitter */ static int get_edge_rfc2783(struct inner_context_t *inner_context, struct timespec *prev_clock_ts, int *prev_edge, struct timespec *clock_ts, int *edge, volatile struct timedelta_t *last_fixtime) { pps_info_t pi; char ts_str1[TIMESPEC_LEN], ts_str2[TIMESPEC_LEN]; struct timespec kernelpps_tv; volatile struct pps_thread_t *thread_context = inner_context->pps_thread; if ( inner_context->pps_canwait ) { /* * RFC2783 specifies that a NULL timeval means to wait, if * PPS_CANWAIT is available. * * since we pps_canwait, we skipped the TIOMCIWAIT * * 3 second time out, some GPS output 0.5Hz and some RFC2783 * can only trigger on one edge * a better and more complex solution would be to wait * for 1/20 second and suffer the cycles */ kernelpps_tv.tv_sec = 3; kernelpps_tv.tv_nsec = 0; } else { /* * We use of a non-NULL zero timespec here, * which means to return immediately with -1 (section * 3.4.3). This is because we know we just got a pulse because * TIOCMIWAIT just woke up. * The timestamp has already been captured in the kernel, and we * are merely fetching it here. */ memset( (void *)&kernelpps_tv, 0, sizeof(kernelpps_tv)); } memset( (void *)&pi, 0, sizeof(pi)); /* paranoia */ if ( 0 > time_pps_fetch(inner_context->kernelpps_handle, PPS_TSFMT_TSPEC , &pi, &kernelpps_tv)) { char errbuf[BUFSIZ] = "unknown error"; (void)strerror_r(errno, errbuf, sizeof(errbuf)); thread_context->log_hook(thread_context, THREAD_ERROR, "KPPS:%s kernel PPS failed %s\n", thread_context->devicename, errbuf); if ( ETIMEDOUT == errno || EINTR == errno ) { /* just a timeout */ return 1; } return 0; } if ( inner_context->pps_canwait ) { /* get_edge_tiocmiwait() got this if !pps_canwait */ /* quick, grab a copy of last fixtime before it changes */ thread_lock(thread_context); *last_fixtime = thread_context->fix_in; thread_unlock(thread_context); } // find the last edge if ( pi.assert_timestamp.tv_sec > pi.clear_timestamp.tv_sec ) { /* assert 1 sec or more after than clear */ *edge = 1; } else if ( pi.assert_timestamp.tv_sec < pi.clear_timestamp.tv_sec ) { /* assert 1 sec or more before than clear */ *edge = 0; } else if ( pi.assert_timestamp.tv_nsec > pi.clear_timestamp.tv_nsec ) { /* assert less than 1 sec after clear */ *edge = 1; } else { /* assert less than 1 sec before clear */ *edge = 0; } if ( 1 == *edge ) { /* assert after clear */ *prev_edge = 0; if ( 0 == pi.clear_timestamp.tv_sec ) { /* brain damaged pps-gpio sometimes never fills in clear * so make it look like an invisible pulse * if clear is the leading edge, then we are off by the * pulse width */ *prev_clock_ts = pi.assert_timestamp; } else { *prev_clock_ts = pi.clear_timestamp; } *clock_ts = pi.assert_timestamp; } else { /* assert before clear */ *prev_edge = 1; *prev_clock_ts = pi.assert_timestamp; *clock_ts = pi.clear_timestamp; } /* * pps_seq_t is uint32_t on NetBSD, so cast to * unsigned long as a wider-or-equal type to * accomodate Linux's type. */ timespec_str( &pi.assert_timestamp, ts_str1, sizeof(ts_str1) ); timespec_str( &pi.clear_timestamp, ts_str2, sizeof(ts_str2) ); thread_context->log_hook(thread_context, THREAD_PROG, "KPPS:%s assert %s, sequence: %lu, " "clear %s, sequence: %lu - using: %.10s\n", thread_context->devicename, ts_str1, (unsigned long) pi.assert_sequence, ts_str2, (unsigned long) pi.clear_sequence, *edge ? "assert" : "clear"); return 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; }
/* wait for, and get, an edge using TIOCMIWAIT * return -1 for error * 0 for OK */ static int get_edge_tiocmiwait( volatile struct pps_thread_t *thread_context, struct timespec *clock_ts, int *state, volatile struct timedelta_t *last_fixtime) { char ts_str[TIMESPEC_LEN]; /* we are lucky to have TIOCMIWAIT, so wait for next edge */ #define PPS_LINE_TIOC (TIOCM_CD|TIOCM_RI|TIOCM_CTS|TIOCM_DSR) /* * DB9 DB25 Name Full name * --- ---- ---- -------------------- * 3 2 TXD --> Transmit Data * 2 3 RXD <-- Receive Data * 7 4 RTS --> Request To Send * 8 5 CTS <-- Clear To Send * 6 6 DSR <-- Data Set Ready * 4 20 DTR --> Data Terminal Ready * 1 8 DCD <-- Data Carrier Detect * 9 22 RI <-- Ring Indicator * 5 7 GND Signal ground * * Wait for status change on any handshake line. Just one edge, * we do not want to be spinning waiting for the trailing edge of * a pulse. 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. * * Note that it only makes sense to wait on handshake lines * activated from the receive side (DCE->DTE) here; in this * context "DCE" is the GPS. {CD,RI,CTS,DSR} is the * entire set of these. * */ if (ioctl(thread_context->devicefd, TIOCMIWAIT, PPS_LINE_TIOC) != 0) { char errbuf[BUFSIZ] = "unknown error"; (void)strerror_r(errno, errbuf, sizeof(errbuf)); thread_context->log_hook(thread_context, THREAD_WARN, "TPPS:%s ioctl(TIOCMIWAIT) failed: %d %.40s\n", thread_context->devicename, errno, errbuf); return -1;; } /* * Start of time critical section * Only error reporting, not success reporting in critical section */ /* duplicate copy in get_edge_rfc2783 */ /* quick, grab a copy of last_fixtime before it changes */ thread_lock(thread_context); *last_fixtime = thread_context->fix_in; thread_unlock(thread_context); /* end duplicate copy in get_edge_rfc2783 */ /* get the time after we just woke up */ if ( 0 > clock_gettime(CLOCK_REALTIME, clock_ts) ) { /* uh, oh, can not get time! */ char errbuf[BUFSIZ] = "unknown error"; (void)strerror_r(errno, errbuf, sizeof(errbuf)); thread_context->log_hook(thread_context, THREAD_ERROR, "TPPS:%s clock_gettime() failed: %.100s\n", thread_context->devicename, errbuf); return -1;; } /* got the edge, got the time just after the edge, now quickly * get the edge state */ if (ioctl(thread_context->devicefd, (unsigned long)TIOCMGET, state) != 0) { char errbuf[BUFSIZ] = "unknown error"; (void)strerror_r(errno, errbuf, sizeof(errbuf)); thread_context->log_hook(thread_context, THREAD_ERROR, "TPPS:%s ioctl(TIOCMGET) failed: %.100s\n", thread_context->devicename, errbuf); return -1; } /* end of time critical section */ /* mask for monitored lines */ *state &= PPS_LINE_TIOC; timespec_str( clock_ts, ts_str, sizeof(ts_str) ); thread_context->log_hook(thread_context, THREAD_PROG, "TPPS:%s ioctl(TIOCMIWAIT) succeeded, time:%s, state: %d\n", thread_context->devicename, ts_str, *state); return 0; }