diff -Pur ntp/acconfig.h ntp.new/acconfig.h --- ntp/acconfig.h Wed Apr 25 17:11:50 2001 +++ ntp.new/acconfig.h Fri Jul 20 21:11:47 2001 @@ -61,6 +61,9 @@ /* Forum Graphic GPS datating station driver */ #undef CLOCK_FG +/* Garmin GPS Handheld Receivers */ +#undef CLOCK_GARMIN + /* TrueTime GPS receiver/VME interface */ #undef CLOCK_GPSVME diff -Pur ntp/config.h.in ntp.new/config.h.in --- ntp/config.h.in Fri Jul 20 09:36:34 2001 +++ ntp.new/config.h.in Fri Jul 20 21:11:47 2001 @@ -62,6 +62,9 @@ /* Forum Graphic GPS datating station driver */ #undef CLOCK_FG +/* Garmin GPS Handheld Receivers */ +#undef CLOCK_GARMIN + /* TrueTime GPS receiver/VME interface */ #undef CLOCK_GPSVME diff -Pur ntp/configure ntp.new/configure --- ntp/configure Fri Jul 20 09:36:36 2001 +++ ntp.new/configure Fri Jul 20 11:07:17 2001 @@ -673,6 +673,7 @@ --enable-AUDIO-CHU s CHU audio/decoder --enable-DATUM s Datum Programmable Time System --enable-FG + Forum Graphic GPS + --enable-GARMIN + Garmin GPS Handheld Receivers --enable-HEATH s Heath GC-1000 WWV/WWVH receiver --enable-HPGPS + HP 58503A GPS receiver --enable-IRIG s Sun IRIG audio decoder @@ -12396,6 +12397,25 @@ ntp_refclock=yes cat >>confdefs.h <<\EOF #define CLOCK_FG 1 +EOF + +fi +echo "$as_me:12357: result: $ntp_ok" >&5 +echo "${ECHO_T}$ntp_ok" >&6 + +echo "$as_me:12341: checking Garmin GPS Handheld Receivers" >&5 +echo $ECHO_N "checking Garmin GPS Handheld Receivers... $ECHO_C" >&6 +# Check whether --enable-GARMIN or --disable-GARMIN was given. +if test "${enable_GARMIN+set}" = set; then + enableval="$enable_GARMIN" + ntp_ok=$enableval +else + ntp_ok=$ntp_eac +fi; +if test "$ntp_ok" = "yes"; then + ntp_refclock=yes + cat >>confdefs.h <<\EOF +#define CLOCK_GARMIN 1 EOF fi diff -Pur ntp/configure.in ntp.new/configure.in --- ntp/configure.in Fri Jul 20 09:32:13 2001 +++ ntp.new/configure.in Fri Jul 20 21:13:52 2001 @@ -1722,6 +1722,15 @@ fi AC_MSG_RESULT($ntp_ok) +AC_MSG_CHECKING(Garmin GPS Handheld Receivers) +AC_ARG_ENABLE(GARMIN, [ --enable-GARMIN + Garmin GPS Handheld Receivers], + [ntp_ok=$enableval], [ntp_ok=$ntp_eac]) +if test "$ntp_ok" = "yes"; then + ntp_refclock=yes + AC_DEFINE(CLOCK_GARMIN) +fi +AC_MSG_RESULT($ntp_ok) + # Requires modem control AC_MSG_CHECKING(Heath GC-1000 WWV/WWVH receiver) AC_ARG_ENABLE(HEATH, [ --enable-HEATH s Heath GC-1000 WWV/WWVH receiver], diff -Pur ntp/html/driver40.htm ntp.new/html/driver40.htm --- ntp/html/driver40.htm Wed Dec 31 16:00:00 1969 +++ ntp.new/html/driver40.htm Fri Jul 20 21:11:47 2001 @@ -0,0 +1,92 @@ + + + + +Garmin GPS Handheld Receivers + + + +

Garmin GPS Handheld Receivers

+ +
+

Synopsis

+Address: 127.127.40.u
+Reference ID: GPS
+Driver ID: GARMIN
+Serial Port: /dev/gpsu; 9600 bps, 8-bits, no parity
+
Features: (none) +

Description

+ +

NTP clock driver for Garmin GPS handheld receivers using the Garmin +proprietary protocol. It is based on both documented and undocumented +aspects of the Garmin proprietary protocol. + +

To use this driver, your Garmin GPS handheld receiver must be +configured to use Garmin protocol interface mode, not NMEA. + +

This driver has been written and tested using a Linux based system. +To achieve the best quality results, turn off serial port FIFOs and enable +low latency tty driver processing. In Linux, this is accomplished using +the setserial application (e.g. setserial /dev/gpsu uart 16550 +low_latency). Even better results are obtained with kernel timestamps on +received characters. This is what I have been using for most of my tests. + +

This is a "sloppy" clock but I believe it is possible to recover +sub-millisecond results with a good clock filter. Without an accurate +reference clock, I am unable to provide more quantitative performance +results at this time. + +

I suspect that earlier versions of firmware may not return the +undisciplined oscillator count as part of a date/time response. +This information is needed to recover the higher accuracy results. +Without this undocumented field, the driver will only achieve sub-second +accuracy. I welcome technical support from the manufacturer to achieve +universal support for the entire range of models and firmware versions. + +

This driver has been testing with the following units. +I need feedback from users to populate this list! Check for +updates to this list, driver, and other related software at at NTP Drivers for Garmin +GPS Handheld Receivers + +

+ +

Fudge Factors

+ +
+
time1 time
+
Specifies the time offset calibration factor, in seconds and fraction, +with default 0.0.
+ +
time2 time
+
Not used by this driver.
+ +
stratum number
+
Specifies the driver stratum, in decimal from 0 to 15, with default 0. +
+ +
refid string
+
Specifies the driver reference identifier, an ASCII string from one to +four characters, with default GPS.
+ +
flag1 0 | 1
+
Not used by this driver.
+ +
flag2 0 | 1
+
Not used by this driver.
+ +
flag3 0 | 1
+
Not used by this driver.
+ +
flag4 0 | 1
+
Not used by this driver.
+
+ +
+
+Bob Lindell (lindell@isi.edu) +
+ + diff -Pur ntp/html/refclock.htm ntp.new/html/refclock.htm --- ntp/html/refclock.htm Fri May 18 09:32:11 2001 +++ ntp.new/html/refclock.htm Fri Jul 20 21:11:47 2001 @@ -221,7 +221,9 @@ Type 38 hopf GPS/DCF77 6021/komp for Serial Line (HOPF_S)
Type 39 hopf GPS/DCF77 6039 for PCI-Bus -(HOPF_P)

+(HOPF_P)
+Type 40 Garmin GPS Handheld Receivers (GARMIN +)

* All TrueTime receivers are now supported by one driver, type 5. Types 15 and 25 will be retained only for a limited time and may diff -Pur ntp/include/ntp.h ntp.new/include/ntp.h --- ntp/include/ntp.h Wed Jun 13 16:05:21 2001 +++ ntp.new/include/ntp.h Fri Jul 20 21:11:47 2001 @@ -457,7 +457,8 @@ #define REFCLK_FG 37 /* Forum Graphic GPS */ #define REFCLK_HOPF_SERIAL 38 /* hopf DCF77/GPS serial line receiver */ #define REFCLK_HOPF_PCI 39 /* hopf DCF77/GPS PCI receiver */ -#define REFCLK_MAX 39 /* Grow as needed... */ +#define REFCLK_GARMIN 40 /* Garmin GPS Handheld Receivers */ +#define REFCLK_MAX 40 /* Grow as needed... */ /* * We tell reference clocks from real peers by giving the reference diff -Pur ntp/libntp/clocktypes.c ntp.new/libntp/clocktypes.c --- ntp/libntp/clocktypes.c Tue Apr 3 00:12:02 2001 +++ ntp.new/libntp/clocktypes.c Fri Jul 20 21:11:47 2001 @@ -90,6 +90,8 @@ "HOPF_S"}, { REFCLK_HOPF_PCI, "hopf Elektronic PCI receiver (39)", "HOPF_P"}, + { REFCLK_GARMIN, "Garmin GPS Handheld Receivers (40)", + "GPS_GARMIN"}, { -1, "", "" } }; diff -Pur ntp/ntpd/Makefile.am ntp.new/ntpd/Makefile.am --- ntp/ntpd/Makefile.am Tue Apr 3 18:58:01 2001 +++ ntp.new/ntpd/Makefile.am Fri Jul 20 21:11:47 2001 @@ -28,7 +28,8 @@ ntp_util.c ntpd.c refclock_acts.c refclock_arbiter.c refclock_arc.c \ refclock_as2201.c refclock_atom.c refclock_bancomm.c \ refclock_chronolog.c refclock_chu.c refclock_conf.c refclock_datum.c \ - refclock_dumbclock.c refclock_fg.c refclock_gpsvme.c refclock_heath.c \ + refclock_dumbclock.c refclock_fg.c refclock_garmin.c \ + refclock_gpsvme.c refclock_heath.c \ refclock_hopfser.c refclock_hopfpci.c \ refclock_hpgps.c refclock_irig.c refclock_jupiter.c refclock_leitch.c \ refclock_local.c refclock_msfees.c refclock_mx4200.c refclock_nmea.c \ diff -Pur ntp/ntpd/Makefile.in ntp.new/ntpd/Makefile.in --- ntp/ntpd/Makefile.in Fri Jul 20 09:36:39 2001 +++ ntp.new/ntpd/Makefile.in Fri Jul 20 11:07:17 2001 @@ -143,7 +143,8 @@ ntp_util.c ntpd.c refclock_acts.c refclock_arbiter.c refclock_arc.c \ refclock_as2201.c refclock_atom.c refclock_bancomm.c \ refclock_chronolog.c refclock_chu.c refclock_conf.c refclock_datum.c \ - refclock_dumbclock.c refclock_fg.c refclock_gpsvme.c refclock_heath.c \ + refclock_dumbclock.c refclock_fg.c refclock_garmin.c \ + refclock_gpsvme.c refclock_heath.c \ refclock_hopfser.c refclock_hopfpci.c \ refclock_hpgps.c refclock_irig.c refclock_jupiter.c refclock_leitch.c \ refclock_local.c refclock_msfees.c refclock_mx4200.c refclock_nmea.c \ @@ -185,7 +186,7 @@ refclock_bancomm$U.$(OBJEXT) refclock_chronolog$U.$(OBJEXT) \ refclock_chu$U.$(OBJEXT) refclock_conf$U.$(OBJEXT) \ refclock_datum$U.$(OBJEXT) refclock_dumbclock$U.$(OBJEXT) \ -refclock_fg$U.$(OBJEXT) refclock_gpsvme$U.$(OBJEXT) \ +refclock_fg$U.$(OBJEXT) refclock_garmin$U.o refclock_gpsvme$U.$(OBJEXT) \ refclock_heath$U.$(OBJEXT) refclock_hopfser$U.$(OBJEXT) \ refclock_hopfpci$U.$(OBJEXT) refclock_hpgps$U.$(OBJEXT) \ refclock_irig$U.$(OBJEXT) refclock_jupiter$U.$(OBJEXT) \ diff -Pur ntp/ntpd/ntp_control.c ntp.new/ntpd/ntp_control.c --- ntp/ntpd/ntp_control.c Wed Jun 13 16:05:23 2001 +++ ntp.new/ntpd/ntp_control.c Fri Jul 20 21:11:47 2001 @@ -395,6 +395,7 @@ CTL_SST_TS_LF, /* REFCLK_FG (37) */ CTL_SST_TS_UHF, /* REFCLK_HOPF_SERIAL (38) */ CTL_SST_TS_UHF, /* REFCLK_HOPF_PCI (39) */ + CTL_SST_TS_UHF, /* REFCLK_GPS_GARMIN (40) */ }; diff -Pur ntp/ntpd/refclock_conf.c ntp.new/ntpd/refclock_conf.c --- ntp/ntpd/refclock_conf.c Tue Apr 3 00:12:07 2001 +++ ntp.new/ntpd/refclock_conf.c Fri Jul 20 21:11:47 2001 @@ -228,6 +228,12 @@ #define refclock_fg refclock_none #endif +#ifdef CLOCK_GARMIN +extern struct refclock refclock_garmin; +#else +#define refclock_garmin refclock_none +#endif + #ifdef CLOCK_HOPF_SERIAL extern struct refclock refclock_hopfser; #else @@ -286,7 +292,8 @@ &refclock_wwv, /* 36 REFCLOCK_WWV_AUDIO */ &refclock_fg, /* 37 REFCLOCK_FG */ &refclock_hopfser, /* 38 REFCLK_HOPF_SERIAL */ - &refclock_hopfpci /* 39 REFCLK_HOPF_PCI */ + &refclock_hopfpci, /* 39 REFCLK_HOPF_PCI */ + &refclock_garmin /* 40 REFCLK_GARMIN */ }; u_char num_refclock_conf = sizeof(refclock_conf)/sizeof(struct refclock *); diff -Pur ntp/ntpd/refclock_garmin.c ntp.new/ntpd/refclock_garmin.c --- ntp/ntpd/refclock_garmin.c Wed Dec 31 16:00:00 1969 +++ ntp.new/ntpd/refclock_garmin.c Fri Jul 20 21:11:47 2001 @@ -0,0 +1,642 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +#if defined(REFCLOCK) && defined(CLOCK_GARMIN) + +/* + * NTP clock driver for Garmin GPS handheld receivers (1.2) + * Copyright (C) 2000 Bob Lindell (lindell@isi.edu) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +/* + * NTP clock driver for Garmin GPS handheld receivers using the + * Garmin proprietary protocol. It is based on both documented + * and undocumented aspects of the Garmin proprietary protocol. + * + * To use this driver, your Garmin GPS handheld receiver must be + * configured to use Garmin protocol interface mode, not NMEA. + * + * This driver has been written and tested using a Linux based + * system. To achieve the best quality results, turn off serial + * port FIFOs and enable low latency tty driver processing. + * In Linux, this is accomplished using the setserial application + * (e.g. setserial /dev/gps0 uart 16550 low_latency). Even better + * results are obtained with kernel timestamps on received + * characters. This is what I have been using for most of my tests. + * + * This is a "sloppy" clock but I believe it is possible to recover + * sub-millisecond results with a good clock filter. Without an + * accurate reference clock, I am unable to provide more quantitative + * performance results at this time. + * + * I suspect that earlier versions of firmware may not return the + * undisciplined oscillator count as part of a date/time response. + * This information is needed to recover the higher accuracy results. + * Without this undocumented field, the driver will only achieve + * sub-second accuracy. I welcome technical support from the + * manufacturer to achieve universal support for the entire range + * of models and firmware versions. + * + * This driver has been testing with the following units. + * I need feedback from users to populate this list! Check for + * updates to this list, driver, and other related software + * at http://www.isi.edu/~lindell/garmin.html. + * + * * Garmin GPS 12 firmware version 4.54 + * * ?? + */ + +/* #define TIMESTAMPS */ + +#include +#include +#include + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_refclock.h" +#include "ntp_stdlib.h" +#ifdef TIMESTAMPS +#include "ntp_unixtime.h" +#endif /* TIMESTAMPS */ + +/* + * One day, POSIX types will be a given. + */ + +typedef unsigned char uint8_x; +typedef short int16_x; +typedef unsigned short uint16_x; +typedef u_int32 uint32_x; + +/* + * Generic functions + */ + +#define SIZEOF(x) (sizeof(x) / sizeof((x)[0])) + +/* + * Time constants + */ + +#define USEC_PER_SEC 1000000 +#define SEC_PER_MIN 60 +#define MIN_PER_HOUR 60 +#define HOUR_PER_DAY 24 +#define SEC_PER_DAY (SEC_PER_MIN * MIN_PER_HOUR * HOUR_PER_DAY) + +/* + * The Garmin proprietary protocol is partially documented + * by the manufacturer. The document, "Garmin GPS Interface + * Specification", dated December, 1999, is available at + * http://www.garmin.com/support/pdf/iop_spec.pdf. A good reference + * for some of the undocumented packet formats can be found at + * http://artico.lma.fi.upm.es/numerico/miembros/antonio/async/. + */ + +/* + * Packet IDs at the Garmin protocol link layer. + */ + +#define PID_ACK_BYTE 6 +#define PID_COMMAND_DATA 10 +#define PID_DATE_TIME_DATA 14 +#define PID_UNDOC_PVT 20 +#define PID_NAK_BYTE 21 +#define PID_UNDOC_ASYNC 28 +#define PID_PVT_DATA 51 + +/* + * Packet commands at the Garmin protocol application layer. + */ + +#define CMND_TRANSFER_TIME 5 + +/* + * Garmin data structure constants + */ + +#define FIX_2D 2 /* At least a 2D fix */ +#define CLK_SHIFT 9 /* LSB of oscillator */ +#define CLK_MASK ((1 << CLK_SHIFT) - 1) + +/* + * Garmin packet functions + */ + +#define PKT_MAX (2 + 2 * 257 + 2) +#define PKT_PID(x) ((x)[0]) +#define PKT_SIZE(x) ((x)[1]) + +#ifdef XNTP_BIG_ENDIAN +#define GTOH16(buf,i) (((buf)[(i) + 1] << 8) | (buf)[i]) +#define GTOH32(buf,i) ((GTOH16(buf,(i) + 2) << 16) | GTOH16(buf,i)) +#define GTOHD(buf,i) gtohd(buf,i) +static +double +gtohd(uint8_x *buf,int i) { + union { + unsigned long l[2]; + double d; + } x; + + x.l[1] = GTOH32(buf,i); + x.l[0] = GTOH32(buf,i + 4); + return(x.d); +} +#else /* XNTP_BIG_ENDIAN */ +#define GTOH16(buf,i) (*((uint16_x *) &(buf)[i])) +#define GTOH32(buf,i) (*((uint32_x *) &(buf)[i])) +#define GTOHD(buf,i) (*((double *) &(buf)[i])) +#endif /* XNTP_BIG_ENDIAN */ + +/* + * NTP daemon definitions + */ + +#ifdef SYS_WINNT +#define DEVICE "COM%d:" /* COM 1 - 3 supported */ +#else +#define DEVICE "/dev/gps%d" /* name of radio device */ +#endif +#define SPEED232 B9600 /* UART speed (9600 bps) */ +#define PRECISION (-10) /* precision (about 1 ms) */ +#define REFID "GPS\0" /* reference id */ +#define DESCRIPTION "Garmin GPS Clock" /* who we are */ + +#define BUF_SIZE 1024 /* ring buffer size */ +#define MIN_SAMPLES 10 /* min samples per poll period */ + +/* + * NTP daemon unit control structure + */ + +struct unit { + uint8_x buffer[BUF_SIZE]; /* received data */ + l_fp ts[BUF_SIZE]; /* time of byte arrival */ + uint16_x rp; /* read pointer */ + uint16_x wp; /* write pointer */ + double period; /* period of counter (usecs) */ + double tow0; /* previous value of GPS tow */ + uint32_x counter0; /* previous value of counter */ + int16_x leap; /* GPS leap second offset to UTC */ + int high; /* High accuracy mode enabled */ +#ifdef TIMESTAMPS + uint8_x tcnt; /* byte count for timeval data */ + struct timeval tv; /* time of byte arrival */ +#endif /* TIMESTAMPS */ +}; + +/* + * Function prototypes + */ + +typedef void (*callback)(struct recvbuf *,uint8_x *,l_fp *); + +static int gstart P((int,struct peer *)); +static void gshutdown P((int,struct peer *)); +static void greceive P((struct recvbuf *)); +static void gpoll P((int,struct peer *)); +static int gwrite P((int,uint8_x *)); +static void gread P((struct recvbuf *,callback)); +static void process P((struct recvbuf *,uint8_x *,l_fp *)); + +/* + * NTP daemon transfer vector + */ + +struct refclock refclock_garmin = { + gstart, /* start up driver */ + gshutdown, /* shut down driver */ + gpoll, /* transmit poll message */ + noentry, /* handle control */ + noentry, /* initialize driver */ + noentry, /* buginfo */ + NOFLAGS /* not used */ +}; + +/* Start async message generation of PID_PVT_DATA and PID_UNDOC_PVT */ +static uint8_x async[] = { PID_UNDOC_ASYNC, 2, 4, 3 }; + +/* + * start - open the devices and initialize data for + * processing. + */ + +static +int +gstart(int unit,struct peer *peer) +{ + struct unit *up; + struct refclockproc *pp; + char device[sizeof(DEVICE) + 1]; + + /* + * Allocate and initialize structures + */ + up = (struct unit *) emalloc(sizeof(*up)); + if (up == NULL) + return (FALSE); + memset((char *)up,0,sizeof(*up)); + pp = peer->procptr; + pp->unitptr = (caddr_t)up; + pp->io.srcclock = (caddr_t)peer; + pp->io.clock_recv = greceive; + pp->io.datalen = 0; + peer->precision = PRECISION; + pp->clockdesc = DESCRIPTION; + memcpy((char *)&pp->refid,REFID,4); + (void)sprintf(device,DEVICE,unit); + pp->io.fd = refclock_open(device,SPEED232,LDISC_RAW); + if (pp->io.fd < 0) { + free(up); + return (FALSE); + } + if (!io_addclock(&pp->io)) { + (void) close(pp->io.fd); + free(up); + return (FALSE); + } + gwrite(pp->io.fd,async); + return (TRUE); +} + +/* + * shutdown - shut down the clock. + */ + +static +void +gshutdown(int unit,struct peer *peer) +{ + struct unit *up; + struct refclockproc *pp; + /* Stop async message generation */ + static uint8_x stop[] = { PID_UNDOC_ASYNC, 2, 0, 0 }; + + pp = peer->procptr; + up = (struct unit *)pp->unitptr; + gwrite(pp->io.fd,stop); + io_closeclock(&pp->io); + free(up); +} + +/* + * receive - receive data from the clock. + */ + +static +void +greceive(struct recvbuf *rbufp) +{ + gread(rbufp,process); +} + +/* + * poll - called by the transmit procedure. + */ + +static +void +gpoll(int unit,struct peer *peer) +{ + struct refclockproc *pp; + struct unit *up; + + pp = peer->procptr; + up = (struct unit *)pp->unitptr; + pp->polls++; + /* + * Reexamine the GPS leap offset every poll period. + * Also support hot disconnects and reconnects of the GPS + * clock. We don't want to deprive the user from going on + * an enjoyable hike with their receiver. + */ + gwrite(pp->io.fd,async); + if (pp->coderecv < (pp->codeproc + MIN_SAMPLES)) { + refclock_report(peer,CEVNT_TIMEOUT); + up->rp = up->wp; +#ifdef TIMESTAMPS + up->tcnt = 0; +#endif /* TIMESTAMPS */ + return; + } + record_clock_stats(&peer->srcadr,pp->a_lastcode); + refclock_receive(peer); +} + + +/* + * Process packets of the Garmin protocol. The Garmin specification + * refers to this as the application layer processing. + */ + +static +void +process(struct recvbuf *rbufp,uint8_x *buf,l_fp *t) +{ + uint32_x counter; + struct unit *up; + struct refclockproc *pp; + struct peer *peer; + double tow; + /* Poll for date and time */ + static uint8_x date[] = { PID_COMMAND_DATA, 2, CMND_TRANSFER_TIME, 0 }; + /* Start async message generation of PID_UNDOC_PVT only */ + static uint8_x simplepvt[] = { PID_UNDOC_ASYNC, 2, 4, 0 }; + + peer = (struct peer *)rbufp->recv_srcclock; + pp = peer->procptr; + up = (struct unit *)pp->unitptr; + if (PKT_PID(buf) == PID_UNDOC_PVT) { + /* + * Every second we get the current count of the + * undisciplined oscillator at the beginning of + * the second. The nominal frequency is 0.5115 + * Mhz which is half of 1.023 Mhz GPS data rate. We + * compute the period of this oscillator by measuring + * it against the computed GPS time of week (tow) + * field. The tow field is actually reported in two + * parts, a value which drifts with the oscillator + * and a correction factor. + */ + if (GTOH16(buf,2) < FIX_2D) + return; + gwrite(pp->io.fd,date); /* Ask for the data and time */ + if (!up->high) + return; + counter = (GTOH32(buf,22) << CLK_SHIFT) + | (GTOH32(buf,26) & CLK_MASK); + tow = GTOHD(buf,6) - GTOHD(buf,70); + up->period = USEC_PER_SEC * (tow - up->tow0) + / (counter - up->counter0); + up->counter0 = counter; + up->tow0 = tow; + return; + } + if (PKT_PID(buf) == PID_PVT_DATA) { + /* + * Every second we get a position, velocity, + * and time (PVT) solution of the GPS receiver. + * This gives the GPS leap second offset to UTC. + * The only reason we need this message is to + * correct the date and time packets. + */ + if (GTOH16(buf,18) < FIX_2D) + return; + up->leap = GTOH16(buf,60); + if (up->high) + /* turn off PID_PVT_DATA */ + gwrite(pp->io.fd,simplepvt); + else + up->tow0 = GTOHD(buf,20); + return; + } + if (PKT_PID(buf) == PID_DATE_TIME_DATA) { + /* + * The Garmin documentation says we get back + * 8 bytes including the data and time to the + * nearest second. Some versions of the firmware + * throw in an additional 4 bytes containing the + * upper bits of the undisciplined oscillator count. + */ + pp->lastrec = *t; + pp->year = GTOH16(buf,4); + pp->day = ymd2yd(pp->year,buf[2],buf[3]); + pp->hour = GTOH16(buf,6); + pp->minute = buf[8]; + pp->second = buf[9]; + pp->msec = 0; + pp->leap = LEAP_NOWARNING; + pp->lencode = sprintf(pp->a_lastcode, + "%.02d/%.02d/%d %.02d:%.02d:%.02d",buf[2],buf[3], + pp->year,pp->hour,pp->minute,pp->second); + /* + * The time, which is reported to the nearest + * second, seems to drift relative to real time by + * a substantial fraction of a second. This means + * that we may accidently report that we are in + * the previous or next second. Use the GPS tow + * corrected to UTC instead. + */ + pp->hour = 0; + pp->minute = 0; + pp->usec = USEC_PER_SEC * modf(up->tow0,&tow); + pp->second = tow; + pp->second = (pp->second + SEC_PER_DAY - up->leap) + % SEC_PER_DAY; + /* + * Compute the offset into the current second + * by multiplying the elapsed count since the + * beginning of the second with the estimated period + * of the counter. The response is missing the + * least significant bits of the oscillator count. + * This does not reduce the accuracy of the result + * since device responses seem to occur at some + * large multiple of the oscillator frequency. + */ + if (PKT_SIZE(buf) >= 12) { + counter = GTOH32(buf,10) << CLK_SHIFT; + pp->usec += up->period * (counter - up->counter0); + while (pp->usec >= USEC_PER_SEC) { + pp->usec -= USEC_PER_SEC; + pp->second++; + } + up->high = TRUE; + } + else + up->high = FALSE; + if (!refclock_process(pp)) + refclock_report(peer,CEVNT_BADTIME); + return; + } +} + +/* + * An implementation of the Garmin link layer protocol. As defined + * in the specification, all packet ACKs and NACKs are done only + * at the link layer. + */ + +#define DLE 16 +#define ETX 3 + +#define GETBYTE(c,up,rp) { \ + if (rp != up->wp) { \ + (c) = up->buffer[rp++]; \ + rp = rp % SIZEOF(up->buffer); \ + } \ + else \ + return(0); \ +} + +#define GETBYTE_ESCAPED(c,up,rp) { \ + GETBYTE(c,up,rp); \ + if (c == DLE) \ + GETBYTE(c,up,rp); \ +} + +#define PUTBYTE(c,buf,len) { \ + buf[len++] = c; \ +} + +#define PUTBYTE_ESCAPED(c,buf,len) { \ + PUTBYTE(c,buf,len); \ + if (c == DLE) \ + PUTBYTE(c,buf,len); \ +} + +/* + * Write a packet to the GPS receiver. Add the proper header, + * trailer, and checksum. Perform DLE escaping as defined in the + * specification. + */ + +static +int +gwrite(int fd,uint8_x *buf) +{ + uint8_x c,sum,packet[PKT_MAX]; + uint16_x i,n,len; + + len = 0; + PUTBYTE(DLE,packet,len); + sum = PKT_PID(buf); + PUTBYTE(sum,packet,len); + n = PKT_SIZE(buf) + 2; + for (i = 1;i < n; i++) { + c = buf[i]; + PUTBYTE_ESCAPED(c,packet,len); + sum += c; + } + sum = 256 - sum; + PUTBYTE_ESCAPED(sum,packet,len); + PUTBYTE(DLE,packet,len); + PUTBYTE(ETX,packet,len); + return(write(fd,packet,len) == len); +} + +/* + * Attempt to parse whole packets. If successful, ACK the packet if + * necessary and return true. Bad packets are dropped and get NACKed + * if the PID is known. There is a subtle flaw in the protocol here. + * A NACK is susposed to return the PID. But what if the received + * PID byte was corrupted during transmission? We send back the + * PID we received, right or wrong. + */ + +static +int +parse(int fd,struct unit *up,uint8_x *buf,l_fp *t) +{ + uint8_x c,sum; + uint16_x i,n,rp; + static uint8_x response[] = { 0, 2, 0, 0 }; + /* This is partial list of packets that we should not ACK or NACK */ + static uint8_x list[] = { PID_ACK_BYTE, PID_NAK_BYTE, PID_PVT_DATA, + PID_UNDOC_PVT }; + + rp = up->rp; + do { + *t = up->ts[rp]; + GETBYTE(sum,up,rp); + if (sum != DLE) + continue; + GETBYTE(sum,up,rp); + if (sum == ETX) + continue; + } while (sum == DLE); + PKT_PID(buf) = sum; + GETBYTE_ESCAPED(n,up,rp); + sum += n; + PKT_SIZE(buf) = n; + n += 3; + for (i = 2;i < n; i++) { + GETBYTE_ESCAPED(c,up,rp); + sum += c; + buf[i] = c; + } + GETBYTE(c,up,rp); + PKT_PID(response) = PID_NAK_BYTE; + if (c == DLE) { + GETBYTE(c,up,rp); + if (c == ETX) + if (sum == 0) + PKT_PID(response) = PID_ACK_BYTE; + } + response[2] = PKT_PID(buf); + for (i = 0;i < SIZEOF(list); i++) + if (list[i] == PKT_PID(buf)) + break; + if (i == SIZEOF(list)) + gwrite(fd,response); + up->rp = rp; + if (PKT_PID(response) == PID_NAK_BYTE) + return(-1); + return(1); +} + +/* + * Collect the bytes as they come in from the serial port. When + * entire packets have been collected, pass them up to be processed. + * Bad packets are silently dropped as if they were not received. + */ + +static +void +gread(struct recvbuf *rbufp,callback func) +{ + uint8_x c,packet[PKT_MAX]; + uint16_x i; + l_fp t; + int overflow; + struct refclockproc *pp; + struct peer *peer; + struct unit *up; + + peer = (struct peer *)rbufp->recv_srcclock; + pp = peer->procptr; + up = (struct unit *)pp->unitptr; + overflow = FALSE; + for (i = 0;i < rbufp->recv_length; i++) { + c = rbufp->recv_buffer[i]; +#ifdef TIMESTAMPS + if (up->tcnt < sizeof(up->tv)) { + ((char *) &up->tv)[up->tcnt++] = c; + continue; + } + up->tcnt = 0; + up->tv.tv_sec += JAN_1970; + TVTOTS(&up->tv,&up->ts[up->wp]); +#else /* TIMESTAMPS */ + up->ts[up->wp] = rbufp->recv_time; +#endif /* TIMESTAMPS */ + up->buffer[up->wp++] = c; + up->wp = up->wp % SIZEOF(up->buffer); + overflow |= (up->wp == up->rp); + } + if (overflow) + up->rp = (up->wp + 1) % SIZEOF(up->buffer); + while (parse(pp->io.fd,up,packet,&t) > 0) + (*func)(rbufp,packet,&t); +} + +#else +int refclock_garmin_bs; +#endif /* REFCLOCK */