/*
* Copyright (c) 1997, 1998, 2003
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Lawrence Berkeley Laboratory.
* 4. The name of the University may not be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
/* This clock *REQUIRES* the PPS API to be available */
#if defined(REFCLOCK) && defined(CLOCK_JUPITER) && defined(HAVE_PPSAPI)
/*
* This driver supports the Rockwell Jupiter GPS Receiver board
* adapted to precision timing applications. It requires the
* ppsclock line discipline or streams module described in the
* Line Disciplines and Streams Drivers page. It also requires a
* gadget box and 1-PPS level converter, such as described in the
* Pulse-per-second (PPS) Signal Interfacing page.
*
* It may work (with minor modifications) with other Rockwell GPS
* receivers such as the CityTracker.
*/
/*
* GPS Definitions
*/
#define DEVICE "/dev/gps%d" /* device name and unit */
#define SPEED232 B9600 /* baud */
/*
* Radio interface parameters
*/
#define PRECISION (-18) /* precision assumed (about 4 us) */
#define REFID "GPS\0" /* reference id */
#define DESCRIPTION "Rockwell Jupiter GPS Receiver" /* who we are */
#define DEFFUDGETIME 0 /* default fudge time (ms) */
/* Unix timestamp for the GPS epoch: January 6, 1980 */
#define GPS_EPOCH 315964800
/* Rata Die Number of first day of GPS epoch. This is the number of days
* since 0000-12-31 to 1980-01-06 in the proleptic Gregorian Calendar.
*/
#define RDN_GPS_EPOCH (4*146097 + 138431 + 1)
/* Double short to unsigned int */
#define DS2UI(p) ((getshort((p)[1]) << 16) | getshort((p)[0]))
/* Double short to signed int */
#define DS2I(p) ((getshort((p)[1]) << 16) | getshort((p)[0]))
/* One week's worth of seconds */
#define WEEKSECS (7 * 24 * 60 * 60)
/*
* Jupiter unit control structure.
*/
struct instance {
struct peer *peer; /* peer */
pps_params_t pps_params; /* pps parameters */
pps_info_t pps_info; /* last pps data */
pps_handle_t pps_handle; /* pps handle */
u_int assert; /* pps edge to use */
u_int hardpps; /* enable kernel mode */
l_fp rcv_pps; /* last pps timestamp */
l_fp rcv_next; /* rcv time of next reftime */
TGpsDatum ref_next; /* next GPS time stamp to use with PPS */
TGpsDatum piv_next; /* pivot for week date unfolding */
uint16_t piv_hold; /* TTL for pivot value */
uint16_t rcvtout; /* receive timeout ticker */
int wantid; /* don't reconfig on channel id msg */
u_int moving; /* mobile platform? */
u_char sloppyclockflag; /* fudge flags */
u_short sbuf[512]; /* local input buffer */
int ssize; /* space used in sbuf */
};
/*
* jupiter_start - open the devices and initialize data for processing
*/
static int
jupiter_start(
int unit,
struct peer *peer
)
{
struct refclockproc * const pp = peer->procptr;
struct instance * up;
int fd;
char gpsdev[20];
/*
* Open serial port
*/
snprintf(gpsdev, sizeof(gpsdev), DEVICE, unit);
fd = refclock_open(&peer->srcadr, gpsdev, SPEED232, LDISC_RAW);
if (fd <= 0) {
jupiter_debug(peer, "jupiter_start", "open %s: %s",
gpsdev, strerror(errno));
return (0);
}
up->assert = 1;
up->hardpps = 0;
/*
* Start the PPSAPI interface if it is there. Default to use
* the assert edge and do not enable the kernel hardpps.
*/
if (time_pps_create(fd, &up->pps_handle) < 0) {
up->pps_handle = 0;
msyslog(LOG_ERR,
"refclock_jupiter: time_pps_create failed: %m");
}
else if (!jupiter_ppsapi(up))
goto clean_up;
/* Ensure the receiver is properly configured */
if (!jupiter_config(up))
goto clean_up;
jupiter_pps(up); /* get current PPS state */
return (1);
/*
* If we have new samples since last poll, everything is fine.
* if not, blarb loudly.
*/
if (pp->coderecv != pp->codeproc) {
refclock_receive(peer);
refclock_report(peer, CEVNT_NOMINAL);
} else {
refclock_report(peer, CEVNT_TIMEOUT);
/* Request the receiver id to trigger a reconfig */
jupiter_reqonemsg(up, JUPITER_O_ID);
up->wantid = 0;
}
}
/* While there's at least a header and we parse an intact message */
while (up->ssize > (int)sizeof(*hp) && (cc = jupiter_recv(up)) > 0) {
hp = (struct jheader *)up->sbuf;
sp = (u_short *)(hp + 1);
size = cc - sizeof(*hp);
switch (getshort(hp->id)) {
case JUPITER_O_PULSE:
/* first see if we can push another sample: */
jupiter_checkpps(pp, up);
if (size != sizeof(struct jpulse)) {
jupiter_debug(peer, __func__,
"pulse: len %d != %u",
size, (int)sizeof(struct jpulse));
refclock_report(peer, CEVNT_BADREPLY);
break;
}
/* Parse timecode (even when there's no pps)
*
* There appears to be a firmware bug related to
* the pulse message; in addition to the one per
* second messages, we get an extra pulse
* message once an hour (on the anniversary of
* the cold start). It seems to come 200 ms
* after the one requested.
*
* But since we feed samples only when a new PPS
* pulse is found we can simply ignore that and
* aggregate/update any existing timing message.
*/
if ((cp = jupiter_parse_t(up, sp, rbufp->recv_time)) != NULL) {
jupiter_debug(peer, __func__,
"pulse: %s", cp);
}
break;
case JUPITER_O_GPOS:
if (size != sizeof(struct jgpos)) {
jupiter_debug(peer, __func__,
"gpos: len %d != %u",
size, (int)sizeof(struct jgpos));
refclock_report(peer, CEVNT_BADREPLY);
break;
}
/* Toss if not designated "valid" by the gps.
* !!NOTE!! do *not* kill data received so far!
*/
if ((flags & JUPITER_O_PULSE_VALID) == 0) {
refclock_report(up->peer, CEVNT_BADTIME);
return ("time mark not valid");
}
up->rcv_next = rcvtime; /* remember when this happened */
/* The timecode is presented as seconds into the current GPS week */
sweek = DS2UI(jp->sweek) % WEEKSECS;
/* check if we have to apply the UTC offset ourselves */
if ((flags & JUPITER_O_PULSE_UTC) == 0) {
struct timespec tofs;
tofs.tv_sec = getshort(jp->offs);
tofs.tv_nsec = DS2I(jp->offns);
fofs = tspec_intv_to_lfp(tofs);
L_NEG(&fofs);
} else {
ZERO(fofs);
}
/*
* If we don't know the current GPS week, calculate it from the
* current time. (It's too bad they didn't include this
* important value in the pulse message).
*
* So we pick the pivot value from the other messages like gpos
* or chan if we can. Of course, the PULSE message can be in UTC
* or GPS time scale, and the other messages are simply always
* GPS time.
*
* But as long as the difference between the time stamps is less
* than a half week, the unfolding of a week time is unambigeous
* and well suited for the problem we have here. And we won't
* see *that* many leap seconds, ever.
*/
if (up->piv_next.weeks) {
up->ref_next = gpscal_from_weektime2(
sweek, fofs, &up->piv_next);
up->piv_next = up->ref_next;
} else {
up->ref_next = gpscal_from_weektime1(
sweek, fofs, rcvtime);
}
if (jg->navval != 0) {
/*
* Solution not valid. Use caution and refuse
* to determine GPS week from this message.
*/
return ("Navigation solution not valid");
}
va_start(ap, fmt);
/*
* Print debug message to stdout
* In the future, we may want to get get more creative...
*/
mvsnprintf(buffer, sizeof(buffer), fmt, ap);
record_clock_stats(&peer->srcadr, buffer);
#ifdef DEBUG
if (debug) {
printf("%s: %s\n", function, buffer);
fflush(stdout);
}
#endif
va_end(ap);
}
/* Checksum and transmit a message to the Jupiter */
static char *
jupiter_send(
struct instance * const up,
struct jheader * hp
)
{
u_int len, size;
ssize_t cc;
u_short *sp;
static char errstr[132];
sum = 0;
while (len-- > 0) {
x = *sp++;
sum += getshort(x);
}
return (~sum + 1);
}
/* Return the size of the next message (or zero if we don't have it all yet) */
static int
jupiter_recv(
struct instance * const up
)
{
int n, len, size, cc;
struct jheader *hp;
u_char *bp;
u_short *sp;
/* Must have at least a header's worth */
cc = sizeof(*hp);
size = up->ssize;
if (size < cc)
return (0);
/* Search for the sync short if missing */
sp = up->sbuf;
hp = (struct jheader *)sp;
if (getshort(hp->sync) != JUPITER_SYNC) {
/* Wasn't at the front, sync up */
jupiter_debug(up->peer, __func__, "syncing");
bp = (u_char *)sp;
n = size;
while (n >= 2) {
if (bp[0] != (JUPITER_SYNC & 0xff)) {
/*
jupiter_debug(up->peer, __func__,
"{0x%x}", bp[0]);
*/
++bp;
--n;
continue;
}
if (bp[1] == ((JUPITER_SYNC >> 8) & 0xff))
break;
/*
jupiter_debug(up->peer, __func__,
"{0x%x 0x%x}", bp[0], bp[1]);
*/
bp += 2;
n -= 2;
}
/*
jupiter_debug(up->peer, __func__, "\n");
*/
/* Shuffle data to front of input buffer */
if (n > 0)
memcpy(sp, bp, n);
size = n;
up->ssize = size;
if (size < cc || hp->sync != JUPITER_SYNC)
return (0);
}
if (jupiter_cksum(sp, (cc / sizeof(u_short) - 1)) !=
getshort(hp->hsum)) {
jupiter_debug(up->peer, __func__, "bad header checksum!");
/* This is drastic but checksum errors should be rare */
up->ssize = 0;
return (0);
}
/* Check for a payload */
len = getshort(hp->len);
if (len > 0) {
n = (len + 1) * sizeof(u_short);
/* Not enough data yet */
if (size < cc + n)
return (0);
/* Check payload checksum */
sp = (u_short *)(hp + 1);
if (jupiter_cksum(sp, len) != getshort(sp[len])) {
jupiter_debug(up->peer,
__func__, "bad payload checksum!");
/* This is drastic but checksum errors should be rare */
up->ssize = 0;
return (0);
}
cc += n;
}
return (cc);
}
#else /* not (REFCLOCK && CLOCK_JUPITER && HAVE_PPSAPI) */
NONEMPTY_TRANSLATION_UNIT
#endif /* not (REFCLOCK && CLOCK_JUPITER && HAVE_PPSAPI) */