int main(int argc, char **argv) { int fd; pps_info_t pi; pps_params_t pp; pps_handle_t ph; int i, mode; u_int olda, oldc; double d = 0; struct timespec to; if (argc < 2) argv[1] = "/dev/cuaa1"; setbuf(stdout, 0); fd = open(argv[1], O_RDONLY); if (fd < 0) err(1, argv[1]); i = time_pps_create(fd, &ph); if (i < 0) err(1, "time_pps_create"); i = time_pps_getcap(ph, &mode); if (i < 0) err(1, "time_pps_getcap"); pp.mode = PPS_CAPTUREASSERT | PPS_ECHOASSERT; pp.mode = PPS_CAPTUREBOTH; /* pp.mode = PPS_CAPTUREASSERT; */ i = time_pps_setparams(ph, &pp); if (i < 0) err(1, "time_pps_setparams"); while (1) { to.tv_nsec = 0; to.tv_sec = 0; i = time_pps_fetch(ph, PPS_TSFMT_TSPEC, &pi, &to); if (i < 0) err(1, "time_pps_fetch"); if (olda == pi.assert_sequence && oldc == pi.clear_sequence) { usleep(10000); continue; } Chew(&pi.assert_timestamp, &pi.clear_timestamp, pi.assert_sequence, pi.clear_sequence); olda = pi.assert_sequence; oldc = pi.clear_sequence; } return(0); }
int64_t PPS::fetch(double timeout) { // Linux v2.6 (LinuxPPS) implementation. #if defined(DUNE_SYS_HAS_TIMEPPS_H) timespec tstout = DUNE_TIMESPEC_INIT_SEC_FP(timeout); pps_info_t bfr = {0}; int rv = time_pps_fetch(m_handle, PPS_TSFMT_TSPEC, &bfr, &tstout); if (rv < 0) return -1; return (int64_t)bfr.assert_timestamp.tv_sec * Time::c_nsec_per_sec + (int64_t)bfr.assert_timestamp.tv_nsec; // Not yet implemented. #else (void)timeout; return -1; #endif }
/* * refclock_pps - called once per second * * This routine is called once per second. It snatches the PPS * timestamp from the kernel and saves the sign-extended fraction in * a circular buffer for processing at the next poll event. */ int refclock_pps( struct peer *peer, /* peer structure pointer */ struct refclock_atom *ap, /* atom structure pointer */ int mode /* mode bits */ ) { struct refclockproc *pp; pps_info_t pps_info; struct timespec timeout; double dtemp; /* * We require the clock to be synchronized before setting the * parameters. When the parameters have been set, fetch the * most recent PPS timestamp. */ pp = peer->procptr; if (ap->handle == 0) return (0); if (ap->pps_params.mode == 0 && sys_leap != LEAP_NOTINSYNC) { if (refclock_params(pp->sloppyclockflag, ap) < 1) return (0); } timeout.tv_sec = 0; timeout.tv_nsec = 0; memset(&pps_info, 0, sizeof(pps_info_t)); if (time_pps_fetch(ap->handle, PPS_TSFMT_TSPEC, &pps_info, &timeout) < 0) { refclock_report(peer, CEVNT_FAULT); return (0); } timeout = ap->ts; if (ap->pps_params.mode & PPS_CAPTUREASSERT) ap->ts = pps_info.assert_timestamp; else if (ap->pps_params.mode & PPS_CAPTURECLEAR) ap->ts = pps_info.clear_timestamp; else return (0); /* * There can be zero, one or two PPS pulses between polls, * depending on the poll interval relative to the PPS interval. * The pulse must be newer and within the range gate relative * to the last pulse. */ if (ap->ts.tv_sec <= timeout.tv_sec || abs(ap->ts.tv_nsec - timeout.tv_nsec) > RANGEGATE) return (0); /* * Convert to signed fraction offset and stuff in median filter. */ pp->lastrec.l_ui = (u_int32)ap->ts.tv_sec + JAN_1970; dtemp = ap->ts.tv_nsec / 1e9; pp->lastrec.l_uf = (u_int32)(dtemp * FRAC); if (dtemp > .5) dtemp -= 1.; SAMPLE(-dtemp + pp->fudgetime1); #ifdef DEBUG if (debug > 1) printf("refclock_pps: %lu %f %f\n", current_time, dtemp, pp->fudgetime1); #endif return (1); }
/*@-mustfreefresh -type -unrecog -branchstate@*/ static /*@null@*/ void *gpsd_ppsmonitor(void *arg) { struct gps_device_t *session = (struct gps_device_t *)arg; double last_fixtime_real = 0, last_fixtime_clock = 0; #ifndef HAVE_CLOCK_GETTIME struct timeval clock_tv = {0, 0}; #endif /* HAVE_CLOCK_GETTIME */ struct timespec clock_ts = {0, 0}; time_t last_second_used = 0; #if defined(TIOCMIWAIT) int cycle, duration; /* state is the last state of the tty control ssignals */ int state = 0, unchanged = 0; /* state_last is previous state */ int state_last = 0; /* pulse stores the time of the last two edges */ struct timespec pulse[2] = { {0, 0}, {0, 0} }; /* edge, used as index into pulse to find previous edges */ int edge = 0; /* 0 = clear edge, 1 = assert edge */ #endif /* TIOCMIWAIT */ #if defined(HAVE_SYS_TIMEPPS_H) int edge_kpps = 0; /* 0 = clear edge, 1 = assert edge */ #ifndef S_SPLINT_S int cycle_kpps, duration_kpps; /* kpps_pulse stores the time of the last two edges */ struct timespec pulse_kpps[2] = { {0, 0}, {0, 0} }; struct timespec ts_kpps; pps_info_t pi; memset( (void *)&pi, 0, sizeof(pps_info_t)); #endif /* S_SPLINT_S */ #endif /* defined(HAVE_SYS_TIMEPPS_H) */ /* * 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. * * Once we have the latest edge we compare it to the last edge which we * stored. If the edge passes sanity checks we use it to send to * ntpshm and chrony_send */ while (session->thread_report_hook != NULL || session->context->pps_hook != NULL) { bool ok = false; #if defined(HAVE_SYS_TIMEPPS_H) // cppcheck-suppress variableScope bool ok_kpps = false; #endif /* HAVE_SYS_TIMEPPS_H */ char *log = NULL; #if defined(TIOCMIWAIT) /* we are lucky to have TIOCMIWAIT, so wait for next edge */ #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(session->context->debug, LOG_ERROR, "PPS ioctl(TIOCMIWAIT) failed: %d %.40s\n", errno, strerror(errno)); break; } /* quick, grab a copy of last_fixtime before it changes */ last_fixtime_real = session->last_fixtime.real; last_fixtime_clock = session->last_fixtime.clock; /*@-noeffect@*/ /* get the time after we just woke up */ #ifdef HAVE_CLOCK_GETTIME /* using clock_gettime() here, that is nSec, * not uSec like gettimeofday */ if ( 0 > clock_gettime(CLOCK_REALTIME, &clock_ts) ) { /* uh, oh, can not get time! */ gpsd_report(session->context->debug, LOG_ERROR, "PPS clock_gettime() failed\n"); break; } #else if ( 0 > gettimeofday(&clock_tv, NULL) ) { /* uh, oh, can not get time! */ gpsd_report(session->context->debug, LOG_ERROR, "PPS gettimeofday() failed\n"); break; } TVTOTS( &clock_ts, &clock_tv); #endif /* HAVE_CLOCK_GETTIME */ /*@+noeffect@*/ /* got the edge, got the time just after the edge, now quickly * get the edge state */ /*@ +ignoresigns */ if (ioctl(session->gpsdata.gps_fd, TIOCMGET, &state) != 0) { gpsd_report(session->context->debug, LOG_ERROR, "PPS ioctl(TIOCMGET) failed\n"); break; } /*@ -ignoresigns */ /* mask for monitored lines */ state &= PPS_LINE_TIOC; edge = (state > state_last) ? 1 : 0; #endif /* TIOCMIWAIT */ /* ok and log used by KPPS and TIOCMIWAIT */ // cppcheck-suppress redundantAssignment ok = false; log = NULL; #if defined(HAVE_SYS_TIMEPPS_H) && !defined(S_SPLINT_S) if ( 0 <= session->kernelpps_handle ) { struct timespec kernelpps_tv; /* 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 */ #ifdef TIOCMIWAIT /* * 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)); #else /* not TIOCMIWAIT */ /* * RFC2783 specifies that a NULL timeval means to wait. * * FIXME, this will fail on 2Hz 'PPS', maybe should wait 3 Sec. */ kernelpps_tv.tv_sec = 1; kernelpps_tv.tv_nsec = 0; #endif if ( 0 > time_pps_fetch(session->kernelpps_handle, PPS_TSFMT_TSPEC , &pi, &kernelpps_tv)) { gpsd_report(session->context->debug, LOG_ERROR, "KPPS kernel PPS failed\n"); } else { // find the last edge // FIXME a bit simplistic, should hook into the // cycle/duration check below. if ( pi.assert_timestamp.tv_sec > pi.clear_timestamp.tv_sec ) { edge_kpps = 1; ts_kpps = pi.assert_timestamp; } else if ( pi.assert_timestamp.tv_sec < pi.clear_timestamp.tv_sec ) { edge_kpps = 0; ts_kpps = pi.clear_timestamp; } else if ( pi.assert_timestamp.tv_nsec > pi.clear_timestamp.tv_nsec ) { edge_kpps = 1; ts_kpps = pi.assert_timestamp; } else { edge_kpps = 0; ts_kpps = 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. */ gpsd_report(session->context->debug, LOG_PROG, "KPPS assert %ld.%09ld, sequence: %ld - " "clear %ld.%09ld, sequence: %ld\n", pi.assert_timestamp.tv_sec, pi.assert_timestamp.tv_nsec, (unsigned long) pi.assert_sequence, pi.clear_timestamp.tv_sec, pi.clear_timestamp.tv_nsec, (unsigned long) pi.clear_sequence); gpsd_report(session->context->debug, LOG_PROG, "KPPS data: using %s\n", edge_kpps ? "assert" : "clear"); cycle_kpps = timespec_diff_ns(ts_kpps, pulse_kpps[edge_kpps])/1000; duration_kpps = timespec_diff_ns(ts_kpps, pulse_kpps[(int)(edge_kpps == 0)])/1000; gpsd_report(session->context->debug, LOG_PROG, "KPPS cycle: %7d uSec, duration: %7d uSec @ %lu.%09lu\n", cycle_kpps, duration_kpps, (unsigned long)ts_kpps.tv_sec, (unsigned long)ts_kpps.tv_nsec); pulse_kpps[edge_kpps] = ts_kpps; if (990000 < cycle_kpps && 1010000 > cycle_kpps) { /* KPPS passes a basic sanity check */ ok_kpps = true; log = "KPPS"; } } } #endif /* defined(HAVE_SYS_TIMEPPS_H) && !defined(S_SPLINT_S) */ #if defined(TIOCMIWAIT) /*@ +boolint @*/ cycle = timespec_diff_ns(clock_ts, pulse[edge]) / 1000; duration = timespec_diff_ns(clock_ts, pulse[(int)(edge == 0)])/1000; /*@ -boolint @*/ if (state == state_last) { /* some pulses may be so short that state never changes */ if (999000 < cycle && 1001000 > cycle) { duration = 0; unchanged = 0; gpsd_report(session->context->debug, LOG_RAW, "PPS pps-detect on %s invisible pulse\n", session->gpsdata.dev.path); } else if (++unchanged == 10) { /* not really unchanged, just out of bounds */ unchanged = 1; gpsd_report(session->context->debug, LOG_WARN, "PPS TIOCMIWAIT returns unchanged state, ppsmonitor sleeps 10\n"); (void)sleep(10); } } else { gpsd_report(session->context->debug, LOG_RAW, "PPS pps-detect on %s changed to %d\n", session->gpsdata.dev.path, state); unchanged = 0; } state_last = state; /* save this edge so we know next cycle time */ pulse[edge] = clock_ts; gpsd_report(session->context->debug, LOG_PROG, "PPS edge: %d, cycle: %7d uSec, duration: %7d uSec @ %lu.%09lu\n", edge, cycle, duration, (unsigned long)clock_ts.tv_sec, (unsigned long)clock_ts.tv_nsec); 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. 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 ( 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 (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 (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"; } #endif /* TIOCMIWAIT */ if ( ok && last_second_used >= last_fixtime_real ) { /* uh, oh, this second already handled */ ok = 0; log = "this second already handled\n"; } if (ok) { /* offset is the skew from expected to observed pulse time */ double offset; /* delay after last fix */ double delay; char *log1 = NULL; /* drift.real is the time we think the pulse represents */ struct timedrift_t drift; gpsd_report(session->context->debug, LOG_RAW, "PPS edge accepted %.100s", log); #if defined(HAVE_SYS_TIMEPPS_H) if ( 0 <= session->kernelpps_handle && ok_kpps) { /* use KPPS time */ /* pick the right edge */ if ( edge_kpps ) { clock_ts = pi.assert_timestamp; /* structure copy */ } else { clock_ts = pi.clear_timestamp; /* structure copy */ } } #endif /* defined(HAVE_SYS_TIMEPPS_H) */ /* else, use plain PPS */ /* This innocuous-looking "+ 1" embodies a significant * assumption: that GPSes report time to the second over the * serial stream *after* emitting PPS for the top of second. * Thus, when we see PPS our available report is from the * previous cycle and we must increment. * * FIXME! The GR-601W at 38,400 or faster can send the * serial fix before PPS by about 10 mSec! */ /*@+relaxtypes@*/ drift.real.tv_sec = last_fixtime_real + 1; drift.real.tv_nsec = 0; /* need to be fixed for 5Hz */ drift.clock = clock_ts; /*@-relaxtypes@*/ /* check to see if we have a fresh timestamp from the * GPS serial input then use that */ offset = (drift.real.tv_sec - drift.clock.tv_sec); offset += ((drift.real.tv_nsec - drift.clock.tv_nsec) / 1e9); delay = (drift.clock.tv_sec + drift.clock.tv_nsec / 1e9) - last_fixtime_clock; if (0.0 > delay || 1.0 < delay) { gpsd_report(session->context->debug, LOG_RAW, "PPS: no current GPS seconds: %f\n", delay); log1 = "timestamp out of range"; } else { /*@-compdef@*/ last_second_used = last_fixtime_real; if (session->thread_report_hook != NULL) log1 = session->thread_report_hook(session, &drift); else log1 = "no report hook"; if (session->context->pps_hook != NULL) session->context->pps_hook(session, &drift); /*@ -unrecog (splint has no pthread declarations as yet) @*/ (void)pthread_mutex_lock(&ppslast_mutex); /*@ +unrecog @*/ /*@-type@*/ /* splint is confused about struct timespec */ session->ppslast = drift; /*@+type@*/ session->ppscount++; /*@ -unrecog (splint has no pthread declarations as yet) @*/ (void)pthread_mutex_unlock(&ppslast_mutex); /*@ +unrecog @*/ /*@+compdef@*/ /*@-type@*/ /* splint is confused about struct timespec */ gpsd_report(session->context->debug, LOG_INF, "PPS hooks called with %.20s %lu.%09lu offset %.9f\n", log1, (unsigned long)clock_ts.tv_sec, (unsigned long)clock_ts.tv_nsec, offset); /*@+type@*/ } /*@-type@*/ /* splint is confused about struct timespec */ gpsd_report(session->context->debug, LOG_PROG, "PPS edge %.20s %lu.%09lu offset %.9f\n", log1, (unsigned long)clock_ts.tv_sec, (unsigned long)clock_ts.tv_nsec, offset); /*@+type@*/ } else { gpsd_report(session->context->debug, LOG_RAW, "PPS edge rejected %.100s", log); } } #if defined(HAVE_SYS_TIMEPPS_H) if (session->kernelpps_handle > 0) { gpsd_report(session->context->debug, LOG_PROG, "PPS descriptor cleaned up\n"); (void)time_pps_destroy(session->kernelpps_handle); } #endif if (session->thread_wrap_hook != NULL) session->thread_wrap_hook(session); gpsd_report(session->context->debug, LOG_PROG, "PPS gpsd_ppsmonitor exited.\n"); return NULL; }
int serWaitForSerialChange ( serDevT* dev ) { struct timeval tv; time_f timef; int i,ret; #ifdef ENABLE_TIMEPPS struct timespec timeout; pps_info_t ppsinfo; int ppslines; #endif #ifdef ENABLE_GPIO struct pollfd pollfds[1]; #endif if ( dev->modemlines == 0 ) return -1; switch ( dev->mode ) { case SERPORT_MODE_POLL: for ( i=0; i<10*1000; i++ ) { gettimeofday ( &tv, NULL ); timeval2time_f ( &tv, timef ); ret = serGetDevStatusLines ( dev, timef ); if ( ret < 0 ) return -1; if ( ret == 1 ) return 0; usleep ( 1000 ); } //timeout return -1; break; #ifdef ENABLE_GPIO case SERPORT_MODE_GPIO: pollfds[0].fd = dev->fd; pollfds[0].events = POLLERR; i = poll(pollfds, 1, 10000); /* timeout 10 seconds */ if (i != 1 && !(pollfds[0].revents & POLLERR) ) return -1; gettimeofday ( &tv, NULL ); timeval2time_f ( &tv, timef ); if ( serGetDevStatusLines ( dev, timef ) < 0 ) return -1; return 0; break; #endif #ifdef ENABLE_TIOCMIWAIT case SERPORT_MODE_IWAIT: signal ( SIGALRM, sigalrm ); alarm ( 10 ); if ( ioctl ( dev->fd, TIOCMIWAIT, dev->modemlines) != 0 ) return -1; gettimeofday ( &tv, NULL ); timeval2time_f ( &tv, timef ); signal ( SIGALRM, SIG_DFL ); alarm ( 0 ); if ( serGetDevStatusLines ( dev, timef ) < 0 ) return -1; return 0; break; #endif #ifdef ENABLE_TIMEPPS case SERPORT_MODE_TIMEPPS: timeout.tv_sec = 0; timeout.tv_nsec = 0; for ( i=0; i<10*100; i++ ) { if ( time_pps_fetch ( dev->ppshandle, PPS_TSFMT_TSPEC, &ppsinfo, &timeout ) == -1 ) { loggerf ( LOGGER_NOTE, "ppsfetch failed: %d\n", errno ); return -1; } if ( ppsinfo.assert_sequence != dev->ppslastassert ) { timespec2time_f ( &ppsinfo.assert_timestamp, timef ); ppslines = TIOCM_CD; //NOTE: assuming that pps support is on the DCD line dev->ppslastassert = ppsinfo.assert_sequence; if ( serStoreDevStatusLines ( dev, ppslines, timef ) < 0 ) return -1; return 0; } else if ( ppsinfo.clear_sequence != dev->ppslastclear ) { timespec2time_f ( &ppsinfo.clear_timestamp, timef ); ppslines = 0; dev->ppslastclear = ppsinfo.clear_sequence; if ( serStoreDevStatusLines ( dev, ppslines, timef ) < 0 ) return -1; return 0; } usleep ( 10000 ); } return -1; break; #endif } loggerf ( LOGGER_NOTE, "Error: serWaitForSerialChange(): mode not supported\n" ); //unknown serial port mode !! return -1; }
/*@-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; }
/* 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; }
int main(int argc, char **argv) { int fd; FILE *fdo; pps_info_t pi; pps_params_t pp; pps_handle_t ph; int i, mode; u_int olda, oldc; struct timespec to; char const *ofn; ofn = NULL; while ((i = getopt(argc, argv, "aAbBcCeo:uv")) != -1) { switch (i) { case 'a': aflag = 1; break; case 'A': Aflag = 1; break; case 'b': aflag = 1; cflag = 1; break; case 'B': Aflag = 1; Cflag = 1; break; case 'c': cflag = 1; break; case 'C': Cflag = 1; break; case 'e': eflag = 1; break; case 'o': ofn = optarg; break; case 'u': uflag = 1; break; case 'v': vflag = 1; break; case '?': default: fprintf(stderr, "Usage: ppsapitest [-aAcC] device\n"); exit (1); } } if (ofn != NULL) { fdo = fopen(ofn, "w"); if (fdo == NULL) err(1, "Cannot open %s", ofn); } else { fdo = NULL; } argc -= optind; argv += optind; if (argc > 0) { fd = open(argv[0], O_RDONLY); if (fd < 0) err(1, argv[0]); } else { fd = 0; } i = time_pps_create(fd, &ph); if (i < 0) err(1, "time_pps_create"); i = time_pps_getcap(ph, &mode); if (i < 0) err(1, "time_pps_getcap"); if (vflag) { fprintf(stderr, "Supported modebits:"); if (mode & PPS_CAPTUREASSERT) fprintf(stderr, " CAPTUREASSERT"); if (mode & PPS_CAPTURECLEAR) fprintf(stderr, " CAPTURECLEAR"); if (mode & PPS_OFFSETASSERT) fprintf(stderr, " OFFSETASSERT"); if (mode & PPS_OFFSETCLEAR) fprintf(stderr, " OFFSETCLEAR"); if (mode & PPS_ECHOASSERT) fprintf(stderr, " ECHOASSERT"); if (mode & PPS_ECHOCLEAR) fprintf(stderr, " ECHOCLEAR"); if (mode & PPS_CANWAIT) fprintf(stderr, " CANWAIT"); if (mode & PPS_CANPOLL) fprintf(stderr, " CANPOLL"); if (mode & PPS_TSFMT_TSPEC) fprintf(stderr, " TSPEC"); if (mode & PPS_TSFMT_NTPFP) fprintf(stderr, " NTPFP"); fprintf(stderr, "\n"); } if (!aflag && !cflag) { if (mode & PPS_CAPTUREASSERT) aflag = 1; if (mode & PPS_CAPTURECLEAR) cflag = 1; } if (!Aflag && !Cflag) { Aflag = aflag; Cflag = cflag; } if (Cflag && !(mode & PPS_CAPTURECLEAR)) errx(1, "-C but cannot capture on clear flank"); if (Aflag && !(mode & PPS_CAPTUREASSERT)) errx(1, "-A but cannot capture on assert flank"); i = time_pps_getparams(ph, &pp); if (i < 0) err(1, "time_pps_getparams():"); if (aflag) pp.mode |= PPS_CAPTUREASSERT; if (cflag) pp.mode |= PPS_CAPTURECLEAR; if (eflag & aflag) pp.mode |= PPS_ECHOASSERT; if (eflag & cflag) pp.mode |= PPS_ECHOCLEAR; if (!(pp.mode & PPS_TSFMT_TSPEC)) pp.mode |= PPS_TSFMT_TSPEC; i = time_pps_setparams(ph, &pp); if (i < 0) { err(1, "time_pps_setparams(mode %x):", pp.mode); } /* * Pick up first event outside the loop in order to not * get something ancient into the outfile. */ to.tv_nsec = 0; to.tv_sec = 0; i = time_pps_fetch(ph, PPS_TSFMT_TSPEC, &pi, &to); if (i < 0) err(1, "time_pps_fetch()"); olda = pi.assert_sequence; oldc = pi.clear_sequence; while (1) { to.tv_nsec = 0; to.tv_sec = 0; i = time_pps_fetch(ph, PPS_TSFMT_TSPEC, &pi, &to); if (i < 0) err(1, "time_pps_fetch()"); if (oldc != pi.clear_sequence && Cflag) ; else if (olda != pi.assert_sequence && Aflag) ; else { usleep(10000); continue; } if (fdo != NULL) { if (fwrite(&pi, sizeof pi, 1, fdo) != 1) err(1, "Write error on %s", ofn); if (uflag) fflush(fdo); } Chew(&pi.assert_timestamp, &pi.clear_timestamp, pi.assert_sequence, pi.clear_sequence); olda = pi.assert_sequence; oldc = pi.clear_sequence; } return(0); }