xref: /freebsd/contrib/ntp/ntpd/refclock_nmea.c (revision 3ff369fed2a08f32dda232c10470b949bef9489f)
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 #ifdef HAVE_CONFIG_H
7 #include <config.h>
8 #endif
9 
10 #if defined(REFCLOCK) && defined(CLOCK_NMEA)
11 
12 #include "ntpd.h"
13 #include "ntp_io.h"
14 #include "ntp_unixtime.h"
15 #include "ntp_refclock.h"
16 #include "ntp_stdlib.h"
17 
18 #include <stdio.h>
19 #include <ctype.h>
20 
21 #ifdef HAVE_PPSAPI
22 # ifdef HAVE_TIMEPPS_H
23 #  include <timepps.h>
24 # else
25 #  ifdef HAVE_SYS_TIMEPPS_H
26 #   include <sys/timepps.h>
27 #  endif
28 # endif
29 #endif /* HAVE_PPSAPI */
30 
31 /*
32  * This driver supports the NMEA GPS Receiver with
33  *
34  * Protype was refclock_trak.c, Thanks a lot.
35  *
36  * The receiver used spits out the NMEA sentences for boat navigation.
37  * And you thought it was an information superhighway.  Try a raging river
38  * filled with rapids and whirlpools that rip away your data and warp time.
39  *
40  * If HAVE_PPSAPI is defined code to use the PPSAPI will be compiled in.
41  * On startup if initialization of the PPSAPI fails, it will fall back
42  * to the "normal" timestamps.
43  *
44  * The PPSAPI part of the driver understands fudge flag2 and flag3. If
45  * flag2 is set, it will use the clear edge of the pulse. If flag3 is
46  * set, kernel hardpps is enabled.
47  *
48  * GPS sentences other than RMC (the default) may be enabled by setting
49  * the relevent bits of 'mode' in the server configuration line
50  * server 127.127.20.x mode X
51  *
52  * bit 0 - enables RMC (1)
53  * bit 1 - enables GGA (2)
54  * bit 2 - enables GLL (4)
55  * multiple sentences may be selected
56  */
57 
58 /*
59  * Definitions
60  */
61 #ifdef SYS_WINNT
62 # define DEVICE "COM%d:" 	/* COM 1 - 3 supported */
63 #else
64 # define DEVICE	"/dev/gps%d"	/* name of radio device */
65 #endif
66 #define	SPEED232	B4800	/* uart speed (4800 bps) */
67 #define	PRECISION	(-9)	/* precision assumed (about 2 ms) */
68 #define	PPS_PRECISION	(-20)	/* precision assumed (about 1 us) */
69 #define	REFID		"GPS\0"	/* reference id */
70 #define	DESCRIPTION	"NMEA GPS Clock" /* who we are */
71 #define NANOSECOND	1000000000 /* one second (ns) */
72 #define RANGEGATE	500000	/* range gate (ns) */
73 
74 #define LENNMEA		75	/* min timecode length */
75 
76 /*
77  * Tables to compute the ddd of year form icky dd/mm timecode. Viva la
78  * leap.
79  */
80 static int day1tab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
81 static int day2tab[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
82 
83 /*
84  * Unit control structure
85  */
86 struct nmeaunit {
87 	int	pollcnt;	/* poll message counter */
88 	int	polled;		/* Hand in a sample? */
89 	l_fp	tstamp;		/* timestamp of last poll */
90 #ifdef HAVE_PPSAPI
91 	struct timespec ts;	/* last timestamp */
92 	pps_params_t pps_params; /* pps parameters */
93 	pps_info_t pps_info;	/* last pps data */
94 	pps_handle_t handle;	/* pps handlebars */
95 #endif /* HAVE_PPSAPI */
96 };
97 
98 /*
99  * Function prototypes
100  */
101 static	int	nmea_start	P((int, struct peer *));
102 static	void	nmea_shutdown	P((int, struct peer *));
103 #ifdef HAVE_PPSAPI
104 static	void	nmea_control	P((int, struct refclockstat *, struct
105 				    refclockstat *, struct peer *));
106 static	int	nmea_ppsapi	P((struct peer *, int, int));
107 static	int	nmea_pps	P((struct nmeaunit *, l_fp *));
108 #endif /* HAVE_PPSAPI */
109 static	void	nmea_receive	P((struct recvbuf *));
110 static	void	nmea_poll	P((int, struct peer *));
111 static	void	gps_send	P((int, const char *, struct peer *));
112 static	char	*field_parse	P((char *, int));
113 
114 /*
115  * Transfer vector
116  */
117 struct	refclock refclock_nmea = {
118 	nmea_start,		/* start up driver */
119 	nmea_shutdown,	/* shut down driver */
120 	nmea_poll,		/* transmit poll message */
121 #ifdef HAVE_PPSAPI
122 	nmea_control,		/* fudge control */
123 #else
124 	noentry,		/* fudge control */
125 #endif /* HAVE_PPSAPI */
126 	noentry,		/* initialize driver */
127 	noentry,		/* buginfo */
128 	NOFLAGS			/* not used */
129 };
130 
131 /*
132  * nmea_start - open the GPS devices and initialize data for processing
133  */
134 static int
135 nmea_start(
136 	int unit,
137 	struct peer *peer
138 	)
139 {
140 	register struct nmeaunit *up;
141 	struct refclockproc *pp;
142 	int fd;
143 	char device[20];
144 
145 	/*
146 	 * Open serial port. Use CLK line discipline, if available.
147 	 */
148 	(void)sprintf(device, DEVICE, unit);
149 
150 	if (!(fd = refclock_open(device, SPEED232, LDISC_CLK)))
151 	    return (0);
152 
153 	/*
154 	 * Allocate and initialize unit structure
155 	 */
156 	if (!(up = (struct nmeaunit *)
157 	      emalloc(sizeof(struct nmeaunit)))) {
158 		(void) close(fd);
159 		return (0);
160 	}
161 	memset((char *)up, 0, sizeof(struct nmeaunit));
162 	pp = peer->procptr;
163 	pp->io.clock_recv = nmea_receive;
164 	pp->io.srcclock = (caddr_t)peer;
165 	pp->io.datalen = 0;
166 	pp->io.fd = fd;
167 	if (!io_addclock(&pp->io)) {
168 		(void) close(fd);
169 		free(up);
170 		return (0);
171 	}
172 	pp->unitptr = (caddr_t)up;
173 
174 	/*
175 	 * Initialize miscellaneous variables
176 	 */
177 	peer->precision = PRECISION;
178 	pp->clockdesc = DESCRIPTION;
179 	memcpy((char *)&pp->refid, REFID, 4);
180 	up->pollcnt = 2;
181 	gps_send(pp->io.fd,"$PMOTG,RMC,0000*1D\r\n", peer);
182 
183 #ifdef HAVE_PPSAPI
184 	/*
185 	 * Start the PPSAPI interface if it is there. Default to use
186 	 * the assert edge and do not enable the kernel hardpps.
187 	 */
188 	if (time_pps_create(fd, &up->handle) < 0) {
189 		up->handle = 0;
190 		msyslog(LOG_ERR,
191 		    "refclock_nmea: time_pps_create failed: %m");
192 		return (1);
193 	}
194 	return(nmea_ppsapi(peer, 0, 0));
195 #else
196 	return (1);
197 #endif /* HAVE_PPSAPI */
198 }
199 
200 /*
201  * nmea_shutdown - shut down a GPS clock
202  */
203 static void
204 nmea_shutdown(
205 	int unit,
206 	struct peer *peer
207 	)
208 {
209 	register struct nmeaunit *up;
210 	struct refclockproc *pp;
211 
212 	pp = peer->procptr;
213 	up = (struct nmeaunit *)pp->unitptr;
214 #ifdef HAVE_PPSAPI
215 	if (up->handle != 0)
216 		time_pps_destroy(up->handle);
217 #endif /* HAVE_PPSAPI */
218 	io_closeclock(&pp->io);
219 	free(up);
220 }
221 
222 #ifdef HAVE_PPSAPI
223 /*
224  * nmea_control - fudge control
225  */
226 static void
227 nmea_control(
228 	int unit,		/* unit (not used */
229 	struct refclockstat *in, /* input parameters (not uded) */
230 	struct refclockstat *out, /* output parameters (not used) */
231 	struct peer *peer	/* peer structure pointer */
232 	)
233 {
234 	struct refclockproc *pp;
235 
236 	pp = peer->procptr;
237 	nmea_ppsapi(peer, pp->sloppyclockflag & CLK_FLAG2,
238 	    pp->sloppyclockflag & CLK_FLAG3);
239 }
240 
241 
242 /*
243  * Initialize PPSAPI
244  */
245 int
246 nmea_ppsapi(
247 	struct peer *peer,	/* peer structure pointer */
248 	int enb_clear,		/* clear enable */
249 	int enb_hardpps		/* hardpps enable */
250 	)
251 {
252 	struct refclockproc *pp;
253 	struct nmeaunit *up;
254 	int capability;
255 
256 	pp = peer->procptr;
257 	up = (struct nmeaunit *)pp->unitptr;
258 	if (time_pps_getcap(up->handle, &capability) < 0) {
259 		msyslog(LOG_ERR,
260 		    "refclock_nmea: time_pps_getcap failed: %m");
261 		return (0);
262 	}
263 	memset(&up->pps_params, 0, sizeof(pps_params_t));
264 	if (enb_clear)
265 		up->pps_params.mode = capability & PPS_CAPTURECLEAR;
266 	else
267 		up->pps_params.mode = capability & PPS_CAPTUREASSERT;
268 	if (!up->pps_params.mode) {
269 		msyslog(LOG_ERR,
270 		    "refclock_nmea: invalid capture edge %d",
271 		    !enb_clear);
272 		return (0);
273 	}
274 	up->pps_params.mode |= PPS_TSFMT_TSPEC;
275 	if (time_pps_setparams(up->handle, &up->pps_params) < 0) {
276 		msyslog(LOG_ERR,
277 		    "refclock_nmea: time_pps_setparams failed: %m");
278 		return (0);
279 	}
280 	if (enb_hardpps) {
281 		if (time_pps_kcbind(up->handle, PPS_KC_HARDPPS,
282 				    up->pps_params.mode & ~PPS_TSFMT_TSPEC,
283 				    PPS_TSFMT_TSPEC) < 0) {
284 			msyslog(LOG_ERR,
285 			    "refclock_nmea: time_pps_kcbind failed: %m");
286 			return (0);
287 		}
288 		pps_enable = 1;
289 	}
290 	peer->precision = PPS_PRECISION;
291 
292 #if DEBUG
293 	if (debug) {
294 		time_pps_getparams(up->handle, &up->pps_params);
295 		printf(
296 		    "refclock_ppsapi: capability 0x%x version %d mode 0x%x kern %d\n",
297 		    capability, up->pps_params.api_version,
298 		    up->pps_params.mode, enb_hardpps);
299 	}
300 #endif
301 
302 	return (1);
303 }
304 
305 /*
306  * Get PPSAPI timestamps.
307  *
308  * Return 0 on failure and 1 on success.
309  */
310 static int
311 nmea_pps(
312 	struct nmeaunit *up,
313 	l_fp *tsptr
314 	)
315 {
316 	pps_info_t pps_info;
317 	struct timespec timeout, ts;
318 	double dtemp;
319 	l_fp tstmp;
320 
321 	/*
322 	 * Convert the timespec nanoseconds field to ntp l_fp units.
323 	 */
324 	if (up->handle == 0)
325 		return (0);
326 	timeout.tv_sec = 0;
327 	timeout.tv_nsec = 0;
328 	memcpy(&pps_info, &up->pps_info, sizeof(pps_info_t));
329 	if (time_pps_fetch(up->handle, PPS_TSFMT_TSPEC, &up->pps_info,
330 	    &timeout) < 0)
331 		return (0);
332 	if (up->pps_params.mode & PPS_CAPTUREASSERT) {
333 		if (pps_info.assert_sequence ==
334 		    up->pps_info.assert_sequence)
335 			return (0);
336 		ts = up->pps_info.assert_timestamp;
337 	} else if (up->pps_params.mode & PPS_CAPTURECLEAR) {
338 		if (pps_info.clear_sequence ==
339 		    up->pps_info.clear_sequence)
340 			return (0);
341 		ts = up->pps_info.clear_timestamp;
342 	} else {
343 		return (0);
344 	}
345 	if ((up->ts.tv_sec == ts.tv_sec) && (up->ts.tv_nsec == ts.tv_nsec))
346 		return (0);
347 	up->ts = ts;
348 
349 	tstmp.l_ui = ts.tv_sec + JAN_1970;
350 	dtemp = ts.tv_nsec * FRAC / 1e9;
351 	tstmp.l_uf = (u_int32)dtemp;
352 	*tsptr = tstmp;
353 	return (1);
354 }
355 #endif /* HAVE_PPSAPI */
356 
357 /*
358  * nmea_receive - receive data from the serial interface
359  */
360 static void
361 nmea_receive(
362 	struct recvbuf *rbufp
363 	)
364 {
365 	register struct nmeaunit *up;
366 	struct refclockproc *pp;
367 	struct peer *peer;
368 	int month, day;
369 	int i;
370 	char *cp, *dp;
371 	int cmdtype;
372 	/* Use these variables to hold data until we decide its worth keeping */
373 	char	rd_lastcode[BMAX];
374 	l_fp	rd_tmp;
375 	u_short	rd_lencode;
376 
377 	/*
378 	 * Initialize pointers and read the timecode and timestamp
379 	 */
380 	peer = (struct peer *)rbufp->recv_srcclock;
381 	pp = peer->procptr;
382 	up = (struct nmeaunit *)pp->unitptr;
383 	rd_lencode = refclock_gtlin(rbufp, rd_lastcode, BMAX, &rd_tmp);
384 
385 	/*
386 	 * There is a case that a <CR><LF> gives back a "blank" line
387 	 */
388 	if (rd_lencode == 0)
389 	    return;
390 
391 #ifdef DEBUG
392 	if (debug)
393 	    printf("nmea: gpsread %d %s\n", rd_lencode,
394 		   rd_lastcode);
395 #endif
396 
397 	/*
398 	 * We check the timecode format and decode its contents. The
399 	 * we only care about a few of them.  The most important being
400 	 * the $GPRMC format
401 	 * $GPRMC,hhmmss,a,fddmm.xx,n,dddmmm.xx,w,zz.z,yyy.,ddmmyy,dd,v*CC
402 	 * For Magellan (ColorTrak) GLL probably datum (order of sentences)
403 	 * also mode (0,1,2,3) select sentence ANY/ALL, RMC, GGA, GLL
404 	 * $GPGLL,3513.8385,S,14900.7851,E,232420.594,A*21
405   	 * $GPGGA,232420.59,3513.8385,S,14900.7851,E,1,05,3.4,00519,M,,,,*3F
406 	 * $GPRMB,...
407 	 * $GPRMC,232418.19,A,3513.8386,S,14900.7853,E,00.0,000.0,121199,12.,E*77
408 	 * $GPAPB,...
409 	 * $GPGSA,...
410 	 * $GPGSV,...
411 	 * $GPGSV,...
412 	 */
413 #define GPXXX	0
414 #define GPRMC	1
415 #define GPGGA	2
416 #define GPGLL	4
417 	cp = rd_lastcode;
418 	cmdtype=0;
419 	if(strncmp(cp,"$GPRMC",6)==0) {
420 		cmdtype=GPRMC;
421 	}
422 	else if(strncmp(cp,"$GPGGA",6)==0) {
423 		cmdtype=GPGGA;
424 	}
425 	else if(strncmp(cp,"$GPGLL",6)==0) {
426 		cmdtype=GPGLL;
427 	}
428 	else if(strncmp(cp,"$GPXXX",6)==0) {
429 		cmdtype=GPXXX;
430 	}
431 	else
432 	    return;
433 
434 
435 	/* See if I want to process this message type */
436 	if ( ((peer->ttlmax == 0) && (cmdtype != GPRMC))
437            || ((peer->ttlmax != 0) && !(cmdtype & peer->ttlmax)) )
438 		return;
439 
440 	pp->lencode = rd_lencode;
441 	strcpy(pp->a_lastcode,rd_lastcode);
442 	cp = pp->a_lastcode;
443 
444 	pp->lastrec = up->tstamp = rd_tmp;
445 	up->pollcnt = 2;
446 
447 #ifdef DEBUG
448 	if (debug)
449 	    printf("nmea: timecode %d %s\n", pp->lencode,
450 		   pp->a_lastcode);
451 #endif
452 
453 
454 	/* Grab field depending on clock string type */
455 	switch( cmdtype ) {
456 	    case GPRMC:
457 		/*
458 		 * Test for synchronization.  Check for quality byte.
459 		 */
460 		dp = field_parse(cp,2);
461 		if( dp[0] != 'A')
462 			pp->leap = LEAP_NOTINSYNC;
463 		else
464 			pp->leap = LEAP_NOWARNING;
465 
466 		/* Now point at the time field */
467 		dp = field_parse(cp,1);
468 		break;
469 
470 
471 	    case GPGGA:
472 		/*
473 		 * Test for synchronization.  Check for quality byte.
474 		 */
475 		dp = field_parse(cp,6);
476 		if( dp[0] == '0')
477 			pp->leap = LEAP_NOTINSYNC;
478 		else
479 			pp->leap = LEAP_NOWARNING;
480 
481 		/* Now point at the time field */
482 		dp = field_parse(cp,1);
483 		break;
484 
485 
486 	    case GPGLL:
487 		/*
488 		 * Test for synchronization.  Check for quality byte.
489 		 */
490 		dp = field_parse(cp,6);
491 		if( dp[0] != 'A')
492 			pp->leap = LEAP_NOTINSYNC;
493 		else
494 			pp->leap = LEAP_NOWARNING;
495 
496 		/* Now point at the time field */
497 		dp = field_parse(cp,5);
498 		break;
499 
500 
501 	    case GPXXX:
502 		return;
503 	    default:
504 		return;
505 
506 	}
507 
508 		/*
509 		 *	Check time code format of NMEA
510 		 */
511 
512 		if( !isdigit((int)dp[0]) ||
513 		    !isdigit((int)dp[1]) ||
514 		    !isdigit((int)dp[2]) ||
515 		    !isdigit((int)dp[3]) ||
516 		    !isdigit((int)dp[4]) ||
517 		    !isdigit((int)dp[5])
518 		    ) {
519 			refclock_report(peer, CEVNT_BADREPLY);
520 			return;
521 		}
522 
523 
524 	/*
525 	 * Convert time and check values.
526 	 */
527 	pp->hour = ((dp[0] - '0') * 10) + dp[1] - '0';
528 	pp->minute = ((dp[2] - '0') * 10) + dp[3] -  '0';
529 	pp->second = ((dp[4] - '0') * 10) + dp[5] - '0';
530 	/* Default to 0 milliseconds, if decimal convert milliseconds in
531 	   one, two or three digits
532 	*/
533 	pp->msec = 0;
534 	if (dp[6] == '.') {
535 		if (isdigit((int)dp[7])) {
536 			pp->msec = (dp[7] - '0') * 100;
537 			if (isdigit((int)dp[8])) {
538 				pp->msec += (dp[8] - '0') * 10;
539 				if (isdigit((int)dp[9])) {
540 					pp->msec += (dp[9] - '0');
541 				}
542 			}
543 		}
544 	}
545 
546 	if (pp->hour > 23 || pp->minute > 59 || pp->second > 59
547 	  || pp->msec > 1000) {
548 		refclock_report(peer, CEVNT_BADTIME);
549 		return;
550 	}
551 
552 
553 	/*
554 	 * Convert date and check values.
555 	 */
556 	if (cmdtype==GPRMC) {
557 	    dp = field_parse(cp,9);
558 	    day = dp[0] - '0';
559 	    day = (day * 10) + dp[1] - '0';
560 	    month = dp[2] - '0';
561 	    month = (month * 10) + dp[3] - '0';
562 	    pp->year = dp[4] - '0';
563 	    pp->year = (pp->year * 10) + dp[5] - '0';
564 	}
565 	else {
566 	/* only time */
567 	    time_t tt = time(NULL);
568 	    struct tm * t = gmtime(&tt);
569 	    day = t->tm_mday;
570 	    month = t->tm_mon + 1;
571 	    pp->year= t->tm_year;
572 	}
573 
574 	if (month < 1 || month > 12 || day < 1) {
575 		refclock_report(peer, CEVNT_BADTIME);
576 		return;
577 	}
578 
579         /* Hmmmm this will be a nono for 2100,2200,2300 but I don't think I'll be here */
580         /* good thing that 2000 is a leap year */
581 	/* pp->year will be 00-99 if read from GPS, 00->  (years since 1900) from tm_year */
582 	if (pp->year % 4) {
583 		if (day > day1tab[month - 1]) {
584 			refclock_report(peer, CEVNT_BADTIME);
585 			return;
586 		}
587 		for (i = 0; i < month - 1; i++)
588 		    day += day1tab[i];
589 	} else {
590 		if (day > day2tab[month - 1]) {
591 			refclock_report(peer, CEVNT_BADTIME);
592 			return;
593 		}
594 		for (i = 0; i < month - 1; i++)
595 		    day += day2tab[i];
596 	}
597 	pp->day = day;
598 
599 
600 #ifdef HAVE_PPSAPI
601 	/*
602 	 * If the PPSAPI is working, rather use its timestamps.
603 	 * assume that the PPS occurs on the second so blow any msec
604 	 */
605 	if (nmea_pps(up, &rd_tmp) == 1) {
606 		pp->lastrec = up->tstamp = rd_tmp;
607 		pp->msec = 0;
608 	}
609 #endif /* HAVE_PPSAPI */
610 
611 	/*
612 	 * Process the new sample in the median filter and determine the
613 	 * reference clock offset and dispersion. We use lastrec as both
614 	 * the reference time and receive time, in order to avoid being
615 	 * cute, like setting the reference time later than the receive
616 	 * time, which may cause a paranoid protocol module to chuck out
617 	 * the data.
618 	 */
619 
620 	if (!refclock_process(pp)) {
621 		refclock_report(peer, CEVNT_BADTIME);
622 		return;
623 	}
624 
625 
626 
627 	/*
628 	 * Only go on if we had been polled.
629 	 */
630 	if (!up->polled)
631 	    return;
632 	up->polled = 0;
633 
634 	refclock_receive(peer);
635 
636         /* If we get here - what we got from the clock is OK, so say so */
637          refclock_report(peer, CEVNT_NOMINAL);
638 
639 	record_clock_stats(&peer->srcadr, pp->a_lastcode);
640 
641 }
642 
643 /*
644  * nmea_poll - called by the transmit procedure
645  *
646  * We go to great pains to avoid changing state here, since there may be
647  * more than one eavesdropper receiving the same timecode.
648  */
649 static void
650 nmea_poll(
651 	int unit,
652 	struct peer *peer
653 	)
654 {
655 	register struct nmeaunit *up;
656 	struct refclockproc *pp;
657 
658 	pp = peer->procptr;
659 	up = (struct nmeaunit *)pp->unitptr;
660 	if (up->pollcnt == 0)
661 	    refclock_report(peer, CEVNT_TIMEOUT);
662 	else
663 	    up->pollcnt--;
664 	pp->polls++;
665 	up->polled = 1;
666 
667 	/*
668 	 * usually nmea_receive can get a timestamp every second
669 	 */
670 
671 	gps_send(pp->io.fd,"$PMOTG,RMC,0000*1D\r\n", peer);
672 }
673 
674 /*
675  *
676  *	gps_send(fd,cmd, peer)  Sends a command to the GPS receiver.
677  *	 as	gps_send(fd,"rqts,u\r", peer);
678  *
679  *	We don't currently send any data, but would like to send
680  *	RTCM SC104 messages for differential positioning. It should
681  *	also give us better time. Without a PPS output, we're
682  *	Just fooling ourselves because of the serial code paths
683  *
684  */
685 static void
686 gps_send(
687 	int fd,
688 	const char *cmd,
689 	struct peer *peer
690 	)
691 {
692 
693 	if (write(fd, cmd, strlen(cmd)) == -1) {
694 		refclock_report(peer, CEVNT_FAULT);
695 	}
696 }
697 
698 static char *
699 field_parse(
700 	char *cp,
701 	int fn
702 	)
703 {
704 	char *tp;
705 	int i = fn;
706 
707 	for (tp = cp; *tp != '\0'; tp++) {
708 		if (*tp == ',')
709 		    i--;
710 		if (i == 0)
711 		    break;
712 	}
713 	return (++tp);
714 }
715 #else
716 int refclock_nmea_bs;
717 #endif /* REFCLOCK */
718