Exemplo n.º 1
0
void pps_thread_activate(struct gps_device_t *session)
/* activate a thread to watch the device's PPS transitions */
{
    int retval;
    pthread_t pt;
#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(session->context->debug, LOG_WARN,
		    "KPPS kernel PPS will be used\n");
    }
#endif
    /*@-compdef -nullpass@*/
    retval = pthread_create(&pt, NULL, gpsd_ppsmonitor, (void *)session);
    /*@+compdef +nullpass@*/
    gpsd_report(session->context->debug, LOG_PROG, "PPS thread %s\n",
		(retval==0) ? "launched" : "FAILED");
}
Exemplo n.º 2
0
void pps_thread_activate(volatile struct pps_thread_t *pps_thread)
/* activate a thread to watch the device's PPS transitions */
{
    int retval;
    pthread_t pt;
    /*
     * FIXME: this launch code is not itself thread-safe!
     * It would be if inner_context could be auto, but the monitor
     * routine gets garbage when we try that.  Ideally the body
     * of this function would be guarded by a separate mutex.
     * Either that, or this should be an exception to the no-malloc rule.
     */
    static struct inner_context_t	inner_context;

    inner_context.pps_thread = pps_thread;
#if defined(HAVE_SYS_TIMEPPS_H)
    /* some operations in init_kernel_pps() require root privs */
    (void)init_kernel_pps(&inner_context);
    if ( 0 <= inner_context.kernelpps_handle ) {
	pps_thread->log_hook(pps_thread, THREAD_INF,
		    "KPPS:%s kernel PPS will be used\n",
		    pps_thread->devicename);
    } else
#endif
    {
	pps_thread->log_hook(pps_thread, THREAD_WARN,
		    "KPPS:%s kernel PPS unavailable, PPS accuracy will suffer\n",
		    pps_thread->devicename);
    }

    memset( &pt, 0, sizeof(pt));
    retval = pthread_create(&pt, NULL, gpsd_ppsmonitor, (void *)&inner_context);
    pps_thread->log_hook(pps_thread, THREAD_PROG, "PPS:%s thread %s\n",
	        pps_thread->devicename,
		(retval==0) ? "launched" : "FAILED");
}
Exemplo n.º 3
0
/*@-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;
}