Ejemplo n.º 1
0
/* offset is actual_ts - clock_ts */
static void chrony_send(struct gps_device_t *session, struct timedelta_t *td)
{
    char real_str[TIMESPEC_LEN];
    char clock_str[TIMESPEC_LEN];
    struct timespec offset;
    struct sock_sample sample;
    struct tm tm;
    int leap_notify = session->context->leap_notify;

    /*
     * insist that leap seconds only happen in june and december
     * GPS emits leap pending for 3 months prior to insertion
     * NTP expects leap pending for only 1 month prior to insertion
     * Per http://bugs.ntp.org/1090
     *
     * ITU-R TF.460-6, Section 2.1, says lappe seconds can be primarily
     * in Jun/Dec but may be in March or September
     */
    (void)gmtime_r( &(td->real.tv_sec), &tm);
    if ( 5 != tm.tm_mon && 11 != tm.tm_mon ) {
        /* Not june, not December, no way */
        leap_notify = LEAP_NOWARNING;
    }


    /* chrony expects tv-sec since Jan 1970 */
    sample.pulse = 0;
    sample.leap = leap_notify;
    sample.magic = SOCK_MAGIC;
    /* chronyd wants a timeval, not a timspec, not to worry, it is
     * just the top of the second */
    TSTOTV(&sample.tv, &td->clock);
    /* calculate the offset as a timespec to not lose precision */
    TS_SUB( &offset, &td->real, &td->clock);
    /* if tv_sec greater than 2 then tv_nsec loses precision, but
     * not a big deal as slewing will be required */
    sample.offset = TSTONS( &offset );
    sample._pad = 0;

    timespec_str( &td->real, real_str, sizeof(real_str) );
    timespec_str( &td->clock, clock_str, sizeof(clock_str) );
    gpsd_log(&session->context->errout, LOG_RAW,
	     "PPS chrony_send %s @ %s Offset: %0.9f\n",
	     real_str, clock_str, sample.offset);
    (void)send(session->chronyfd, &sample, sizeof (sample), 0);
}
Ejemplo n.º 2
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;
}