int main(int argc, char **argv) { int fd; pps_handle_t ph; pps_params_t ppsparams; int capability; int err; if (argc < 2) argv[1] = "/dev/cuaa1"; setbuf(stdout, 0); fd = open(argv[1], O_RDONLY); if (fd < 0) fprintf(stderr, "Cannot open device: %s\n", argv[1]); err = time_pps_create(fd, &ph); if (err < 0) fprintf(stderr, "time_pps_create\n"); err = report_pps_capability(ph, &capability); if (err < 0) { time_pps_destroy(ph); return (1); } err = set_pps_parameters(ph, capability, PPS_TSCLK_FBCK, &ppsparams); if (err < 0) { time_pps_destroy(ph); return (1); } err = capture_pulses(ph, 3); if (err < 0) { time_pps_destroy(ph); return (1); } err = set_pps_parameters(ph, capability, PPS_TSCLK_FFWD, &ppsparams); if (err < 0) { time_pps_destroy(ph); return (1); } err = capture_pulses(ph, 0); if (err < 0) { time_pps_destroy(ph); return (1); } time_pps_destroy(ph); return(0); }
int capture_pulses(pps_handle_t ph, int ppscount) { pps_info_ffc_t pi_ffc; struct timespec to; unsigned int old_assert, old_clear; int count; int err; count = 0; to.tv_nsec = 0; to.tv_sec = 0; old_assert = 0; old_clear = 0; while (1) { err = time_pps_fetch_ffc(ph, PPS_TSFMT_TSPEC, &pi_ffc, &to); if (err < 0) { fprintf(stderr, "time_pps_fetch"); time_pps_destroy(ph); return (err); } if (old_assert == pi_ffc.assert_sequence && old_clear == pi_ffc.clear_sequence) { //usleep(10000); usleep(500000); continue; } // TODO: should do a bit of housecleaning if we fetch in between CLEAR // and ASSERT, otherwise, get mismatch pulses. Note, possible on of the // edges is missed by kernel, so seq numbers may drift away print_pulse(&pi_ffc); old_assert = pi_ffc.assert_sequence; old_clear = pi_ffc.clear_sequence; count++; if ( ppscount && (count >= ppscount)) break; } fprintf(stdout, "Captured %d pulses\n", count); return (0); }
static void wwvb_control( int unit, struct refclockstat *in_st, struct refclockstat *out_st, struct peer *peer ) { register struct wwvbunit *up; struct refclockproc *pp; pp = peer->procptr; up = pp->unitptr; if (!(pp->sloppyclockflag & CLK_FLAG1)) { if (!up->ppsapi_tried) return; up->ppsapi_tried = 0; if (!up->ppsapi_lit) return; peer->flags &= ~FLAG_PPS; peer->precision = PRECISION; time_pps_destroy(up->atom.handle); up->atom.handle = 0; up->ppsapi_lit = 0; return; } if (up->ppsapi_tried) return; /* * Light up the PPSAPI interface. */ up->ppsapi_tried = 1; if (refclock_ppsapi(pp->io.fd, &up->atom)) { up->ppsapi_lit = 1; return; } NLOG(NLOG_CLOCKINFO) msyslog(LOG_WARNING, "%s flag1 1 but PPSAPI fails", refnumtoa(&peer->srcadr)); }
void ofApp::setup() { ofx::PPS pps; int num; pps_handle_t handle[4]; int avail_mode[4]; int i = 0; int ret; int argc = 2; char * argv[] = { "cmd", "/dev/pps0", }; for (i = 1; i < argc && i <= 4; i++) { ret = pps.findSource(argv[i], &handle[i - 1], &avail_mode[i - 1]); if (ret < 0) return;//exit(EXIT_FAILURE); } num = i - 1; printf("ok, found %d source(s), now start fetching data...\n", num); /* loop, printing the most recent timestamp every second or so */ while (1) { for (i = 0; i < num; i++) { ret = pps.fetchSource(i, &handle[i], &avail_mode[i]); if (ret < 0 && errno != ETIMEDOUT) return; //exit(EXIT_FAILURE); } } for (; i >= 0; i--) time_pps_destroy(handle[i]); return; }
static inline int set_flags() { struct timex tmx = { .modes = 0 }; if (adjtimex(&tmx) == -1) { fprintf(stderr, "adjtimex get failed: %s\n", strerror(errno)); return -1; } tmx.modes = ADJ_STATUS; tmx.status |= (STA_PPSFREQ | STA_PPSTIME); if (adjtimex(&tmx) == -1) { fprintf(stderr, "adjtimex set failed: %s\n", strerror(errno)); return -1; } return 0; } static inline int unset_flags() { struct timex tmx = { .modes = 0 }; if (adjtimex(&tmx) == -1) { fprintf(stderr, "adjtimex get failed: %s\n", strerror(errno)); return -1; } tmx.modes = ADJ_STATUS; tmx.status &= ~(STA_PPSFREQ | STA_PPSTIME); if (adjtimex(&tmx) == -1) { fprintf(stderr, "adjtimex set failed: %s\n", strerror(errno)); return -1; } return 0; } static inline void usage(char *name) { fprintf(stderr, "Usage: %s [-bBfFac] <ppsdev>\n" "Commands:\n" " -b bind kernel PPS consumer\n" " -B unbind kernel PPS consumer\n" " -f set kernel NTP PPS flags\n" " -F unset kernel NTP PPS flags\n" "Options:\n" " -a use assert edge\n" " -c use clear edge (default)\n", name); } static void parse_args(int argc, char **argv) { while (1) { int c; /* getopt_long stores the option index here. */ int option_index = 0; static struct option long_options[] = { {"bind", no_argument, 0, 'b'}, {"unbind", no_argument, 0, 'B'}, {"set-flags", no_argument, 0, 'f'}, {"unset-flags", no_argument, 0, 'F'}, {"assert", no_argument, 0, 'a'}, {"clear", no_argument, 0, 'c'}, {"help", no_argument, 0, 'h'}, {0, 0, 0, 0} }; c = getopt_long(argc, argv, "bBfFach", long_options, &option_index); /* detect the end of the options. */ if (c == -1) break; switch (c) { case 'b': { do_bind = 1; break; } case 'B': { do_bind = 2; break; } case 'f': { do_setflags = 1; break; } case 'F': { do_setflags = 2; break; } case 'a': { opt_edge = PPS_CAPTUREASSERT; break; } case 'c': { opt_edge = PPS_CAPTURECLEAR; break; } case 'h': { usage(argv[0]); exit(0); } default: { usage(argv[0]); exit(1); } } } if ((do_bind == 0) && (do_setflags == 0)) { printf("No command specified!\n"); usage(argv[0]); exit(1); } if (optind == argc - 1) { device = argv[optind]; } else { printf("Device name missing!\n"); usage(argv[0]); exit(1); } } int main(int argc, char *argv[]) { pps_handle_t handle; int avail_mode; /* Check the command line */ parse_args(argc, argv); if (find_source(device, &handle, &avail_mode) < 0) exit(EXIT_FAILURE); if (do_setflags == 2) if (unset_flags() < 0) { fprintf(stderr, "Failed to unset flags!\n"); exit(EXIT_FAILURE); } if (do_bind == 2) if (bind(handle, 0) < 0) { fprintf(stderr, "Unbind failed: %s\n", strerror(errno)); exit(EXIT_FAILURE); } if (do_bind == 1) if (bind(handle, opt_edge) < 0) { fprintf(stderr, "Bind failed: %s\n", strerror(errno)); exit(EXIT_FAILURE); } if (do_setflags == 1) if (set_flags() < 0) { fprintf(stderr, "Failed to set flags!\n"); exit(EXIT_FAILURE); } time_pps_destroy(handle); return 0; }
/*@-compdestroy -nullpass -unrecog@*/ static int init_kernel_pps(struct gps_device_t *session) /* return handle for kernel pps, or -1; requires root privileges */ { #ifndef S_SPLINT_S pps_params_t pp; #endif /* S_SPLINT_S */ int ret; #ifdef linux /* These variables are only needed by Linux to find /dev/ppsN. */ int ldisc = 18; /* the PPS line discipline */ glob_t globbuf; size_t i; /* to match type of globbuf.gl_pathc */ char pps_num = '\0'; /* /dev/pps[pps_num] is our device */ char path[GPS_PATH_MAX] = ""; #endif session->kernelpps_handle = -1; if ( isatty(session->gpsdata.gps_fd) == 0 ) { gpsd_report(session->context->debug, LOG_INF, "KPPS gps_fd not a tty\n"); return -1; } /* * This next code block abuses "ret" by storing the filedescriptor * to use for RFC2783 calls. */ ret = -1; #ifdef linux /* * On Linux, one must make calls to associate a serial port with a * /dev/ppsN device and then grovel in system data to determine * the association. */ /*@+ignoresigns@*/ /* Attach the line PPS discipline, so no need to ldattach */ /* This activates the magic /dev/pps0 device */ /* Note: this ioctl() requires root */ if ( 0 > ioctl(session->gpsdata.gps_fd, TIOCSETD, &ldisc)) { gpsd_report(session->context->debug, LOG_INF, "KPPS cannot set PPS line discipline: %s\n", strerror(errno)); return -1; } /*@-ignoresigns@*/ /* uh, oh, magic file names!, RFC2783 neglects to specify how * to associate the serial device and pps device names */ /* need to look in /sys/devices/virtual/pps/pps?/path * (/sys/class/pps/pps?/path is just a link to that) * to find the /dev/pps? that matches our serial port. * this code fails if there are more then 10 pps devices. * * yes, this could be done with libsysfs, but trying to keep the * number of required libs small, and libsysfs would still be linux only */ memset( (void *)&globbuf, 0, sizeof(globbuf)); (void)glob("/sys/devices/virtual/pps/pps?/path", 0, NULL, &globbuf); memset( (void *)&path, 0, sizeof(path)); for ( i = 0; i < globbuf.gl_pathc; i++ ) { int fd = open(globbuf.gl_pathv[i], O_RDONLY); if ( 0 <= fd ) { ssize_t r = read( fd, path, sizeof(path) -1); if ( 0 < r ) { path[r - 1] = '\0'; /* remove trailing \x0a */ } (void)close(fd); } gpsd_report(session->context->debug, LOG_INF, "KPPS checking %s, %s\n", globbuf.gl_pathv[i], path); if ( 0 == strncmp( path, session->gpsdata.dev.path, sizeof(path))) { /* this is the pps we are looking for */ /* FIXME, now build the proper pps device path */ pps_num = globbuf.gl_pathv[i][28]; break; } memset( (void *)&path, 0, sizeof(path)); } /* done with blob, clear it */ globfree(&globbuf); if ( 0 == (int)pps_num ) { gpsd_report(session->context->debug, LOG_INF, "KPPS device not found.\n"); return -1; } /* contruct the magic device path */ (void)snprintf(path, sizeof(path), "/dev/pps%c", pps_num); /* root privs are required for this device open */ if ( 0 != getuid() ) { gpsd_report(session->context->debug, LOG_INF, "KPPS only works as root \n"); return -1; } ret = open(path, O_RDWR); if ( 0 > ret ) { gpsd_report(session->context->debug, LOG_INF, "KPPS cannot open %s: %s\n", path, strerror(errno)); return -1; } #else /* not linux */ /* * On BSDs that support RFC2783, one uses the API calls on serial * port file descriptor. */ // cppcheck-suppress redundantAssignment ret = session->gpsdata.gps_fd; #endif /* assert(ret >= 0); */ gpsd_report(session->context->debug, LOG_INF, "KPPS RFC2783 fd is %d\n", ret); /* RFC 2783 implies the time_pps_setcap() needs priviledges * * keep root a tad longer just in case */ if ( 0 > time_pps_create(ret, &session->kernelpps_handle )) { gpsd_report(session->context->debug, LOG_INF, "KPPS time_pps_create(%d) failed: %s\n", ret, strerror(errno)); return -1; } else { #ifndef S_SPLINT_S /* have kernel PPS handle */ int caps; /* get features supported */ if ( 0 > time_pps_getcap(session->kernelpps_handle, &caps)) { gpsd_report(session->context->debug, LOG_ERROR, "KPPS time_pps_getcap() failed\n"); } else { gpsd_report(session->context->debug, LOG_INF, "KPPS caps %0x\n", caps); } #ifdef linux /* linux 2.6.34 can not PPS_ECHOASSERT | PPS_ECHOCLEAR */ memset( (void *)&pp, 0, sizeof(pps_params_t)); pp.mode = PPS_CAPTUREBOTH; #else /* not linux */ /* * Attempt to follow RFC2783 as straightforwardly as possible. */ pp.mode = PPS_TSFMT_TSPEC | PPS_CAPTUREBOTH; #endif #endif /* S_SPLINT_S */ if ( 0 > time_pps_setparams(session->kernelpps_handle, &pp)) { gpsd_report(session->context->debug, LOG_ERROR, "KPPS time_pps_setparams() failed: %s\n", strerror(errno)); time_pps_destroy(session->kernelpps_handle); return -1; } } return 0; }
/*@-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; }
/*@-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; }
/* return handle for kernel pps, or -1 */ static int init_kernel_pps(struct gps_device_t *session) { int ldisc = 18; /* the PPS line discipline */ pps_params_t pp; glob_t globbuf; size_t i; /* to match type of globbuf.gl_pathc */ char pps_num = 0; /* /dev/pps[pps_num] is our device */ char path[GPS_PATH_MAX] = ""; session->kernelpps_handle = -1; if ( !isatty(session->gpsdata.gps_fd) ) { gpsd_report(LOG_INF, "KPPS gps_fd not a tty\n"); return -1; } /* Attach the line PPS discipline, so no need to ldattach */ /* This activates the magic /dev/pps0 device */ /* Note: this ioctl() requires root */ if ( 0 > ioctl(session->gpsdata.gps_fd, TIOCSETD, &ldisc)) { gpsd_report(LOG_INF, "KPPS cannot set PPS line discipline: %s\n" , strerror(errno)); return -1; } /* uh, oh, magic file names!, this is not how RFC2783 was designed */ /* need to look in /sys/devices/virtual/pps/pps?/path * (/sys/class/pps/pps?/path is just a link to that) * to find the /dev/pps? that matches our serial port. * this code fails if there are more then 10 pps devices. * * yes, this could be done with libsysfs, but trying to keep the * number of required libs small */ memset( (void *)&globbuf, 0, sizeof(globbuf)); glob("/sys/devices/virtual/pps/pps?/path", 0, NULL, &globbuf); memset( (void *)&path, 0, sizeof(path)); for ( i = 0; i < globbuf.gl_pathc; i++ ) { int fd = open(globbuf.gl_pathv[i], O_RDONLY); if ( 0 <= fd ) { ssize_t r = read( fd, path, sizeof(path) -1); if ( 0 < r ) { path[r - 1] = '\0'; /* remove trailing \x0a */ } close(fd); } gpsd_report(LOG_INF, "KPPS checking %s, %s\n" , globbuf.gl_pathv[i], path); if ( 0 == strncmp( path, session->gpsdata.dev.path, sizeof(path))) { /* this is the pps we are looking for */ /* FIXME, now build the proper pps device path */ pps_num = globbuf.gl_pathv[i][28]; break; } memset( (void *)&path, 0, sizeof(path)); } /* done with blob, clear it */ globfree(&globbuf); if ( 0 == pps_num ) { gpsd_report(LOG_INF, "KPPS device not found.\n"); return -1; } /* contruct the magic device path */ (void)snprintf(path, sizeof(path), "/dev/pps%c", pps_num); /* root privs are required for this device open */ int ret = open(path, O_RDWR); if ( 0 > ret ) { gpsd_report(LOG_INF, "KPPS cannot open %s: %s\n" , path, strerror(errno)); return -1; } /* root privs are not required past this point */ if ( 0 > time_pps_create(ret, &session->kernelpps_handle )) { gpsd_report(LOG_INF, "KPPS time_pps_create(%d) failed: %s\n" , ret, strerror(errno)); return -1; } else { /* have kernel PPS handle */ int caps; /* get features supported */ if ( 0 > time_pps_getcap(session->kernelpps_handle, &caps)) { gpsd_report(LOG_ERROR, "KPPS time_pps_getcap() failed\n"); } else { gpsd_report(LOG_INF, "KPPS caps %0x\n", caps); } /* linux 2.6.34 can not PPS_ECHOASSERT | PPS_ECHOCLEAR */ memset( (void *)&pp, 0, sizeof(pps_params_t)); pp.mode = PPS_CAPTUREBOTH; if ( 0 > time_pps_setparams(session->kernelpps_handle, &pp)) { gpsd_report(LOG_ERROR, "KPPS time_pps_setparams() failed: %s\n", strerror(errno)); time_pps_destroy(session->kernelpps_handle); return -1; } } 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; }
static int init_kernel_pps(struct inner_context_t *inner_context) /* return handle for kernel pps, or -1; requires root privileges */ { pps_params_t pp; int ret; #ifdef __linux__ /* These variables are only needed by Linux to find /dev/ppsN. */ int ldisc = 18; /* the PPS line discipline */ glob_t globbuf; #endif char path[PATH_MAX] = ""; volatile struct pps_thread_t *pps_thread = inner_context->pps_thread; inner_context->kernelpps_handle = -1; inner_context->pps_canwait = false; /* * This next code block abuses "ret" by storing the filedescriptor * to use for RFC2783 calls. */ #ifndef __clang_analyzer__ ret = -1; /* this ret will not be unneeded when the 'else' part * of the followinng ifdef becomes an #elif */ #endif /* __clang_analyzer__ */ #ifdef __linux__ /* * Some Linuxes, like the RasbPi's, have PPS devices preexisting. * Other OS have no way to automatically determine the proper /dev/ppsX. * Allow user to pass in an explicit PPS device path. * * (We use strncpy() here because this might be compiled where * strlcpy() is not available.) */ if (strncmp(pps_thread->devicename, "/dev/pps", 8) == 0) (void)strncpy(path, pps_thread->devicename, sizeof(path)); else { char pps_num = '\0'; /* /dev/pps[pps_num] is our device */ size_t i; /* to match type of globbuf.gl_pathc */ /* * Otherwise one must make calls to associate a serial port with a * /dev/ppsN device and then grovel in system data to determine * the association. */ /* Attach the line PPS discipline, so no need to ldattach */ /* This activates the magic /dev/pps0 device */ /* Note: this ioctl() requires root, and device is a tty */ if ( 0 > ioctl(pps_thread->devicefd, TIOCSETD, &ldisc)) { char errbuf[BUFSIZ] = "unknown error"; strerror_r(errno, errbuf, sizeof(errbuf)); pps_thread->log_hook(pps_thread, THREAD_INF, "KPPS:%s cannot set PPS line discipline %s\n", pps_thread->devicename, errbuf); return -1; } /* uh, oh, magic file names!, RFC2783 neglects to specify how * to associate the serial device and pps device names */ /* need to look in /sys/devices/virtual/pps/pps?/path * (/sys/class/pps/pps?/path is just a link to that) * to find the /dev/pps? that matches our serial port. * this code fails if there are more then 10 pps devices. * * yes, this could be done with libsysfs, but trying to keep * the number of required libs small, and libsysfs would still * be linux only */ memset( (void *)&globbuf, 0, sizeof(globbuf)); (void)glob("/sys/devices/virtual/pps/pps?/path", 0, NULL, &globbuf); memset( (void *)&path, 0, sizeof(path)); for ( i = 0; i < globbuf.gl_pathc; i++ ) { int fd = open(globbuf.gl_pathv[i], O_RDONLY); if ( 0 <= fd ) { ssize_t r = read( fd, path, sizeof(path) -1); if ( 0 < r ) { path[r - 1] = '\0'; /* remove trailing \x0a */ } (void)close(fd); } pps_thread->log_hook(pps_thread, THREAD_PROG, "KPPS:%s checking %s, %s\n", pps_thread->devicename, globbuf.gl_pathv[i], path); if ( 0 == strncmp( path, pps_thread->devicename, sizeof(path))) { /* this is the pps we are looking for */ /* FIXME, now build the proper pps device path */ pps_num = globbuf.gl_pathv[i][28]; break; } memset( (void *)&path, 0, sizeof(path)); } /* done with blob, clear it */ globfree(&globbuf); if ( 0 == (int)pps_num ) { pps_thread->log_hook(pps_thread, THREAD_INF, "KPPS:%s device not found.\n", pps_thread->devicename); return -1; } /* construct the magic device path */ (void)snprintf(path, sizeof(path), "/dev/pps%c", pps_num); } /* root privs are probably required for this device open * do not bother to check uid, just go for the open() */ ret = open(path, O_RDWR); if ( 0 > ret ) { char errbuf[BUFSIZ] = "unknown error"; (void)strerror_r(errno, errbuf, sizeof(errbuf)); pps_thread->log_hook(pps_thread, THREAD_INF, "KPPS:%s cannot open %s: %s\n", pps_thread->devicename, path, errbuf); return -1; } #else /* not __linux__ */ /* * On BSDs that support RFC2783, one uses the API calls on serial * port file descriptor. * * FIXME! need more specific than 'not linux' */ (void)strlcpy(path, pps_thread->devicename, sizeof(path)); // cppcheck-suppress redundantAssignment ret = pps_thread->devicefd; #endif /* assert(ret >= 0); */ pps_thread->log_hook(pps_thread, THREAD_INF, "KPPS:%s RFC2783 path:%s, fd is %d\n", pps_thread->devicename, path, ret); /* RFC 2783 implies the time_pps_setcap() needs priviledges * * keep root a tad longer just in case */ if ( 0 > time_pps_create(ret, (pps_handle_t *)&inner_context->kernelpps_handle )) { char errbuf[BUFSIZ] = "unknown error"; (void)strerror_r(errno, errbuf, sizeof(errbuf)); pps_thread->log_hook(pps_thread, THREAD_INF, "KPPS:%s time_pps_create(%d) failed: %s\n", pps_thread->devicename, ret, errbuf); return -1; } /* have kernel PPS handle */ /* get RFC2783 features supported */ inner_context->pps_caps = 0; if ( 0 > time_pps_getcap(inner_context->kernelpps_handle, &inner_context->pps_caps)) { char errbuf[BUFSIZ] = "unknown error"; inner_context->pps_caps = 0; (void)strerror_r(errno, errbuf, sizeof(errbuf)); pps_thread->log_hook(pps_thread, THREAD_INF, "KPPS:%s time_pps_getcap() failed: %.100s\n", pps_thread->devicename, errbuf); return -1; } else { pps_thread->log_hook(pps_thread, THREAD_INF, "KPPS:%s pps_caps 0x%02X\n", pps_thread->devicename, inner_context->pps_caps); } /* construct the setparms structure */ memset( (void *)&pp, 0, sizeof(pps_params_t)); pp.api_version = PPS_API_VERS_1; /* version 1 protocol */ if ( 0 == (PPS_TSFMT_TSPEC & inner_context->pps_caps ) ) { /* PPS_TSFMT_TSPEC means return a timespec * mandatory for driver to implement, require it */ pps_thread->log_hook(pps_thread, THREAD_WARN, "KPPS:%s fail, missing mandatory PPS_TSFMT_TSPEC\n", pps_thread->devicename); return -1; } if ( 0 != (PPS_CANWAIT & inner_context->pps_caps ) ) { /* we can wait! so no need for TIOCMIWAIT */ pps_thread->log_hook(pps_thread, THREAD_INF, "KPPS:%s have PPS_CANWAIT\n", pps_thread->devicename); inner_context->pps_canwait = true; } pp.mode = PPS_TSFMT_TSPEC; switch ( (PPS_CAPTUREASSERT | PPS_CAPTURECLEAR) & inner_context->pps_caps ) { case PPS_CAPTUREASSERT: pps_thread->log_hook(pps_thread, THREAD_WARN, "KPPS:%s missing PPS_CAPTURECLEAR, pulse may be offset\n", pps_thread->devicename); pp.mode |= PPS_CAPTUREASSERT; break; case PPS_CAPTURECLEAR: pps_thread->log_hook(pps_thread, THREAD_WARN, "KPPS:%s missing PPS_CAPTUREASSERT, pulse may be offset\n", pps_thread->devicename); pp.mode |= PPS_CAPTURECLEAR; break; case PPS_CAPTUREASSERT | PPS_CAPTURECLEAR: pp.mode |= PPS_CAPTUREASSERT | PPS_CAPTURECLEAR; break; default: /* THREAD_ERR in the calling routine */ pps_thread->log_hook(pps_thread, THREAD_INF, "KPPS:%s missing PPS_CAPTUREASSERT and CLEAR\n", pps_thread->devicename); return -1; } if ( 0 > time_pps_setparams(inner_context->kernelpps_handle, &pp)) { char errbuf[BUFSIZ] = "unknown error"; (void)strerror_r(errno, errbuf, sizeof(errbuf)); pps_thread->log_hook(pps_thread, THREAD_ERROR, "KPPS:%s time_pps_setparams(mode=0x%02X) failed: %s\n", pps_thread->devicename, pp.mode, errbuf); (void)time_pps_destroy(inner_context->kernelpps_handle); return -1; } return 0; }
PPS::~PPS(void) { #if defined(DUNE_SYS_HAS_TIMEPPS_H) time_pps_destroy(m_handle); #endif }