Пример #1
0
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);
}
Пример #2
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);
}
Пример #3
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));
}
Пример #4
0
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;

}
Пример #5
0
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;
}
Пример #6
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;
}
Пример #7
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;
}
Пример #8
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;
}
Пример #9
0
/* 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;
}
Пример #10
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;
}
Пример #11
0
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;
}
Пример #12
0
    PPS::~PPS(void)
    {
#if defined(DUNE_SYS_HAS_TIMEPPS_H)
      time_pps_destroy(m_handle);
#endif
    }