File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / ntp / ntpd / refclock_nmea.c
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Tue May 29 12:08:38 2012 UTC (12 years, 1 month ago) by misho
Branches: ntp, MAIN
CVS tags: v4_2_6p5p0, v4_2_6p5, HEAD
ntp 4.2.6p5

    1: /*
    2:  * refclock_nmea.c - clock driver for an NMEA GPS CLOCK
    3:  *		Michael Petry Jun 20, 1994
    4:  *		 based on refclock_heathn.c
    5:  *
    6:  * Updated to add support for Accord GPS Clock
    7:  *		Venu Gopal Dec 05, 2007
    8:  *		neo.venu@gmail.com, venugopal_d@pgad.gov.in
    9:  *
   10:  * Updated to process 'time1' fudge factor
   11:  *		Venu Gopal May 05, 2008
   12:  *
   13:  * Converted to common PPSAPI code, separate PPS fudge time1
   14:  * from serial timecode fudge time2.
   15:  *		Dave Hart July 1, 2009
   16:  *		hart@ntp.org, davehart@davehart.com
   17:  */
   18: 
   19: #ifdef HAVE_CONFIG_H
   20: #include <config.h>
   21: #endif
   22: 
   23: #if defined(REFCLOCK) && defined(CLOCK_NMEA)
   24: 
   25: #include <sys/stat.h>
   26: #include <stdio.h>
   27: #include <ctype.h>
   28: 
   29: #include "ntpd.h"
   30: #include "ntp_io.h"
   31: #include "ntp_unixtime.h"
   32: #include "ntp_refclock.h"
   33: #include "ntp_stdlib.h"
   34: #include "ntp_calendar.h"
   35: 
   36: #ifdef HAVE_PPSAPI
   37: # include "ppsapi_timepps.h"
   38: # include "refclock_atom.h"
   39: #endif /* HAVE_PPSAPI */
   40: 
   41: #ifdef SYS_WINNT
   42: #undef write	/* ports/winnt/include/config.h: #define write _write */
   43: extern int async_write(int, const void *, unsigned int);
   44: #define write(fd, data, octets)	async_write(fd, data, octets)
   45: #endif
   46: 
   47: #ifndef TIMESPECTOTS
   48: #define TIMESPECTOTS(ptspec, pts)					\
   49: 	do {								\
   50: 		DTOLFP((ptspec)->tv_nsec * 1.0e-9, pts);		\
   51: 		(pts)->l_ui += (u_int32)((ptspec)->tv_sec) + JAN_1970;	\
   52: 	} while (0)
   53: #endif
   54: 
   55: 
   56: /*
   57:  * This driver supports NMEA-compatible GPS receivers
   58:  *
   59:  * Prototype was refclock_trak.c, Thanks a lot.
   60:  *
   61:  * The receiver used spits out the NMEA sentences for boat navigation.
   62:  * And you thought it was an information superhighway.	Try a raging river
   63:  * filled with rapids and whirlpools that rip away your data and warp time.
   64:  *
   65:  * If HAVE_PPSAPI is defined code to use the PPSAPI will be compiled in.
   66:  * On startup if initialization of the PPSAPI fails, it will fall back
   67:  * to the "normal" timestamps.
   68:  *
   69:  * The PPSAPI part of the driver understands fudge flag2 and flag3. If
   70:  * flag2 is set, it will use the clear edge of the pulse. If flag3 is
   71:  * set, kernel hardpps is enabled.
   72:  *
   73:  * GPS sentences other than RMC (the default) may be enabled by setting
   74:  * the relevent bits of 'mode' in the server configuration line
   75:  * server 127.127.20.x mode X
   76:  * 
   77:  * bit 0 - enables RMC (1)
   78:  * bit 1 - enables GGA (2)
   79:  * bit 2 - enables GLL (4)
   80:  * bit 3 - enables ZDA (8) - Standard Time & Date
   81:  * bit 3 - enables ZDG (8) - Accord GPS Clock's custom sentence with GPS time 
   82:  *			     very close to standard ZDA
   83:  * 
   84:  * Multiple sentences may be selected except when ZDG/ZDA is selected.
   85:  *
   86:  * bit 4/5/6 - selects the baudrate for serial port :
   87:  *		0 for 4800 (default) 
   88:  *		1 for 9600 
   89:  *		2 for 19200 
   90:  *		3 for 38400 
   91:  *		4 for 57600 
   92:  *		5 for 115200 
   93:  */
   94: #define NMEA_MESSAGE_MASK_OLD	 0x07
   95: #define NMEA_MESSAGE_MASK_SINGLE 0x08
   96: #define NMEA_MESSAGE_MASK	 (NMEA_MESSAGE_MASK_OLD | NMEA_MESSAGE_MASK_SINGLE)
   97: 
   98: #define NMEA_BAUDRATE_MASK	 0x70
   99: #define NMEA_BAUDRATE_SHIFT	 4
  100: 
  101: /*
  102:  * Definitions
  103:  */
  104: #define	DEVICE		"/dev/gps%d"	/* GPS serial device */
  105: #define	PPSDEV		"/dev/gpspps%d"	/* PPSAPI device override */
  106: #define	SPEED232	B4800	/* uart speed (4800 bps) */
  107: #define	PRECISION	(-9)	/* precision assumed (about 2 ms) */
  108: #define	PPS_PRECISION	(-20)	/* precision assumed (about 1 us) */
  109: #define	REFID		"GPS\0"	/* reference id */
  110: #define	DESCRIPTION	"NMEA GPS Clock" /* who we are */
  111: #ifndef O_NOCTTY
  112: #define M_NOCTTY	0
  113: #else
  114: #define M_NOCTTY	O_NOCTTY
  115: #endif
  116: #ifndef O_NONBLOCK
  117: #define M_NONBLOCK	0
  118: #else
  119: #define M_NONBLOCK	O_NONBLOCK
  120: #endif
  121: #define PPSOPENMODE	(O_RDWR | M_NOCTTY | M_NONBLOCK)
  122: 
  123: /* NMEA sentence array indexes for those we use */
  124: #define NMEA_GPRMC	0	/* recommended min. nav. */
  125: #define NMEA_GPGGA	1	/* fix and quality */
  126: #define NMEA_GPGLL	2	/* geo. lat/long */
  127: #define NMEA_GPZDA	3	/* date/time */
  128: /*
  129:  * $GPZDG is a proprietary sentence that violates the spec, by not
  130:  * using $P and an assigned company identifier to prefix the sentence
  131:  * identifier.	When used with this driver, the system needs to be
  132:  * isolated from other NTP networks, as it operates in GPS time, not
  133:  * UTC as is much more common.	GPS time is >15 seconds different from
  134:  * UTC due to not respecting leap seconds since 1970 or so.  Other
  135:  * than the different timebase, $GPZDG is similar to $GPZDA.
  136:  */
  137: #define NMEA_GPZDG	4
  138: #define NMEA_ARRAY_SIZE (NMEA_GPZDG + 1)
  139: 
  140: /*
  141:  * Sentence selection mode bits
  142:  */
  143: #define USE_ALL			0	/* any/all */
  144: #define USE_GPRMC		1
  145: #define USE_GPGGA		2
  146: #define USE_GPGLL		4
  147: #define USE_GPZDA_ZDG		8	/* affects both */
  148: 
  149: /* mapping from sentence index to controlling mode bit */
  150: u_char sentence_mode[NMEA_ARRAY_SIZE] =
  151: {
  152: 	USE_GPRMC,
  153: 	USE_GPGGA,
  154: 	USE_GPGLL,
  155: 	USE_GPZDA_ZDG,
  156: 	USE_GPZDA_ZDG
  157: };
  158: 
  159: /*
  160:  * Unit control structure
  161:  */
  162: struct nmeaunit {
  163: #ifdef HAVE_PPSAPI
  164: 	struct refclock_atom atom; /* PPSAPI structure */
  165: 	int	ppsapi_tried;	/* attempt PPSAPI once */
  166: 	int	ppsapi_lit;	/* time_pps_create() worked */
  167: 	int	ppsapi_fd;	/* fd used with PPSAPI */
  168: 	int	ppsapi_gate;	/* allow edge detection processing */
  169: 	int	tcount;		/* timecode sample counter */
  170: 	int	pcount;		/* PPS sample counter */
  171: #endif /* HAVE_PPSAPI */
  172: 	l_fp	tstamp;		/* timestamp of last poll */
  173: 	int	gps_time;	/* 0 UTC, 1 GPS time */
  174: 		/* per sentence checksum seen flag */
  175: 	struct calendar used;	/* hh:mm:ss of used sentence */
  176: 	u_char	cksum_seen[NMEA_ARRAY_SIZE];
  177: };
  178: 
  179: /*
  180:  * Function prototypes
  181:  */
  182: static	int	nmea_start	(int, struct peer *);
  183: static	void	nmea_shutdown	(int, struct peer *);
  184: static	void	nmea_receive	(struct recvbuf *);
  185: static	void	nmea_poll	(int, struct peer *);
  186: #ifdef HAVE_PPSAPI
  187: static	void	nmea_control	(int, struct refclockstat *,
  188: 				 struct refclockstat *, struct peer *);
  189: static	void	nmea_timer	(int, struct peer *);
  190: #define		NMEA_CONTROL	nmea_control
  191: #define		NMEA_TIMER	nmea_timer
  192: #else
  193: #define		NMEA_CONTROL	noentry
  194: #define		NMEA_TIMER	noentry
  195: #endif /* HAVE_PPSAPI */
  196: static	void	gps_send	(int, const char *, struct peer *);
  197: static	char *	field_parse	(char *, int);
  198: static	int	nmea_checksum_ok(const char *);
  199: static void nmea_day_unfold(struct calendar*);
  200: static void nmea_century_unfold(struct calendar*);
  201: 
  202: /*
  203:  * Transfer vector
  204:  */
  205: struct	refclock refclock_nmea = {
  206: 	nmea_start,		/* start up driver */
  207: 	nmea_shutdown,		/* shut down driver */
  208: 	nmea_poll,		/* transmit poll message */
  209: 	NMEA_CONTROL,		/* fudge control */
  210: 	noentry,		/* initialize driver */
  211: 	noentry,		/* buginfo */
  212: 	NMEA_TIMER		/* called once per second */
  213: };
  214: 
  215: /*
  216:  * nmea_start - open the GPS devices and initialize data for processing
  217:  */
  218: static int
  219: nmea_start(
  220: 	int unit,
  221: 	struct peer *peer
  222: 	)
  223: {
  224: 	register struct nmeaunit *up;
  225: 	struct refclockproc *pp;
  226: 	int fd;
  227: 	char device[20];
  228: 	int baudrate;
  229: 	char *baudtext;
  230: 
  231: 	pp = peer->procptr;
  232: 
  233: 	/*
  234: 	 * Open serial port. Use CLK line discipline, if available.
  235: 	 */
  236: 	snprintf(device, sizeof(device), DEVICE, unit);
  237: 	
  238: 	/*
  239: 	 * Opening the serial port with appropriate baudrate
  240: 	 * based on the value of bit 4/5/6
  241: 	 */
  242: 	switch ((peer->ttl & NMEA_BAUDRATE_MASK) >> NMEA_BAUDRATE_SHIFT) {
  243: 	case 0:
  244: 	case 6:
  245: 	case 7:
  246: 	default:
  247: 		baudrate = SPEED232;
  248: 		baudtext = "4800";
  249: 		break;
  250: 	case 1:
  251: 		baudrate = B9600;
  252: 		baudtext = "9600";
  253: 		break;
  254: 	case 2:
  255: 		baudrate = B19200;
  256: 		baudtext = "19200";
  257: 		break;
  258: 	case 3:
  259: 		baudrate = B38400;
  260: 		baudtext = "38400";
  261: 		break;
  262: #ifdef B57600
  263: 	case 4:
  264: 		baudrate = B57600;
  265: 		baudtext = "57600";
  266: 		break;
  267: #endif
  268: #ifdef B115200
  269: 	case 5:
  270: 		baudrate = B115200;
  271: 		baudtext = "115200";
  272: 		break;
  273: #endif
  274: 	}
  275: 
  276: 	fd = refclock_open(device, baudrate, LDISC_CLK);
  277: 	
  278: 	if (fd <= 0) {
  279: #ifdef HAVE_READLINK
  280: 		/* nmead support added by Jon Miner (cp_n18@yahoo.com)
  281: 		 *
  282: 		 * See http://home.hiwaay.net/~taylorc/gps/nmea-server/
  283: 		 * for information about nmead
  284: 		 *
  285: 		 * To use this, you need to create a link from /dev/gpsX
  286: 		 * to the server:port where nmead is running.  Something
  287: 		 * like this:
  288: 		 *
  289: 		 * ln -s server:port /dev/gps1
  290: 		 */
  291: 		char buffer[80];
  292: 		char *nmea_host, *nmea_tail;
  293: 		int   nmea_port;
  294: 		int   len;
  295: 		struct hostent *he;
  296: 		struct protoent *p;
  297: 		struct sockaddr_in so_addr;
  298: 
  299: 		if ((len = readlink(device,buffer,sizeof(buffer))) == -1)
  300: 			return(0);
  301: 		buffer[len] = 0;
  302: 
  303: 		if ((nmea_host = strtok(buffer,":")) == NULL)
  304: 			return(0);
  305: 		if ((nmea_tail = strtok(NULL,":")) == NULL)
  306: 			return(0);
  307: 
  308: 		nmea_port = atoi(nmea_tail);
  309: 
  310: 		if ((he = gethostbyname(nmea_host)) == NULL)
  311: 			return(0);
  312: 		if ((p = getprotobyname("tcp")) == NULL)
  313: 			return(0);
  314: 		memset(&so_addr, 0, sizeof(so_addr));
  315: 		so_addr.sin_family = AF_INET;
  316: 		so_addr.sin_port = htons(nmea_port);
  317: 		so_addr.sin_addr = *((struct in_addr *) he->h_addr);
  318: 
  319: 		if ((fd = socket(PF_INET,SOCK_STREAM,p->p_proto)) == -1)
  320: 			return(0);
  321: 		if (connect(fd,(struct sockaddr *)&so_addr, sizeof(so_addr)) == -1) {
  322: 			close(fd);
  323: 			return (0);
  324: 		}
  325: #else
  326: 		pp->io.fd = -1;
  327: 		return (0);
  328: #endif
  329: 	}
  330: 
  331: 	msyslog(LOG_NOTICE, "%s serial %s open at %s bps",
  332: 		refnumtoa(&peer->srcadr), device, baudtext);
  333: 
  334: 	/*
  335: 	 * Allocate and initialize unit structure
  336: 	 */
  337: 	up = emalloc(sizeof(*up));
  338: 	memset(up, 0, sizeof(*up));
  339: 	pp->io.clock_recv = nmea_receive;
  340: 	pp->io.srcclock = (caddr_t)peer;
  341: 	pp->io.datalen = 0;
  342: 	pp->io.fd = fd;
  343: 	if (!io_addclock(&pp->io)) {
  344: 		pp->io.fd = -1;
  345: 		close(fd);
  346: 		free(up);
  347: 		return (0);
  348: 	}
  349: 	pp->unitptr = (caddr_t)up;
  350: 
  351: 	/*
  352: 	 * Initialize miscellaneous variables
  353: 	 */
  354: 	peer->precision = PRECISION;
  355: 	pp->clockdesc = DESCRIPTION;
  356: 	memcpy(&pp->refid, REFID, 4);
  357: 
  358: 	gps_send(fd,"$PMOTG,RMC,0000*1D\r\n", peer);
  359: 
  360: 	return (1);
  361: }
  362: 
  363: 
  364: /*
  365:  * nmea_shutdown - shut down a GPS clock
  366:  * 
  367:  * NOTE this routine is called after nmea_start() returns failure,
  368:  * as well as during a normal shutdown due to ntpq :config unpeer.
  369:  */
  370: static void
  371: nmea_shutdown(
  372: 	int unit,
  373: 	struct peer *peer
  374: 	)
  375: {
  376: 	register struct nmeaunit *up;
  377: 	struct refclockproc *pp;
  378: 
  379: 	UNUSED_ARG(unit);
  380: 
  381: 	pp = peer->procptr;
  382: 	up = (struct nmeaunit *)pp->unitptr;
  383: 	if (up != NULL) {
  384: #ifdef HAVE_PPSAPI
  385: 		if (up->ppsapi_lit) {
  386: 			time_pps_destroy(up->atom.handle);
  387: 			if (up->ppsapi_fd != pp->io.fd)
  388: 				close(up->ppsapi_fd);
  389: 		}
  390: #endif
  391: 		free(up);
  392: 	}
  393: 	if (-1 != pp->io.fd)
  394: 		io_closeclock(&pp->io);
  395: }
  396: 
  397: /*
  398:  * nmea_control - configure fudge params
  399:  */
  400: #ifdef HAVE_PPSAPI
  401: static void
  402: nmea_control(
  403: 	int unit,
  404: 	struct refclockstat *in_st,
  405: 	struct refclockstat *out_st,
  406: 	struct peer *peer
  407: 	)
  408: {
  409: 	char device[32];
  410: 	register struct nmeaunit *up;
  411: 	struct refclockproc *pp;
  412: 	int pps_fd;
  413: 	
  414: 	UNUSED_ARG(in_st);
  415: 	UNUSED_ARG(out_st);
  416: 
  417: 	pp = peer->procptr;
  418: 	up = (struct nmeaunit *)pp->unitptr;
  419: 
  420: 	if (!(CLK_FLAG1 & pp->sloppyclockflag)) {
  421: 		if (!up->ppsapi_tried)
  422: 			return;
  423: 		up->ppsapi_tried = 0;
  424: 		if (!up->ppsapi_lit)
  425: 			return;
  426: 		peer->flags &= ~FLAG_PPS;
  427: 		peer->precision = PRECISION;
  428: 		time_pps_destroy(up->atom.handle);
  429: 		if (up->ppsapi_fd != pp->io.fd)
  430: 			close(up->ppsapi_fd);
  431: 		up->atom.handle = 0;
  432: 		up->ppsapi_lit = 0;
  433: 		up->ppsapi_fd = -1;
  434: 		return;
  435: 	}
  436: 
  437: 	if (up->ppsapi_tried)
  438: 		return;
  439: 	/*
  440: 	 * Light up the PPSAPI interface.
  441: 	 */
  442: 	up->ppsapi_tried = 1;
  443: 
  444: 	/*
  445: 	 * if /dev/gpspps$UNIT can be opened that will be used for
  446: 	 * PPSAPI.  Otherwise, the GPS serial device /dev/gps$UNIT
  447: 	 * already opened is used for PPSAPI as well.
  448: 	 */
  449: 	snprintf(device, sizeof(device), PPSDEV, unit);
  450: 
  451: 	pps_fd = open(device, PPSOPENMODE, S_IRUSR | S_IWUSR);
  452: 
  453: 	if (-1 == pps_fd)
  454: 		pps_fd = pp->io.fd;
  455: 	
  456: 	if (refclock_ppsapi(pps_fd, &up->atom)) {
  457: 		up->ppsapi_lit = 1;
  458: 		up->ppsapi_fd = pps_fd;
  459: 		/* prepare to use the PPS API for our own purposes now. */
  460: 		refclock_params(pp->sloppyclockflag, &up->atom);
  461: 		return;
  462: 	}
  463: 
  464: 	NLOG(NLOG_CLOCKINFO)
  465: 		msyslog(LOG_WARNING, "%s flag1 1 but PPSAPI fails",
  466: 			refnumtoa(&peer->srcadr));
  467: }
  468: #endif	/* HAVE_PPSAPI */
  469: 
  470: 
  471: /*
  472:  * nmea_timer - called once per second, fetches PPS
  473:  *		timestamp and stuffs in median filter.
  474:  */
  475: #ifdef HAVE_PPSAPI
  476: static void
  477: nmea_timer(
  478: 	int		unit,
  479: 	struct peer *	peer
  480: 	)
  481: {
  482: 	struct nmeaunit *up;
  483: 	struct refclockproc *pp;
  484: 
  485: 	UNUSED_ARG(unit);
  486: 
  487: 	pp = peer->procptr;
  488: 	up = (struct nmeaunit *)pp->unitptr;
  489: 
  490: 	if (up->ppsapi_lit && up->ppsapi_gate &&
  491: 	    refclock_pps(peer, &up->atom, pp->sloppyclockflag) > 0) {
  492: 		up->pcount++,
  493: 		peer->flags |= FLAG_PPS;
  494: 		peer->precision = PPS_PRECISION;
  495: 	}
  496: }
  497: #endif	/* HAVE_PPSAPI */
  498: 
  499: #ifdef HAVE_PPSAPI
  500: /*
  501:  * This function is used to correlate a receive time stamp and a
  502:  * reference time with a PPS edge time stamp. It applies the necessary
  503:  * fudges (fudge1 for PPS, fudge2 for receive time) and then tries to
  504:  * move the receive time stamp to the corresponding edge. This can
  505:  * warp into future, if a transmission delay of more than 500ms is not
  506:  * compensated with a corresponding fudge time2 value, because then
  507:  * the next PPS edge is nearer than the last. (Similiar to what the
  508:  * PPS ATOM driver does, but we deal with full time stamps here, not
  509:  * just phase shift information.) Likewise, a negative fudge time2
  510:  * value must be used if the reference time stamp correlates with the
  511:  * *following* PPS pulse.
  512:  *
  513:  * Note that the receive time fudge value only needs to move the receive
  514:  * stamp near a PPS edge but that close proximity is not required;
  515:  * +/-100ms precision should be enough. But since the fudge value will
  516:  * probably also be used to compensate the transmission delay when no PPS
  517:  * edge can be related to the time stamp, it's best to get it as close
  518:  * as possible.
  519:  *
  520:  * It should also be noted that the typical use case is matching to
  521:  * the preceeding edge, as most units relate their sentences to the
  522:  * current second.
  523:  *
  524:  * The function returns PPS_RELATE_NONE (0) if no PPS edge correlation
  525:  * can be fixed; PPS_RELATE_EDGE (1) when a PPS edge could be fixed, but
  526:  * the distance to the reference time stamp is too big (exceeds +/-400ms)
  527:  * and the ATOM driver PLL cannot be used to fix the phase; and
  528:  * PPS_RELATE_PHASE (2) when the ATOM driver PLL code can be used.
  529:  *
  530:  * On output, the receive time stamp is replaced with the
  531:  * corresponding PPS edge time if a fix could be made; the PPS fudge
  532:  * is updated to reflect the proper fudge time to apply. (This implies
  533:  * that 'refclock_process_f()' must be used!)
  534:  */
  535: #define PPS_RELATE_NONE	 0	/* no pps correlation possible	  */
  536: #define PPS_RELATE_EDGE	 1	/* recv time fixed, no phase lock */
  537: #define PPS_RELATE_PHASE 2	/* recv time fixed, phase lock ok */
  538: 
  539: static int
  540: refclock_ppsrelate(
  541: 	const struct refclockproc  *pp	    ,	/* for sanity	  */
  542: 	const struct refclock_atom *ap	    ,	/* for PPS io	  */
  543: 	const l_fp		   *reftime ,
  544: 	l_fp			   *rd_stamp,	/* i/o read stamp */
  545: 	double			    pp_fudge,	/* pps fudge	  */
  546: 	double			   *rd_fudge)	/* i/o read fudge */
  547: {
  548: 	pps_info_t	pps_info;
  549: 	struct timespec timeout;
  550: 	l_fp		pp_stamp, pp_delta;
  551: 	double		delta, idelta;
  552: 
  553: 	if (pp->leap == LEAP_NOTINSYNC)
  554: 		return PPS_RELATE_NONE;	/* clock is insane, no chance */
  555: 	
  556: 	memset(&timeout, 0, sizeof(timeout));
  557: 	memset(&pps_info, 0, sizeof(pps_info_t));
  558: 
  559: 	if (time_pps_fetch(ap->handle, PPS_TSFMT_TSPEC,
  560: 			   &pps_info, &timeout) < 0)
  561: 		return PPS_RELATE_NONE;
  562: 
  563: 	/* get last active PPS edge before receive */
  564: 	if (ap->pps_params.mode & PPS_CAPTUREASSERT)
  565: 		timeout = pps_info.assert_timestamp;
  566: 	else if (ap->pps_params.mode & PPS_CAPTURECLEAR)
  567: 		timeout = pps_info.clear_timestamp;
  568: 	else
  569: 		return PPS_RELATE_NONE;
  570: 
  571: 	/* get delta between receive time and PPS time */
  572: 	TIMESPECTOTS(&timeout, &pp_stamp);
  573: 	pp_delta = *rd_stamp;
  574: 	L_SUB(&pp_delta, &pp_stamp);
  575: 	LFPTOD(&pp_delta, delta);
  576: 	delta += pp_fudge - *rd_fudge;
  577: 	if (fabs(delta) > 1.5)
  578: 		return PPS_RELATE_NONE; /* PPS timeout control */
  579: 	
  580: 	/* eventually warp edges, check phase */
  581: 	idelta	  = floor(delta + 0.5);
  582: 	pp_fudge -= idelta;
  583: 	delta	 -= idelta;
  584: 	if (fabs(delta) > 0.45)
  585: 		return PPS_RELATE_NONE; /* dead band control */
  586: 
  587: 	/* we actually have a PPS edge to relate with! */
  588: 	*rd_stamp = pp_stamp;
  589: 	*rd_fudge = pp_fudge;
  590: 
  591: 	/* if whole system out-of-sync, do not try to PLL */
  592: 	if (sys_leap == LEAP_NOTINSYNC)
  593: 		return PPS_RELATE_EDGE;	/* cannot PLL with atom code */
  594: 
  595: 	/* check against reftime if ATOM PLL can be used */
  596: 	pp_delta = *reftime;
  597: 	L_SUB(&pp_delta, &pp_stamp);
  598: 	LFPTOD(&pp_delta, delta);
  599: 	delta += pp_fudge;
  600: 	if (fabs(delta) > 0.45)
  601: 		return PPS_RELATE_EDGE;	/* cannot PLL with atom code */
  602: 
  603: 	/* all checks passed, gets an AAA rating here! */
  604: 	return PPS_RELATE_PHASE; /* can PLL with atom code */
  605: }
  606: #endif	/* HAVE_PPSAPI */
  607: 
  608: /*
  609:  * nmea_receive - receive data from the serial interface
  610:  */
  611: static void
  612: nmea_receive(
  613: 	struct recvbuf *rbufp
  614: 	)
  615: {
  616: 	register struct nmeaunit *up;
  617: 	struct refclockproc *pp;
  618: 	struct peer *peer;
  619: 	char *cp, *dp, *msg;
  620: 	u_char sentence;
  621: 	/* Use these variables to hold data until we decide its worth
  622: 	 * keeping */
  623: 	char	rd_lastcode[BMAX];
  624: 	l_fp	rd_timestamp, reftime;
  625: 	int	rd_lencode;
  626: 	double	rd_fudge;
  627: 	struct calendar date;
  628: 
  629: 	/*
  630: 	 * Initialize pointers and read the timecode and timestamp
  631: 	 */
  632: 	peer = rbufp->recv_peer;
  633: 	pp = peer->procptr;
  634: 	up = (struct nmeaunit *)pp->unitptr;
  635: 
  636: 	rd_lencode = refclock_gtlin(
  637: 			rbufp, 
  638: 			rd_lastcode, 
  639: 			sizeof(rd_lastcode), 
  640: 			&rd_timestamp);
  641: 
  642: 	/*
  643: 	 * There is a case that a <CR><LF> gives back a "blank" line.
  644: 	 * We can't have a well-formed sentence with less than 8 chars.
  645: 	 */
  646: 	if (0 == rd_lencode)
  647: 		return;
  648: 
  649: 	if (rd_lencode < 8) {
  650: 		refclock_report(peer, CEVNT_BADREPLY);
  651: 		return;
  652: 	}
  653: 
  654: 	DPRINTF(1, ("nmea: gpsread %d %s\n", rd_lencode, rd_lastcode));
  655: 
  656: 	/*
  657: 	 * We check the timecode format and decode its contents. The
  658: 	 * we only care about a few of them.  The most important being
  659: 	 * the $GPRMC format
  660: 	 * $GPRMC,hhmmss,a,fddmm.xx,n,dddmmm.xx,w,zz.z,yyy.,ddmmyy,dd,v*CC
  661: 	 * mode (0,1,2,3) selects sentence ANY/ALL, RMC, GGA, GLL, ZDA
  662: 	 * $GPGLL,3513.8385,S,14900.7851,E,232420.594,A*21
  663: 	 * $GPGGA,232420.59,3513.8385,S,14900.7851,E,1,05,3.4,00519,M,,,,*3F
  664: 	 * $GPRMC,232418.19,A,3513.8386,S,14900.7853,E,00.0,000.0,121199,12.,E*77
  665: 	 *
  666: 	 * Defining GPZDA to support Standard Time & Date
  667: 	 * sentence. The sentence has the following format 
  668: 	 *  
  669: 	 *  $--ZDA,HHMMSS.SS,DD,MM,YYYY,TH,TM,*CS<CR><LF>
  670: 	 *
  671: 	 *  Apart from the familiar fields, 
  672: 	 *  'TH'    Time zone Hours
  673: 	 *  'TM'    Time zone Minutes
  674: 	 *
  675: 	 * Defining GPZDG to support Accord GPS Clock's custom NMEA 
  676: 	 * sentence. The sentence has the following format 
  677: 	 *  
  678: 	 *  $GPZDG,HHMMSS.S,DD,MM,YYYY,AA.BB,V*CS<CR><LF>
  679: 	 *
  680: 	 *  It contains the GPS timestamp valid for next PPS pulse.
  681: 	 *  Apart from the familiar fields, 
  682: 	 *  'AA.BB' denotes the signal strength( should be < 05.00 ) 
  683: 	 *  'V'	    denotes the GPS sync status : 
  684: 	 *	   '0' indicates INVALID time, 
  685: 	 *	   '1' indicates accuracy of +/-20 ms
  686: 	 *	   '2' indicates accuracy of +/-100 ns
  687: 	 */
  688: 
  689: 	cp = rd_lastcode;
  690: 	if (cp[0] == '$') {
  691: 		/* Allow for GLGGA and GPGGA etc. */
  692: 		msg = cp + 3;
  693: 
  694: 		if (strncmp(msg, "RMC", 3) == 0)
  695: 			sentence = NMEA_GPRMC;
  696: 		else if (strncmp(msg, "GGA", 3) == 0)
  697: 			sentence = NMEA_GPGGA;
  698: 		else if (strncmp(msg, "GLL", 3) == 0)
  699: 			sentence = NMEA_GPGLL;
  700: 		else if (strncmp(msg, "ZDG", 3) == 0)
  701: 			sentence = NMEA_GPZDG;
  702: 		else if (strncmp(msg, "ZDA", 3) == 0)
  703: 			sentence = NMEA_GPZDA;
  704: 		else
  705: 			return;
  706: 	} else
  707: 		return;
  708: 
  709: 	/* See if I want to process this message type */
  710: 	if ((peer->ttl & NMEA_MESSAGE_MASK) &&
  711: 	   !(peer->ttl & sentence_mode[sentence]))
  712: 		return;
  713: 
  714: 	/* 
  715: 	 * $GPZDG provides GPS time not UTC, and the two mix poorly.
  716: 	 * Once have processed a $GPZDG, do not process any further
  717: 	 * UTC sentences (all but $GPZDG currently).
  718: 	 */
  719: 	if (up->gps_time && NMEA_GPZDG != sentence)
  720: 		return;
  721: 
  722: 	/*
  723: 	 * Apparently, older NMEA specifications (which are expensive)
  724: 	 * did not require the checksum for all sentences.  $GPMRC is
  725: 	 * the only one so far identified which has always been required
  726: 	 * to include a checksum.
  727: 	 *
  728: 	 * Today, most NMEA GPS receivers checksum every sentence.  To
  729: 	 * preserve its error-detection capabilities with modern GPSes
  730: 	 * while allowing operation without checksums on all but $GPMRC,
  731: 	 * we keep track of whether we've ever seen a checksum on a
  732: 	 * given sentence, and if so, reject future checksum failures.
  733: 	 */
  734: 	if (nmea_checksum_ok(rd_lastcode)) {
  735: 		up->cksum_seen[sentence] = TRUE;
  736: 	} else if (NMEA_GPRMC == sentence || up->cksum_seen[sentence]) {
  737: 		refclock_report(peer, CEVNT_BADREPLY);
  738: 		return;
  739: 	}
  740: 
  741: 	cp = rd_lastcode;
  742: 
  743: 	/* Grab field depending on clock string type */
  744: 	memset(&date, 0, sizeof(date));
  745: 	switch (sentence) {
  746: 
  747: 	case NMEA_GPRMC:
  748: 		/*
  749: 		 * Test for synchronization.  Check for quality byte.
  750: 		 */
  751: 		dp = field_parse(cp, 2);
  752: 		if (dp[0] != 'A')
  753: 			pp->leap = LEAP_NOTINSYNC;
  754: 		else
  755: 			pp->leap = LEAP_NOWARNING;
  756: 
  757: 		/* Now point at the time field */
  758: 		dp = field_parse(cp, 1);
  759: 		break;
  760: 
  761: 	case NMEA_GPGGA:
  762: 		/*
  763: 		 * Test for synchronization.  Check for quality byte.
  764: 		 */
  765: 		dp = field_parse(cp, 6);
  766: 		if (dp[0] == '0')
  767: 			pp->leap = LEAP_NOTINSYNC;
  768: 		else
  769: 			pp->leap = LEAP_NOWARNING;
  770: 
  771: 		/* Now point at the time field */
  772: 		dp = field_parse(cp, 1);
  773: 		break;
  774: 
  775: 	case NMEA_GPGLL:
  776: 		/*
  777: 		 * Test for synchronization.  Check for quality byte.
  778: 		 */
  779: 		dp = field_parse(cp, 6);
  780: 		if (dp[0] != 'A')
  781: 			pp->leap = LEAP_NOTINSYNC;
  782: 		else
  783: 			pp->leap = LEAP_NOWARNING;
  784: 
  785: 		/* Now point at the time field */
  786: 		dp = field_parse(cp, 5);
  787: 		break;
  788: 	
  789: 	case NMEA_GPZDG:
  790: 		/* For $GPZDG check for validity of GPS time. */
  791: 		dp = field_parse(cp, 6);
  792: 		if (dp[0] == '0') 
  793: 			pp->leap = LEAP_NOTINSYNC;
  794: 		else 
  795: 			pp->leap = LEAP_NOWARNING;
  796: 		/* fall through to NMEA_GPZDA */
  797: 
  798: 	case NMEA_GPZDA:
  799: 		if (NMEA_GPZDA == sentence)
  800: 			pp->leap = LEAP_NOWARNING;
  801: 
  802: 		/* Now point at the time field */
  803: 		dp = field_parse(cp, 1);
  804: 		break;
  805: 
  806: 	default:
  807: 		return;
  808: 	}
  809: 
  810: 	/*
  811: 	 * Check time code format of NMEA
  812: 	 */
  813: 	if (!isdigit((int)dp[0]) ||
  814: 	    !isdigit((int)dp[1]) ||
  815: 	    !isdigit((int)dp[2]) ||
  816: 	    !isdigit((int)dp[3]) ||
  817: 	    !isdigit((int)dp[4]) ||
  818: 	    !isdigit((int)dp[5])) {
  819: 
  820: 		DPRINTF(1, ("NMEA time code %c%c%c%c%c%c non-numeric",
  821: 			    dp[0], dp[1], dp[2], dp[3], dp[4], dp[5]));
  822: 		refclock_report(peer, CEVNT_BADTIME);
  823: 		return;
  824: 	}
  825: 
  826: 	/*
  827: 	 * Convert time and check values.
  828: 	 */
  829: 	date.hour = ((dp[0] - '0') * 10) + dp[1] - '0';
  830: 	date.minute = ((dp[2] - '0') * 10) + dp[3] -  '0';
  831: 	date.second = ((dp[4] - '0') * 10) + dp[5] - '0';
  832: 	/* 
  833: 	 * Default to 0 milliseconds, if decimal convert milliseconds in
  834: 	 * one, two or three digits
  835: 	 */
  836: 	pp->nsec = 0; 
  837: 	if (dp[6] == '.') {
  838: 		if (isdigit((int)dp[7])) {
  839: 			pp->nsec = (dp[7] - '0') * 100000000;
  840: 			if (isdigit((int)dp[8])) {
  841: 				pp->nsec += (dp[8] - '0') * 10000000;
  842: 				if (isdigit((int)dp[9])) {
  843: 					pp->nsec += (dp[9] - '0') * 1000000;
  844: 				}
  845: 			}
  846: 		}
  847: 	}
  848: 
  849: 	if (date.hour > 23 || date.minute > 59 || 
  850: 	    date.second > 59 || pp->nsec > 1000000000) {
  851: 
  852: 		DPRINTF(1, ("NMEA hour/min/sec/nsec range %02d:%02d:%02d.%09ld\n",
  853: 			    pp->hour, pp->minute, pp->second, pp->nsec));
  854: 		refclock_report(peer, CEVNT_BADTIME);
  855: 		return;
  856: 	}
  857: 
  858: 	/*
  859: 	 * Used only the first recognized sentence each second.
  860: 	 */
  861: 	if (date.hour   == up->used.hour   &&
  862: 	    date.minute == up->used.minute &&
  863: 	    date.second == up->used.second)
  864: 		return;
  865: 
  866: 	pp->lencode = (u_short)rd_lencode;
  867: 	memcpy(pp->a_lastcode, rd_lastcode, pp->lencode + 1);
  868: 	up->tstamp = rd_timestamp;
  869: 	pp->lastrec = up->tstamp;
  870: 	DPRINTF(1, ("nmea: timecode %d %s\n", pp->lencode, pp->a_lastcode));
  871: 
  872: 	/*
  873: 	 * Convert date and check values.
  874: 	 */
  875: 	if (NMEA_GPRMC == sentence) {
  876: 
  877: 		dp = field_parse(cp,9);
  878: 		date.monthday = 10 * (dp[0] - '0') + (dp[1] - '0');
  879: 		date.month    = 10 * (dp[2] - '0') + (dp[3] - '0');
  880: 		date.year     = 10 * (dp[4] - '0') + (dp[5] - '0');
  881: 		nmea_century_unfold(&date);
  882: 
  883: 	} else if (NMEA_GPZDA == sentence || NMEA_GPZDG == sentence) {
  884: 
  885: 		dp = field_parse(cp, 2);
  886: 		date.monthday = 10 * (dp[0] - '0') + (dp[1] - '0');
  887: 		dp = field_parse(cp, 3);
  888: 		date.month = 10 * (dp[0] - '0') + (dp[1] - '0');
  889: 		dp = field_parse(cp, 4);
  890: 		date.year = 1000 * (dp[0] - '0') + 100 * (dp[1] - '0')
  891: 			  + 10 * (dp[2] - '0') + (dp[3] - '0');
  892: 
  893: 	} else
  894: 		nmea_day_unfold(&date);
  895: 
  896: 	if (date.month < 1 || date.month > 12 ||
  897: 	    date.monthday < 1 || date.monthday > 31) {
  898: 		refclock_report(peer, CEVNT_BADDATE);
  899: 		return;
  900: 	}
  901: 
  902: 	up->used.hour = date.hour;
  903: 	up->used.minute = date.minute;
  904: 	up->used.second = date.second;
  905: 
  906: 	/*
  907: 	 * If "fudge 127.127.20.__ flag4 1" is configured in ntp.conf,
  908: 	 * remove the location and checksum from the NMEA sentence
  909: 	 * recorded as the last timecode and visible to remote users
  910: 	 * with:
  911: 	 *
  912: 	 * ntpq -c clockvar <server>
  913: 	 *
  914: 	 * Note that this also removes the location from the clockstats
  915: 	 * log (if it is enabled).  Some NTP operators monitor their
  916: 	 * NMEA GPS using the change in location in clockstats over
  917: 	 * time as as a proxy for the quality of GPS reception and
  918: 	 * thereby time reported.
  919: 	 */
  920: 	if (CLK_FLAG4 & pp->sloppyclockflag) {
  921: 		/*
  922: 		 * Start by pointing cp and dp at the fields with 
  923: 		 * longitude and latitude in the last timecode.
  924: 		 */
  925: 		switch (sentence) {
  926: 
  927: 		case NMEA_GPGLL:
  928: 			cp = field_parse(pp->a_lastcode, 1);
  929: 			dp = field_parse(cp, 2);
  930: 			break;
  931: 
  932: 		case NMEA_GPGGA:
  933: 			cp = field_parse(pp->a_lastcode, 2);
  934: 			dp = field_parse(cp, 2);
  935: 			break;
  936: 
  937: 		case NMEA_GPRMC:
  938: 			cp = field_parse(pp->a_lastcode, 3);
  939: 			dp = field_parse(cp, 2);
  940: 			break;
  941: 
  942: 		case NMEA_GPZDA:
  943: 		case NMEA_GPZDG:
  944: 		default:
  945: 			cp = dp = NULL;
  946: 		}
  947: 
  948: 		/* Blank the entire latitude & longitude. */
  949: 		while (cp) {
  950: 			while (',' != *cp) {
  951: 				if ('.' != *cp)
  952: 					*cp = '_';
  953: 				cp++;
  954: 			}
  955: 
  956: 			/* Longitude at cp then latitude at dp */
  957: 			if (cp < dp)
  958: 				cp = dp;
  959: 			else
  960: 				cp = NULL;
  961: 		}
  962: 
  963: 		/* Blank the checksum, the last two characters */
  964: 		if (dp) {
  965: 			cp = pp->a_lastcode + pp->lencode - 2;
  966: 			if (0 == cp[2])
  967: 				cp[0] = cp[1] = '_';
  968: 		}
  969: 
  970: 	}
  971: 
  972: 	/*
  973: 	 * Get the reference time stamp from the calendar buffer.
  974: 	 * Process the new sample in the median filter and determine
  975: 	 * the timecode timestamp, but only if the PPS is not in
  976: 	 * control.
  977: 	 */
  978: 	rd_fudge = pp->fudgetime2;
  979: 	date.yearday = 0; /* make sure it's not used */
  980: 	DTOLFP(pp->nsec * 1.0e-9, &reftime);
  981: 	reftime.l_ui += caltontp(&date);
  982: 
  983: 	/* $GPZDG postprocessing first... */
  984: 	if (NMEA_GPZDG == sentence) {
  985: 		/*
  986: 		 * Note if we're only using GPS timescale from now on.
  987: 		 */
  988: 		if (!up->gps_time) {
  989: 			up->gps_time = 1;
  990: 			NLOG(NLOG_CLOCKINFO)
  991: 			msyslog(LOG_INFO, "%s using only $GPZDG",
  992: 				refnumtoa(&peer->srcadr));
  993: 		}
  994: 		/*
  995: 		 * $GPZDG indicates the second after the *next* PPS
  996: 		 * pulse. So we remove 1 second from the reference
  997: 		 * time now.
  998: 		 */
  999: 		reftime.l_ui--;
 1000: 	}
 1001: 
 1002: #ifdef HAVE_PPSAPI
 1003: 	up->tcount++;
 1004: 	/*
 1005: 	 * If we have PPS running, we try to associate the sentence with
 1006: 	 * the last active edge of the PPS signal.
 1007: 	 */
 1008: 	if (up->ppsapi_lit)
 1009: 		switch (refclock_ppsrelate(pp, &up->atom, &reftime,
 1010: 					  &rd_timestamp, pp->fudgetime1,
 1011: 					  &rd_fudge))
 1012: 		{
 1013: 		case PPS_RELATE_EDGE:
 1014: 			up->ppsapi_gate = 0;
 1015: 			break;
 1016: 		case PPS_RELATE_PHASE:
 1017: 			up->ppsapi_gate = 1;
 1018: 			break;
 1019: 		default:
 1020: 			break;
 1021: 		}
 1022: 	else 
 1023: 		up->ppsapi_gate = 0;
 1024: 
 1025: 	if (up->ppsapi_gate && (peer->flags & FLAG_PPS))
 1026: 		return;
 1027: #endif /* HAVE_PPSAPI */
 1028: 
 1029: 	refclock_process_offset(pp, reftime, rd_timestamp, rd_fudge);
 1030: }
 1031: 
 1032: 
 1033: /*
 1034:  * nmea_poll - called by the transmit procedure
 1035:  *
 1036:  * We go to great pains to avoid changing state here, since there may be
 1037:  * more than one eavesdropper receiving the same timecode.
 1038:  */
 1039: static void
 1040: nmea_poll(
 1041: 	int unit,
 1042: 	struct peer *peer
 1043: 	)
 1044: {
 1045: 	register struct nmeaunit *up;
 1046: 	struct refclockproc *pp;
 1047: 
 1048: 	pp = peer->procptr;
 1049: 	up = (struct nmeaunit *)pp->unitptr;
 1050: 
 1051: 	/*
 1052: 	 * Process median filter samples. If none received, declare a
 1053: 	 * timeout and keep going.
 1054: 	 */
 1055: #ifdef HAVE_PPSAPI
 1056: 	if (up->pcount == 0) {
 1057: 		peer->flags &= ~FLAG_PPS;
 1058: 		peer->precision = PRECISION;
 1059: 	}
 1060: 	if (up->tcount == 0) {
 1061: 		pp->coderecv = pp->codeproc;
 1062: 		refclock_report(peer, CEVNT_TIMEOUT);
 1063: 		return;
 1064: 	}
 1065: 	up->pcount = up->tcount = 0;
 1066: #else /* HAVE_PPSAPI */
 1067: 	if (pp->coderecv == pp->codeproc) {
 1068: 		refclock_report(peer, CEVNT_TIMEOUT);
 1069: 		return;
 1070: 	}
 1071: #endif /* HAVE_PPSAPI */
 1072: 
 1073: 	pp->polls++;
 1074: 	pp->lastref = pp->lastrec;
 1075: 	refclock_receive(peer);
 1076: 	record_clock_stats(&peer->srcadr, pp->a_lastcode);
 1077: 
 1078: 	/*
 1079: 	 * usually nmea_receive can get a timestamp every second, 
 1080: 	 * but at least one Motorola unit needs prompting each
 1081: 	 * time.
 1082: 	 */
 1083: 
 1084: 	gps_send(pp->io.fd,"$PMOTG,RMC,0000*1D\r\n", peer);
 1085: }
 1086: 
 1087: 
 1088: /*
 1089:  *
 1090:  *	gps_send(fd,cmd, peer)	Sends a command to the GPS receiver.
 1091:  *	 as	gps_send(fd,"rqts,u\r", peer);
 1092:  *
 1093:  *	We don't currently send any data, but would like to send
 1094:  *	RTCM SC104 messages for differential positioning. It should
 1095:  *	also give us better time. Without a PPS output, we're
 1096:  *	Just fooling ourselves because of the serial code paths
 1097:  *
 1098:  */
 1099: static void
 1100: gps_send(
 1101: 	int fd,
 1102: 	const char *cmd,
 1103: 	struct peer *peer
 1104: 	)
 1105: {
 1106: 	if (write(fd, cmd, strlen(cmd)) == -1) {
 1107: 		refclock_report(peer, CEVNT_FAULT);
 1108: 	}
 1109: }
 1110: 
 1111: 
 1112: static char *
 1113: field_parse(
 1114: 	char *cp,
 1115: 	int fn
 1116: 	)
 1117: {
 1118: 	char *tp;
 1119: 	int i = fn;
 1120: 
 1121: 	for (tp = cp; i && *tp; tp++)
 1122: 		if (*tp == ',')
 1123: 			i--;
 1124: 
 1125: 	return tp;
 1126: }
 1127: 
 1128: 
 1129: /*
 1130:  * nmea_checksum_ok verifies 8-bit XOR checksum is correct then returns 1
 1131:  *
 1132:  * format is $XXXXX,1,2,3,4*ML
 1133:  *
 1134:  * 8-bit XOR of characters between $ and * noninclusive is transmitted
 1135:  * in last two chars M and L holding most and least significant nibbles
 1136:  * in hex representation such as:
 1137:  *
 1138:  *   $GPGLL,5057.970,N,00146.110,E,142451,A*27
 1139:  *   $GPVTG,089.0,T,,,15.2,N,,*7F
 1140:  */
 1141: int
 1142: nmea_checksum_ok(
 1143: 	const char *sentence
 1144: 	)
 1145: {
 1146: 	u_char my_cs;
 1147: 	u_long input_cs;
 1148: 	const char *p;
 1149: 
 1150: 	my_cs = 0;
 1151: 	p = sentence;
 1152: 
 1153: 	if ('$' != *p++)
 1154: 		return 0;
 1155: 
 1156: 	for ( ; *p && '*' != *p; p++) {
 1157: 
 1158: 		my_cs ^= *p;
 1159: 	}
 1160: 
 1161: 	if ('*' != *p++)
 1162: 		return 0;
 1163: 
 1164: 	if (0 == p[0] || 0 == p[1] || 0 != p[2])
 1165: 		return 0;
 1166: 
 1167: 	if (0 == hextoint(p, &input_cs))
 1168: 		return 0;
 1169: 
 1170: 	if (my_cs != input_cs)
 1171: 		return 0;
 1172: 
 1173: 	return 1;
 1174: }
 1175: 
 1176: /*
 1177:  * -------------------------------------------------------------------
 1178:  * funny calendar-oriented stuff -- a bit hard to grok.
 1179:  * -------------------------------------------------------------------
 1180:  */
 1181: /*
 1182:  * Do a periodic unfolding of a truncated value around a given pivot
 1183:  * value.
 1184:  * The result r will hold to pivot <= r < pivot+period (period>0) or
 1185:  * pivot+period < r <= pivot (period < 0) and value % period == r % period,
 1186:  * using floor division convention.
 1187:  */
 1188: static time_t
 1189: nmea_periodic_unfold(
 1190: 	time_t pivot,
 1191: 	time_t value,
 1192: 	time_t period)
 1193: {
 1194: 	/*
 1195: 	 * This will only work as long as 'value - pivot%period' does
 1196: 	 * not create a signed overflow condition.
 1197: 	 */
 1198: 	value = (value - (pivot % period)) % period;
 1199: 	if (value && (value ^ period) < 0)
 1200: 		value += period;
 1201: 	return pivot + value;
 1202: }
 1203: 
 1204: /*
 1205:  * Unfold a time-of-day (seconds since midnight) around the current
 1206:  * system time in a manner that guarantees an absolute difference of
 1207:  * less than 12hrs.
 1208:  *
 1209:  * This function is used for NMEA sentences that contain no date
 1210:  * information. This requires the system clock to be in +/-12hrs
 1211:  * around the true time, or the clock will synchronize the system 1day
 1212:  * off if not augmented with a time sources that also provide the
 1213:  * necessary date information.
 1214:  *
 1215:  * The function updates the refclockproc structure is also uses as
 1216:  * input to fetch the time from.
 1217:  */
 1218: static void
 1219: nmea_day_unfold(
 1220: 	struct calendar *jd)
 1221: {
 1222: 	time_t value, pivot;
 1223: 	struct tm *tdate;
 1224: 
 1225: 	value = ((time_t)jd->hour * MINSPERHR
 1226: 		 + (time_t)jd->minute) * SECSPERMIN
 1227: 		  + (time_t)jd->second;
 1228: 	pivot = time(NULL) - SECSPERDAY/2;
 1229: 
 1230: 	value = nmea_periodic_unfold(pivot, value, SECSPERDAY);
 1231: 	tdate = gmtime(&value);
 1232: 	if (tdate) {
 1233: 		jd->year     = tdate->tm_year + 1900;
 1234: 		jd->yearday  = tdate->tm_yday + 1;
 1235: 		jd->month    = tdate->tm_mon + 1;
 1236: 		jd->monthday = tdate->tm_mday;
 1237: 		jd->hour     = tdate->tm_hour;
 1238: 		jd->minute   = tdate->tm_min;
 1239: 		jd->second   = tdate->tm_sec;
 1240: 	} else {
 1241: 		jd->year     = 0;
 1242: 		jd->yearday  = 0;
 1243: 		jd->month    = 0;
 1244: 		jd->monthday = 0;
 1245: 	}
 1246: }
 1247: 
 1248: /*
 1249:  * Unfold a 2-digit year into full year spec around the current year
 1250:  * of the system time. This requires the system clock to be in -79/+19
 1251:  * years around the true time, or the result will be off by
 1252:  * 100years. The assymetric behaviour was chosen to enable inital sync
 1253:  * for systems that do not have a battery-backup-clock and start with
 1254:  * a date that is typically years in the past.
 1255:  *
 1256:  * The function updates the calendar structure that is also used as
 1257:  * input to fetch the year from.
 1258:  */
 1259: static void
 1260: nmea_century_unfold(
 1261: 	struct calendar *jd)
 1262: {
 1263: 	time_t	   pivot_time;
 1264: 	struct tm *pivot_date;
 1265: 	time_t	   pivot_year;
 1266: 
 1267: 	/* get warp limit and century start of pivot from system time */
 1268: 	pivot_time = time(NULL);
 1269: 	pivot_date = gmtime(&pivot_time);
 1270: 	pivot_year = pivot_date->tm_year + 1900 - 20;
 1271: 	jd->year = nmea_periodic_unfold(pivot_year, jd->year, 100);
 1272: }
 1273: 
 1274: #else
 1275: int refclock_nmea_bs;
 1276: #endif /* REFCLOCK && CLOCK_NMEA */

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>