diff options
-rw-r--r-- | SConstruct | 3 | ||||
-rw-r--r-- | gps.h | 3 | ||||
-rw-r--r-- | ntpshm.h | 15 | ||||
-rw-r--r-- | ntpshmmon.c | 8 | ||||
-rw-r--r-- | ntpshmread.c | 4 | ||||
-rw-r--r-- | ntpshmwrite.c | 450 | ||||
-rw-r--r-- | timehint.c | 440 |
7 files changed, 480 insertions, 443 deletions
@@ -838,6 +838,7 @@ libgps_sources = [ "libgps_sock.c", "netlib.c", "ntpshmread.c", + "ntpshmwrite.c", "rtcm2_json.c", "rtcm3_json.c", "shared_json.c", @@ -1023,7 +1024,7 @@ gpsdlibs = ["-lgpsd"] + usblibs + bluezlibs + gpslibs # Source groups -gpsd_sources = ['gpsd.c','ntpshmwrite.c','shmexport.c','dbusexport.c'] +gpsd_sources = ['gpsd.c','timehint.c', 'shmexport.c','dbusexport.c'] if env['systemd']: gpsd_sources.append("sd_socket.c") @@ -1883,10 +1883,13 @@ struct policy_t { char remote[GPS_PATH_MAX]; /* ...if this was passthrough */ }; +#ifndef HAVE_TIMEDELTA struct timedelta_t { struct timespec real; struct timespec clock; }; +#define HAVE_TIMEDELTA +#endif /* HAVE_TIMEDELTA */ /* difference between timespecs in nanoseconds */ /* int is too small, avoid floats */ @@ -66,10 +66,21 @@ struct shm_stat_t { int leap; }; +#ifndef HAVE_TIMEDELTA + +struct timedelta_t { + struct timespec real; + struct timespec clock; +}; + +#define HAVE_TIMEDELTA +#endif /* HAVE_TIMEDELTA */ + struct shmTime /*@null@*/ *shm_get(int, bool, bool); -extern char *shm_name(const int); -enum segstat_t shm_query(/*@null@*/struct shmTime *, +extern char *ntp_name(const int); +enum segstat_t ntp_read(/*@null@*/struct shmTime *, /*@out@*/struct shm_stat_t *, const bool); +void ntp_write(volatile struct shmTime *, struct timedelta_t *, int, int); /* end */ diff --git a/ntpshmmon.c b/ntpshmmon.c index f272ebd2..38447960 100644 --- a/ntpshmmon.c +++ b/ntpshmmon.c @@ -84,7 +84,7 @@ int main(int argc, char **argv) struct shm_stat_t shm_stat; for (i = 0; i < NTPSEGMENTS; i++) { - enum segstat_t status = shm_query(segments[i], &shm_stat, false); + enum segstat_t status = ntp_read(segments[i], &shm_stat, false); if (verbose) fprintf(stderr, "unit %d status %d\n", i, status); switch(status) @@ -94,7 +94,7 @@ int main(int argc, char **argv) /*@-type@*//* splint is confused about struct timespec */ if (shm_stat.tvc.tv_sec != tick[i].tv_sec || shm_stat.tvc.tv_nsec != tick[i].tv_nsec) { printf("sample %s %ld.%09ld %ld.%09ld %ld.%09ld %d %3d\n", - shm_name(i), + ntp_name(i), shm_stat.tvc.tv_sec, shm_stat.tvc.tv_nsec, shm_stat.tvr.tv_sec, shm_stat.tvr.tv_nsec, shm_stat.tvt.tv_sec, shm_stat.tvt.tv_nsec, @@ -113,7 +113,7 @@ int main(int argc, char **argv) case BAD_MODE: /*@-mustfreefresh@*/ fprintf(stderr, "ntpshmmon: unknown mode %d on segment %s\n", - shm_stat.status, shm_name(i)); + shm_stat.status, ntp_name(i)); /*@+mustfreefresh@*/ break; case CLASH: @@ -122,7 +122,7 @@ int main(int argc, char **argv) default: /*@-mustfreefresh@*/ fprintf(stderr, "ntpshmmon: unknown status %d on segment %s\n", - status, shm_name(i)); + status, ntp_name(i)); /*@+mustfreefresh@*/ break; } diff --git a/ntpshmread.c b/ntpshmread.c index d230f663..1282f779 100644 --- a/ntpshmread.c +++ b/ntpshmread.c @@ -47,7 +47,7 @@ struct shmTime /*@null@*/ *shm_get(const int unit, const bool create, const bool } /*@-statictrans@*/ -char *shm_name(const int unit) +char *ntp_name(const int unit) /* return the name of a specified segment */ { static char name[5] = "NTP\0"; @@ -58,7 +58,7 @@ char *shm_name(const int unit) } /*@+statictrans@*/ -enum segstat_t shm_query(/*@null@*/struct shmTime *shm_in, /*@out@*/struct shm_stat_t *shm_stat, const bool consume) +enum segstat_t ntp_read(/*@null@*/struct shmTime *shm_in, /*@out@*/struct shm_stat_t *shm_stat, const bool consume) /* try to grab a sample from the specified SHM segment */ { volatile struct shmTime shmcopy, *shm = shm_in; diff --git a/ntpshmwrite.c b/ntpshmwrite.c index f03b152a..f001d9ea 100644 --- a/ntpshmwrite.c +++ b/ntpshmwrite.c @@ -1,11 +1,5 @@ /* - * ntpshmwrite.c - put time information in SHM segment for xntpd, or to chrony - * - * struct shmTime and getShmTime from file in the xntp distribution: - * sht.c - Testprogram for shared memory refclock - * - * Note that for easy debugging all logging from this file is prefixed - * with PPS or NTP. + * ntpshmwrite.c - put time information in SHM segment for ntpd * * This file is Copyright (c) 2010 by the GPSD project BSD terms apply: * see the file COPYING in the distribution root for details. @@ -24,235 +18,13 @@ #include <unistd.h> #endif /* S_SPLINT_S */ -#include "gpsd.h" - -#ifdef NTPSHM_ENABLE #include "ntpshm.h" +#include "compiler.h" -/* Note: you can start gpsd as non-root, and have it work with ntpd. - * However, it will then only use the ntpshm segments 2 3, and higher. - * - * Ntpd always runs as root (to be able to control the system clock). - * After that it often (depending on its host configuration) drops to run as - * user ntpd and group ntpd. - * - * As of February 2015 its rules for the creation of ntpshm segments are: - * - * Segments 0 and 1: permissions 0600, i.e. other programs can only - * read and write as root. - * - * Segments 2, 3, and higher: - * permissions 0666, i.e. other programs can read - * and write as any user. I.e.: if ntpd has been - * configured to use these segments, any - * unprivileged user is allowed to provide data - * for synchronisation. - * - * By default ntpd creates 0 segments (though the documentation is - * written in such a way as to suggest it creates 4). It can be - * configured to create up to 217. gpsd creates two segments for each - * device it can drive; by default this is 8 segments for 4 - * devices,but can be higher if it was compiled with a larger value of - * MAX_DEVICES. - * - * Started as root, gpsd does as ntpd when attaching (creating) the - * segments. In contrast to ntpd, which only attaches (creates) - * configured segments, gpsd creates all segments. Thus a gpsd will - * by default create eight segments 0-7 that an ntpd with default - * configuration does not watch. - * - * Started as non-root, gpsd will only attach (create) segments 2 and - * above, with permissions 0666. As the permissions are for any user, - * the creator does not matter. - * - * For each GPS module gpsd controls, it will use the attached ntpshm - * segments in pairs (for coarse clock and pps source, respectively) - * starting from the first found segments. I.e. started as root, one - * GPS will deliver data on all segments including 0 and 1; started as - * non-root, gpsd will be deliver data only on segments 2 and higher. - * - * Segments are allocated to activated devices on a first-come-first-served - * basis. A device's segment is marked unused when the device is closed and - * may be re-used by devices connected later. - * - * To debug, try looking at the live segments this way: - * - * ipcs -m - * - * results should look like this: - * ------ Shared Memory Segments -------- - * key shmid owner perms bytes nattch status - * 0x4e545030 0 root 700 96 2 - * 0x4e545031 32769 root 700 96 2 - * 0x4e545032 163842 root 666 96 1 - * 0x4e545033 196611 root 666 96 1 - * 0x4e545034 253555 root 666 96 1 - * 0x4e545035 367311 root 666 96 1 - * - * For a bit more data try this: - * cat /proc/sysvipc/shm - * - * If gpsd can not open the segments be sure you are not running SELinux - * or apparmor. - * - * if you see the shared segments (keys 1314148400 -- 1314148405), and - * no gpsd or ntpd is running, you can remove them like this: - * - * ipcrm -M 0x4e545030 - * ipcrm -M 0x4e545031 - * ipcrm -M 0x4e545032 - * ipcrm -M 0x4e545033 - * ipcrm -M 0x4e545034 - * ipcrm -M 0x4e545035 - * - * Removing these segments is usually not necessary, as the operating system - * garbage-collects them when they have no attached processes. - */ - -#define PPS_MIN_FIXES 3 /* # fixes to wait for before shipping PPS */ - -static /*@null@*/ volatile struct shmTime *getShmTime(struct gps_context_t *context, int unit) -{ - int shmid; - unsigned int perms; - volatile struct shmTime *p; - // set the SHM perms the way ntpd does - if (unit < 2) { - // we are root, be careful - perms = 0600; - } else { - // we are not root, try to work anyway - perms = 0666; - } - - /* - * Note: this call requires root under BSD, and possibly on - * well-secured Linux systems. This is why ntpshmwrite.context_init() has to be - * called before privilege-dropping. - */ - shmid = shmget((key_t) (NTPD_BASE + unit), - sizeof(struct shmTime), (int)(IPC_CREAT | perms)); - if (shmid == -1) { - gpsd_report(&context->errout, LOG_ERROR, - "NTPD shmget(%ld, %zd, %o) fail: %s\n", - (long int)(NTPD_BASE + unit), sizeof(struct shmTime), - (int)perms, strerror(errno)); - return NULL; - } - p = (struct shmTime *)shmat(shmid, 0, 0); - /*@ -mustfreefresh */ - if ((int)(long)p == -1) { - gpsd_report(&context->errout, LOG_ERROR, - "NTPD shmat failed: %s\n", - strerror(errno)); - return NULL; - } - gpsd_report(&context->errout, LOG_PROG, - "NTPD shmat(%d,0,0) succeeded, segment %d\n", - shmid, unit); - return p; - /*@ +mustfreefresh */ -} - -void ntpshm_context_init(struct gps_context_t *context) -/* Attach all NTP SHM segments. Called once at startup, while still root. */ -{ - int i; - - for (i = 0; i < NTPSHMSEGS; i++) { - // Only grab the first two when running as root. - if (2 <= i || 0 == getuid()) { - context->shmTime[i] = getShmTime(context, i); - } - } - memset(context->shmTimeInuse, 0, sizeof(context->shmTimeInuse)); -} - -/*@-unqualifiedtrans@*/ -static /*@null@*/ volatile struct shmTime *ntpshm_alloc(struct gps_context_t *context) -/* allocate NTP SHM segment. return its segment number, or -1 */ -{ - int i; - - for (i = 0; i < NTPSHMSEGS; i++) - if (context->shmTime[i] != NULL && !context->shmTimeInuse[i]) { - context->shmTimeInuse[i] = true; - - /* - * In case this segment gets sent to ntpd before an - * ephemeris is available, the LEAP_NOTINSYNC value will - * tell ntpd that this source is in a "clock alarm" state - * and should be ignored. The goal is to prevent ntpd - * from declaring the GPS a falseticker before it gets - * all its marbles together. - */ - memset((void *)context->shmTime[i], 0, sizeof(struct shmTime)); - context->shmTime[i]->mode = 1; - context->shmTime[i]->leap = LEAP_NOTINSYNC; - context->shmTime[i]->precision = -1; /* initially 0.5 sec */ - context->shmTime[i]->nsamples = 3; /* stages of median filter */ - - return context->shmTime[i]; - } - - return NULL; -} -/*@+unqualifiedtrans@*/ - -static bool ntpshm_free(struct gps_context_t * context, volatile struct shmTime *s) -/* free NTP SHM segment */ -{ - int i; - - for (i = 0; i < NTPSHMSEGS; i++) - if (s == context->shmTime[i]) { - context->shmTimeInuse[i] = false; - return true; - } - - return false; -} - -void ntpshm_session_init(struct gps_device_t *session) -{ - /*@-mustfreeonly@*/ -#ifdef NTPSHM_ENABLE - /* mark NTPD shared memory segments as unused */ - session->shm_clock = NULL; -#endif /* NTPSHM_ENABLE */ -#ifdef PPS_ENABLE - session->shm_pps = NULL; -#endif /* PPS_ENABLE */ - /*@+mustfreeonly@*/ -} - -int ntpshm_put(struct gps_device_t *session, volatile struct shmTime *shmseg, struct timedelta_t *td) +void ntp_write(volatile struct shmTime *shmseg, + struct timedelta_t *td, int precision, int leap_notify) /* put a received fix time into shared memory for NTP */ { - char real_str[TIMESPEC_LEN]; - char clock_str[TIMESPEC_LEN]; - /* - * shmTime is volatile to try to prevent C compiler from reordering - * writes, or optimizing some 'dead code'. but CPU cache may still - * write out of order if memory_barrier() is a no-op (our implementation - * isn't portable). - */ - volatile struct shmTime *shmTime = shmseg; - /* Any NMEA will be about -1 or -2. Garmin GPS-18/USB is around -6 or -7. */ - int precision = -1; /* default precision */ - - if (shmTime == NULL) { - gpsd_report(&session->context->errout, LOG_RAW, "NTPD missing shm\n"); - return 0; - } - -#ifdef PPS_ENABLE - /* ntpd sets -20 for PPS refclocks, thus -20 precision */ - /* TODO: if PPS over USB 1.1, then precision = -10 */ - if (shmTime == session->shm_pps) - precision = -20; -#endif /* PPS_ENABLE */ - /* we use the shmTime mode 1 protocol * * ntpd does this: @@ -268,214 +40,24 @@ int ntpshm_put(struct gps_device_t *session, volatile struct shmTime *shmseg, st * */ - shmTime->valid = 0; - shmTime->count++; + shmseg->valid = 0; + shmseg->count++; /* We need a memory barrier here to prevent write reordering by * the compiler or CPU cache */ memory_barrier(); /*@-type@*/ /* splint is confused about struct timespec */ - shmTime->clockTimeStampSec = (time_t)td->real.tv_sec; - shmTime->clockTimeStampUSec = (int)(td->real.tv_nsec/1000); - shmTime->clockTimeStampNSec = (unsigned)td->real.tv_nsec; - shmTime->receiveTimeStampSec = (time_t)td->clock.tv_sec; - shmTime->receiveTimeStampUSec = (int)(td->clock.tv_nsec/1000); - shmTime->receiveTimeStampNSec = (unsigned)td->clock.tv_nsec; + shmseg->clockTimeStampSec = (time_t)td->real.tv_sec; + shmseg->clockTimeStampUSec = (int)(td->real.tv_nsec/1000); + shmseg->clockTimeStampNSec = (unsigned)td->real.tv_nsec; + shmseg->receiveTimeStampSec = (time_t)td->clock.tv_sec; + shmseg->receiveTimeStampUSec = (int)(td->clock.tv_nsec/1000); + shmseg->receiveTimeStampNSec = (unsigned)td->clock.tv_nsec; /*@+type@*/ - shmTime->leap = session->context->leap_notify; - shmTime->precision = precision; + shmseg->leap = leap_notify; + shmseg->precision = precision; memory_barrier(); - shmTime->count++; - shmTime->valid = 1; - - /*@-type@*/ /* splint is confused about struct timespec */ - timespec_str( &td->real, real_str, sizeof(real_str) ); - timespec_str( &td->clock, clock_str, sizeof(clock_str) ); - gpsd_report(&session->context->errout, LOG_RAW, - "NTP ntpshm_put(%s %s) %s @ %s\n", - session->gpsdata.dev.path, - (precision == -20) ? "pps" : "clock", - real_str, clock_str); - /*@+type@*/ - - return 1; -} - -#ifdef PPS_ENABLE -#define SOCK_MAGIC 0x534f434b -struct sock_sample { - struct timeval tv; - double offset; - int pulse; - int leap; - // cppcheck-suppress unusedStructMember - int _pad; - int magic; /* must be SOCK_MAGIC */ -}; - -/*@-mustfreefresh@*/ -static void init_hook(struct gps_device_t *session) -/* for chrony SOCK interface, which allows nSec timekeeping */ -{ - /* open the chrony socket */ - char chrony_path[GPS_PATH_MAX]; - - session->chronyfd = -1; - if ( 0 == getuid() ) { - /* this case will fire on command-line devices; - * they're opened before priv-dropping. Matters because - * only root can use /var/run. - */ - (void)snprintf(chrony_path, sizeof (chrony_path), - "/var/run/chrony.%s.sock", basename(session->gpsdata.dev.path)); - } else { - (void)snprintf(chrony_path, sizeof (chrony_path), - "/tmp/chrony.%s.sock", basename(session->gpsdata.dev.path)); - } - - if (access(chrony_path, F_OK) != 0) { - gpsd_report(&session->context->errout, LOG_PROG, - "PPS chrony socket %s doesn't exist\n", chrony_path); - } else { - session->chronyfd = netlib_localsocket(chrony_path, SOCK_DGRAM); - if (session->chronyfd < 0) - gpsd_report(&session->context->errout, LOG_PROG, - "PPS connect chrony socket failed: %s, error: %d, errno: %d/%s\n", - chrony_path, session->chronyfd, errno, strerror(errno)); - else - gpsd_report(&session->context->errout, LOG_RAW, - "PPS using chrony socket: %s\n", chrony_path); - } -} -/*@+mustfreefresh@*/ - - -/* td is the real time and clock time of the edge */ -/* offset is actual_ts - clock_ts */ -static void chrony_send(struct gps_device_t *session, struct timedelta_t *td) -{ - char real_str[TIMESPEC_LEN]; - char clock_str[TIMESPEC_LEN]; - struct timespec offset; - struct sock_sample sample; - - /* chrony expects tv-sec since Jan 1970 */ - sample.pulse = 0; - sample.leap = session->context->leap_notify; - sample.magic = SOCK_MAGIC; - /*@-compdef@*/ - /*@-type@*//* splint is confused about struct timespec */ - /* chronyd wants a timeval, not a timspec, not to worry, it is - * just the top of the second */ - TSTOTV(&sample.tv, &td->clock); - /* calculate the offset as a timespec to not lose precision */ - TS_SUB( &offset, &td->real, &td->clock); - /* if tv_sec greater than 2 then tv_nsec loses precision, but - * not a big deal as slewing will bbe required */ - sample.offset = TSTONS( &offset ); - /*@+compdef@*/ - sample._pad = 0; - /*@+type@*/ - - /*@-type@*/ /* splint is confused about struct timespec */ - timespec_str( &td->real, real_str, sizeof(real_str) ); - timespec_str( &td->clock, clock_str, sizeof(clock_str) ); - gpsd_report(&session->context->errout, LOG_RAW, - "PPS chrony_send %s @ %s Offset: %0.9f\n", - real_str, clock_str, sample.offset); - /*@+type@*/ - (void)send(session->chronyfd, &sample, sizeof (sample), 0); -} - -static void wrap_hook(struct gps_device_t *session) -{ - if (session->chronyfd != -1) - (void)close(session->chronyfd); -} - -static /*@observer@*/ char *report_hook(struct gps_device_t *session, - struct timedelta_t *td) -/* ship the time of a PPS event to ntpd and/or chrony */ -{ - char *log1; - - if (!session->ship_to_ntpd) - return "skipped ship_to_ntp=0"; - - /* - * Only listen to PPS after several consecutive fixes, - * otherwise time may be inaccurate. (We know this is - * required on all Garmin and u-blox; safest to do it - * for all cases as we have no other general way to know - * if PPS is good. - * - * Not sure yet how to handle u-blox UBX_MODE_TMONLY - */ - if (session->fixcnt <= PPS_MIN_FIXES) - return "no fix"; - - log1 = "accepted"; - if ( 0 <= session->chronyfd ) { - log1 = "accepted chrony sock"; - chrony_send(session, td); - } - if (session->shm_pps != NULL) - (void)ntpshm_put(session, session->shm_pps, td); - - return log1; -} -#endif /* PPS_ENABLE */ - -/*@-mustfreeonly@*/ -void ntpshm_link_deactivate(struct gps_device_t *session) -/* release ntpshm storage for a session */ -{ - if (session->shm_clock != NULL) { - (void)ntpshm_free(session->context, session->shm_clock); - session->shm_clock = NULL; - } -#if defined(PPS_ENABLE) - if (session->shm_pps != NULL) { - pps_thread_deactivate(session); - (void)ntpshm_free(session->context, session->shm_pps); - session->shm_pps = NULL; - } -#endif /* PPS_ENABLE */ -} -/*@+mustfreeonly@*/ - -/*@-mustfreeonly@*/ -void ntpshm_link_activate(struct gps_device_t *session) -/* set up ntpshm storage for a session */ -{ - /* don't talk to NTP when we're running inside the test harness */ - if (session->sourcetype == source_pty) - return; - - /* allocate a shared-memory segment for "NMEA" time data */ - session->shm_clock = ntpshm_alloc(session->context); - - if (session->shm_clock == NULL) { - gpsd_report(&session->context->errout, LOG_INF, - "NTPD ntpshm_alloc() failed\n"); -#if defined(PPS_ENABLE) - } else if (session->sourcetype == source_usb || session->sourcetype == source_rs232) { - /* We also have the 1pps capability, allocate a shared-memory segment - * for the 1pps time data and launch a thread to capture the 1pps - * transitions - */ - if ((session->shm_pps = ntpshm_alloc(session->context)) == NULL) { - gpsd_report(&session->context->errout, LOG_INF, - "NTPD ntpshm_alloc(1) failed\n"); - } else { - init_hook(session); - session->thread_report_hook = report_hook; - session->thread_wrap_hook = wrap_hook; - pps_thread_activate(session); - } -#endif /* PPS_ENABLE */ - } + shmseg->count++; + shmseg->valid = 1; } -/*@+mustfreeonly@*/ -#endif /* NTPSHM_ENABLE */ /* end */ diff --git a/timehint.c b/timehint.c new file mode 100644 index 00000000..afc98637 --- /dev/null +++ b/timehint.c @@ -0,0 +1,440 @@ +/* + * timehint.c - put time information in SHM segment for ntpd, or to chrony + * + * Note that for easy debugging all logging from this file is prefixed + * with PPS or NTP. + * + * This file is Copyright (c) 2010 by the GPSD project BSD terms apply: + * see the file COPYING in the distribution root for details. + */ + +#include <string.h> +#include <libgen.h> +#include <stdbool.h> +#include <math.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#ifndef S_SPLINT_S +#include <sys/wait.h> +#include <sys/socket.h> +#include <unistd.h> +#endif /* S_SPLINT_S */ + +#include "gpsd.h" + +#ifdef NTPSHM_ENABLE +#include "ntpshm.h" + +/* Note: you can start gpsd as non-root, and have it work with ntpd. + * However, it will then only use the ntpshm segments 2 3, and higher. + * + * Ntpd always runs as root (to be able to control the system clock). + * After that it often (depending on its host configuration) drops to run as + * user ntpd and group ntpd. + * + * As of February 2015 its rules for the creation of ntpshm segments are: + * + * Segments 0 and 1: permissions 0600, i.e. other programs can only + * read and write as root. + * + * Segments 2, 3, and higher: + * permissions 0666, i.e. other programs can read + * and write as any user. I.e.: if ntpd has been + * configured to use these segments, any + * unprivileged user is allowed to provide data + * for synchronisation. + * + * By default ntpd creates 0 segments (though the documentation is + * written in such a way as to suggest it creates 4). It can be + * configured to create up to 217. gpsd creates two segments for each + * device it can drive; by default this is 8 segments for 4 + * devices,but can be higher if it was compiled with a larger value of + * MAX_DEVICES. + * + * Started as root, gpsd does as ntpd when attaching (creating) the + * segments. In contrast to ntpd, which only attaches (creates) + * configured segments, gpsd creates all segments. Thus a gpsd will + * by default create eight segments 0-7 that an ntpd with default + * configuration does not watch. + * + * Started as non-root, gpsd will only attach (create) segments 2 and + * above, with permissions 0666. As the permissions are for any user, + * the creator does not matter. + * + * For each GPS module gpsd controls, it will use the attached ntpshm + * segments in pairs (for coarse clock and pps source, respectively) + * starting from the first found segments. I.e. started as root, one + * GPS will deliver data on all segments including 0 and 1; started as + * non-root, gpsd will be deliver data only on segments 2 and higher. + * + * Segments are allocated to activated devices on a first-come-first-served + * basis. A device's segment is marked unused when the device is closed and + * may be re-used by devices connected later. + * + * To debug, try looking at the live segments this way: + * + * ipcs -m + * + * results should look like this: + * ------ Shared Memory Segments -------- + * key shmid owner perms bytes nattch status + * 0x4e545030 0 root 700 96 2 + * 0x4e545031 32769 root 700 96 2 + * 0x4e545032 163842 root 666 96 1 + * 0x4e545033 196611 root 666 96 1 + * 0x4e545034 253555 root 666 96 1 + * 0x4e545035 367311 root 666 96 1 + * + * For a bit more data try this: + * cat /proc/sysvipc/shm + * + * If gpsd can not open the segments be sure you are not running SELinux + * or apparmor. + * + * if you see the shared segments (keys 1314148400 -- 1314148405), and + * no gpsd or ntpd is running, you can remove them like this: + * + * ipcrm -M 0x4e545030 + * ipcrm -M 0x4e545031 + * ipcrm -M 0x4e545032 + * ipcrm -M 0x4e545033 + * ipcrm -M 0x4e545034 + * ipcrm -M 0x4e545035 + * + * Removing these segments is usually not necessary, as the operating system + * garbage-collects them when they have no attached processes. + */ + +#define PPS_MIN_FIXES 3 /* # fixes to wait for before shipping PPS */ + +static /*@null@*/ volatile struct shmTime *getShmTime(struct gps_context_t *context, int unit) +{ + int shmid; + unsigned int perms; + volatile struct shmTime *p; + // set the SHM perms the way ntpd does + if (unit < 2) { + // we are root, be careful + perms = 0600; + } else { + // we are not root, try to work anyway + perms = 0666; + } + + /* + * Note: this call requires root under BSD, and possibly on + * well-secured Linux systems. This is why ntpshmwrite.context_init() has to be + * called before privilege-dropping. + */ + shmid = shmget((key_t) (NTPD_BASE + unit), + sizeof(struct shmTime), (int)(IPC_CREAT | perms)); + if (shmid == -1) { + gpsd_report(&context->errout, LOG_ERROR, + "NTPD shmget(%ld, %zd, %o) fail: %s\n", + (long int)(NTPD_BASE + unit), sizeof(struct shmTime), + (int)perms, strerror(errno)); + return NULL; + } + p = (struct shmTime *)shmat(shmid, 0, 0); + /*@ -mustfreefresh */ + if ((int)(long)p == -1) { + gpsd_report(&context->errout, LOG_ERROR, + "NTPD shmat failed: %s\n", + strerror(errno)); + return NULL; + } + gpsd_report(&context->errout, LOG_PROG, + "NTPD shmat(%d,0,0) succeeded, segment %d\n", + shmid, unit); + return p; + /*@ +mustfreefresh */ +} + +void ntpshm_context_init(struct gps_context_t *context) +/* Attach all NTP SHM segments. Called once at startup, while still root. */ +{ + int i; + + for (i = 0; i < NTPSHMSEGS; i++) { + // Only grab the first two when running as root. + if (2 <= i || 0 == getuid()) { + context->shmTime[i] = getShmTime(context, i); + } + } + memset(context->shmTimeInuse, 0, sizeof(context->shmTimeInuse)); +} + +/*@-unqualifiedtrans@*/ +static /*@null@*/ volatile struct shmTime *ntpshm_alloc(struct gps_context_t *context) +/* allocate NTP SHM segment. return its segment number, or -1 */ +{ + int i; + + for (i = 0; i < NTPSHMSEGS; i++) + if (context->shmTime[i] != NULL && !context->shmTimeInuse[i]) { + context->shmTimeInuse[i] = true; + + /* + * In case this segment gets sent to ntpd before an + * ephemeris is available, the LEAP_NOTINSYNC value will + * tell ntpd that this source is in a "clock alarm" state + * and should be ignored. The goal is to prevent ntpd + * from declaring the GPS a falseticker before it gets + * all its marbles together. + */ + memset((void *)context->shmTime[i], 0, sizeof(struct shmTime)); + context->shmTime[i]->mode = 1; + context->shmTime[i]->leap = LEAP_NOTINSYNC; + context->shmTime[i]->precision = -1; /* initially 0.5 sec */ + context->shmTime[i]->nsamples = 3; /* stages of median filter */ + + return context->shmTime[i]; + } + + return NULL; +} +/*@+unqualifiedtrans@*/ + +static bool ntpshm_free(struct gps_context_t * context, volatile struct shmTime *s) +/* free NTP SHM segment */ +{ + int i; + + for (i = 0; i < NTPSHMSEGS; i++) + if (s == context->shmTime[i]) { + context->shmTimeInuse[i] = false; + return true; + } + + return false; +} + +void ntpshm_session_init(struct gps_device_t *session) +{ + /*@-mustfreeonly@*/ +#ifdef NTPSHM_ENABLE + /* mark NTPD shared memory segments as unused */ + session->shm_clock = NULL; +#endif /* NTPSHM_ENABLE */ +#ifdef PPS_ENABLE + session->shm_pps = NULL; +#endif /* PPS_ENABLE */ + /*@+mustfreeonly@*/ +} + +int ntpshm_put(struct gps_device_t *session, volatile struct shmTime *shmseg, struct timedelta_t *td) +/* put a received fix time into shared memory for NTP */ +{ + char real_str[TIMESPEC_LEN]; + char clock_str[TIMESPEC_LEN]; + + /* Any NMEA will be about -1 or -2. Garmin GPS-18/USB is around -6 or -7. */ + int precision = -1; /* default precision */ + + if (shmseg == NULL) { + gpsd_report(&session->context->errout, LOG_RAW, "NTPD missing shm\n"); + return 0; + } + +#ifdef PPS_ENABLE + /* ntpd sets -20 for PPS refclocks, thus -20 precision */ + /* TODO: if PPS over USB 1.1, then precision = -10 */ + if (shmseg == session->shm_pps) + precision = -20; +#endif /* PPS_ENABLE */ + + ntp_write(shmseg, td, precision, session->context->leap_notify); + + /*@-type@*/ /* splint is confused about struct timespec */ + timespec_str( &td->real, real_str, sizeof(real_str) ); + timespec_str( &td->clock, clock_str, sizeof(clock_str) ); + gpsd_report(&session->context->errout, LOG_RAW, + "NTP ntpshm_put(%s %s) %s @ %s\n", + session->gpsdata.dev.path, + (precision == -20) ? "pps" : "clock", + real_str, clock_str); + /*@+type@*/ + + return 1; +} + +#ifdef PPS_ENABLE +#define SOCK_MAGIC 0x534f434b +struct sock_sample { + struct timeval tv; + double offset; + int pulse; + int leap; + // cppcheck-suppress unusedStructMember + int _pad; + int magic; /* must be SOCK_MAGIC */ +}; + +/*@-mustfreefresh@*/ +static void init_hook(struct gps_device_t *session) +/* for chrony SOCK interface, which allows nSec timekeeping */ +{ + /* open the chrony socket */ + char chrony_path[GPS_PATH_MAX]; + + session->chronyfd = -1; + if ( 0 == getuid() ) { + /* this case will fire on command-line devices; + * they're opened before priv-dropping. Matters because + * only root can use /var/run. + */ + (void)snprintf(chrony_path, sizeof (chrony_path), + "/var/run/chrony.%s.sock", basename(session->gpsdata.dev.path)); + } else { + (void)snprintf(chrony_path, sizeof (chrony_path), + "/tmp/chrony.%s.sock", basename(session->gpsdata.dev.path)); + } + + if (access(chrony_path, F_OK) != 0) { + gpsd_report(&session->context->errout, LOG_PROG, + "PPS chrony socket %s doesn't exist\n", chrony_path); + } else { + session->chronyfd = netlib_localsocket(chrony_path, SOCK_DGRAM); + if (session->chronyfd < 0) + gpsd_report(&session->context->errout, LOG_PROG, + "PPS connect chrony socket failed: %s, error: %d, errno: %d/%s\n", + chrony_path, session->chronyfd, errno, strerror(errno)); + else + gpsd_report(&session->context->errout, LOG_RAW, + "PPS using chrony socket: %s\n", chrony_path); + } +} +/*@+mustfreefresh@*/ + + +/* td is the real time and clock time of the edge */ +/* offset is actual_ts - clock_ts */ +static void chrony_send(struct gps_device_t *session, struct timedelta_t *td) +{ + char real_str[TIMESPEC_LEN]; + char clock_str[TIMESPEC_LEN]; + struct timespec offset; + struct sock_sample sample; + + /* chrony expects tv-sec since Jan 1970 */ + sample.pulse = 0; + sample.leap = session->context->leap_notify; + sample.magic = SOCK_MAGIC; + /*@-compdef@*/ + /*@-type@*//* splint is confused about struct timespec */ + /* chronyd wants a timeval, not a timspec, not to worry, it is + * just the top of the second */ + TSTOTV(&sample.tv, &td->clock); + /* calculate the offset as a timespec to not lose precision */ + TS_SUB( &offset, &td->real, &td->clock); + /* if tv_sec greater than 2 then tv_nsec loses precision, but + * not a big deal as slewing will bbe required */ + sample.offset = TSTONS( &offset ); + /*@+compdef@*/ + sample._pad = 0; + /*@+type@*/ + + /*@-type@*/ /* splint is confused about struct timespec */ + timespec_str( &td->real, real_str, sizeof(real_str) ); + timespec_str( &td->clock, clock_str, sizeof(clock_str) ); + gpsd_report(&session->context->errout, LOG_RAW, + "PPS chrony_send %s @ %s Offset: %0.9f\n", + real_str, clock_str, sample.offset); + /*@+type@*/ + (void)send(session->chronyfd, &sample, sizeof (sample), 0); +} + +static void wrap_hook(struct gps_device_t *session) +{ + if (session->chronyfd != -1) + (void)close(session->chronyfd); +} + +static /*@observer@*/ char *report_hook(struct gps_device_t *session, + struct timedelta_t *td) +/* ship the time of a PPS event to ntpd and/or chrony */ +{ + char *log1; + + if (!session->ship_to_ntpd) + return "skipped ship_to_ntp=0"; + + /* + * Only listen to PPS after several consecutive fixes, + * otherwise time may be inaccurate. (We know this is + * required on all Garmin and u-blox; safest to do it + * for all cases as we have no other general way to know + * if PPS is good. + * + * Not sure yet how to handle u-blox UBX_MODE_TMONLY + */ + if (session->fixcnt <= PPS_MIN_FIXES) + return "no fix"; + + log1 = "accepted"; + if ( 0 <= session->chronyfd ) { + log1 = "accepted chrony sock"; + chrony_send(session, td); + } + if (session->shm_pps != NULL) + (void)ntpshm_put(session, session->shm_pps, td); + + return log1; +} +#endif /* PPS_ENABLE */ + +/*@-mustfreeonly@*/ +void ntpshm_link_deactivate(struct gps_device_t *session) +/* release ntpshm storage for a session */ +{ + if (session->shm_clock != NULL) { + (void)ntpshm_free(session->context, session->shm_clock); + session->shm_clock = NULL; + } +#if defined(PPS_ENABLE) + if (session->shm_pps != NULL) { + pps_thread_deactivate(session); + (void)ntpshm_free(session->context, session->shm_pps); + session->shm_pps = NULL; + } +#endif /* PPS_ENABLE */ +} +/*@+mustfreeonly@*/ + +/*@-mustfreeonly@*/ +void ntpshm_link_activate(struct gps_device_t *session) +/* set up ntpshm storage for a session */ +{ + /* don't talk to NTP when we're running inside the test harness */ + if (session->sourcetype == source_pty) + return; + + /* allocate a shared-memory segment for "NMEA" time data */ + session->shm_clock = ntpshm_alloc(session->context); + + if (session->shm_clock == NULL) { + gpsd_report(&session->context->errout, LOG_INF, + "NTPD ntpshm_alloc() failed\n"); +#if defined(PPS_ENABLE) + } else if (session->sourcetype == source_usb || session->sourcetype == source_rs232) { + /* We also have the 1pps capability, allocate a shared-memory segment + * for the 1pps time data and launch a thread to capture the 1pps + * transitions + */ + if ((session->shm_pps = ntpshm_alloc(session->context)) == NULL) { + gpsd_report(&session->context->errout, LOG_INF, + "NTPD ntpshm_alloc(1) failed\n"); + } else { + init_hook(session); + session->thread_report_hook = report_hook; + session->thread_wrap_hook = wrap_hook; + pps_thread_activate(session); + } +#endif /* PPS_ENABLE */ + } +} +/*@+mustfreeonly@*/ + +#endif /* NTPSHM_ENABLE */ +/* end */ |