summaryrefslogtreecommitdiff
path: root/ntpd/refclock_shm.c
diff options
context:
space:
mode:
Diffstat (limited to 'ntpd/refclock_shm.c')
-rw-r--r--ntpd/refclock_shm.c571
1 files changed, 571 insertions, 0 deletions
diff --git a/ntpd/refclock_shm.c b/ntpd/refclock_shm.c
new file mode 100644
index 0000000..6540e6f
--- /dev/null
+++ b/ntpd/refclock_shm.c
@@ -0,0 +1,571 @@
+/*
+ * refclock_shm - clock driver for utc via shared memory
+ * - under construction -
+ * To add new modes: Extend or union the shmTime-struct. Do not
+ * extend/shrink size, because otherwise existing implementations
+ * will specify wrong size of shared memory-segment
+ * PB 18.3.97
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "ntp_types.h"
+
+#if defined(REFCLOCK) && defined(CLOCK_SHM)
+
+#include "ntpd.h"
+#undef fileno
+#include "ntp_io.h"
+#undef fileno
+#include "ntp_refclock.h"
+#undef fileno
+#include "timespecops.h"
+#undef fileno
+#include "ntp_stdlib.h"
+
+#undef fileno
+#include <ctype.h>
+#undef fileno
+
+#ifndef SYS_WINNT
+# include <sys/ipc.h>
+# include <sys/shm.h>
+# include <assert.h>
+# include <unistd.h>
+# include <stdio.h>
+#endif
+
+/*
+ * This driver supports a reference clock attached thru shared memory
+ */
+
+/*
+ * SHM interface definitions
+ */
+#define PRECISION (-1) /* precision assumed (0.5 s) */
+#define REFID "SHM" /* reference ID */
+#define DESCRIPTION "SHM/Shared memory interface"
+
+#define NSAMPLES 3 /* stages of median filter */
+
+/*
+ * Function prototypes
+ */
+static int shm_start (int unit, struct peer *peer);
+static void shm_shutdown (int unit, struct peer *peer);
+static void shm_poll (int unit, struct peer *peer);
+static void shm_timer (int unit, struct peer *peer);
+static void shm_peek (int unit, struct peer *peer);
+static void shm_clockstats (int unit, struct peer *peer);
+static void shm_control (int unit, const struct refclockstat * in_st,
+ struct refclockstat * out_st, struct peer *peer);
+
+/*
+ * Transfer vector
+ */
+struct refclock refclock_shm = {
+ shm_start, /* start up driver */
+ shm_shutdown, /* shut down driver */
+ shm_poll, /* transmit poll message */
+ shm_control, /* control settings */
+ noentry, /* not used: init */
+ noentry, /* not used: buginfo */
+ shm_timer, /* once per second */
+};
+
+struct shmTime {
+ int mode; /* 0 - if valid is set:
+ * use values,
+ * clear valid
+ * 1 - if valid is set:
+ * if count before and after read of values is equal,
+ * use values
+ * clear valid
+ */
+ volatile int count;
+ time_t clockTimeStampSec;
+ int clockTimeStampUSec;
+ time_t receiveTimeStampSec;
+ int receiveTimeStampUSec;
+ int leap;
+ int precision;
+ int nsamples;
+ volatile int valid;
+ unsigned clockTimeStampNSec; /* Unsigned ns timestamps */
+ unsigned receiveTimeStampNSec; /* Unsigned ns timestamps */
+ int dummy[8];
+};
+
+struct shmunit {
+ struct shmTime *shm; /* pointer to shared memory segment */
+
+ /* debugging/monitoring counters - reset when printed */
+ int ticks; /* number of attempts to read data*/
+ int good; /* number of valid samples */
+ int notready; /* number of peeks without data ready */
+ int bad; /* number of invalid samples */
+ int clash; /* number of access clashes while reading */
+
+ time_t max_delta; /* difference limit */
+ time_t max_delay; /* age/stale limit */
+};
+
+
+struct shmTime *getShmTime(int);
+
+struct shmTime *getShmTime (int unit) {
+#ifndef SYS_WINNT
+ int shmid=0;
+
+ /* 0x4e545030 is NTP0.
+ * Big units will give non-ascii but that's OK
+ * as long as everybody does it the same way.
+ */
+ shmid=shmget (0x4e545030 + unit, sizeof (struct shmTime),
+ IPC_CREAT | ((unit < 2) ? 0600 : 0666));
+ if (shmid == -1) { /* error */
+ msyslog(LOG_ERR, "SHM shmget (unit %d): %m", unit);
+ return 0;
+ }
+ else { /* no error */
+ struct shmTime *p = (struct shmTime *)shmat (shmid, 0, 0);
+ if (p == (struct shmTime *)-1) { /* error */
+ msyslog(LOG_ERR, "SHM shmat (unit %d): %m", unit);
+ return 0;
+ }
+ return p;
+ }
+#else
+ char buf[10];
+ LPSECURITY_ATTRIBUTES psec = 0;
+ HANDLE shmid = 0;
+ SECURITY_DESCRIPTOR sd;
+ SECURITY_ATTRIBUTES sa;
+
+ snprintf(buf, sizeof(buf), "NTP%d", unit);
+ if (unit >= 2) { /* world access */
+ if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) {
+ msyslog(LOG_ERR,"SHM InitializeSecurityDescriptor (unit %d): %m", unit);
+ return 0;
+ }
+ if (!SetSecurityDescriptorDacl(&sd, 1, 0, 0)) {
+ msyslog(LOG_ERR, "SHM SetSecurityDescriptorDacl (unit %d): %m", unit);
+ return 0;
+ }
+ sa.nLength=sizeof (SECURITY_ATTRIBUTES);
+ sa.lpSecurityDescriptor = &sd;
+ sa.bInheritHandle = 0;
+ psec = &sa;
+ }
+ shmid = CreateFileMapping ((HANDLE)0xffffffff, psec, PAGE_READWRITE,
+ 0, sizeof (struct shmTime), buf);
+ if (!shmid) { /*error*/
+ char buf[1000];
+
+ FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM,
+ 0, GetLastError (), 0, buf, sizeof (buf), 0);
+ msyslog(LOG_ERR, "SHM CreateFileMapping (unit %d): %s", unit, buf);
+ return 0;
+ } else {
+ struct shmTime *p = (struct shmTime *) MapViewOfFile (shmid,
+ FILE_MAP_WRITE, 0, 0, sizeof (struct shmTime));
+ if (p == 0) { /*error*/
+ char buf[1000];
+
+ FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM,
+ 0, GetLastError (), 0, buf, sizeof (buf), 0);
+ msyslog(LOG_ERR,"SHM MapViewOfFile (unit %d): %s", unit, buf)
+ return 0;
+ }
+ return p;
+ }
+#endif
+}
+/*
+ * shm_start - attach to shared memory
+ */
+static int
+shm_start(
+ int unit,
+ struct peer *peer
+ )
+{
+ struct refclockproc *pp;
+ struct shmunit *up;
+
+ pp = peer->procptr;
+ pp->io.clock_recv = noentry;
+ pp->io.srcclock = peer;
+ pp->io.datalen = 0;
+ pp->io.fd = -1;
+
+ up = emalloc_zero(sizeof(*up));
+
+ up->shm = getShmTime(unit);
+
+ /*
+ * Initialize miscellaneous peer variables
+ */
+ memcpy((char *)&pp->refid, REFID, 4);
+ if (up->shm != 0) {
+ pp->unitptr = up;
+ up->shm->precision = PRECISION;
+ peer->precision = up->shm->precision;
+ up->shm->valid = 0;
+ up->shm->nsamples = NSAMPLES;
+ pp->clockdesc = DESCRIPTION;
+ /* items to be changed later in 'shm_control()': */
+ up->max_delay = 5;
+ up->max_delta = 4*3600;
+ return 1;
+ } else {
+ free(up);
+ pp->unitptr = NULL;
+ return 0;
+ }
+}
+
+
+/*
+ * shm_control - configure flag1/time2 params
+ *
+ * These are not yet available during 'shm_start', so we have to do any
+ * pre-computations we want to avoid during regular poll/timer callbacks
+ * in this callback.
+ */
+static void
+shm_control(
+ int unit,
+ const struct refclockstat * in_st,
+ struct refclockstat * out_st,
+ struct peer * peer
+ )
+{
+ struct refclockproc *pp;
+ struct shmunit *up;
+
+ pp = peer->procptr;
+ up = pp->unitptr;
+
+ if (NULL == up)
+ return;
+ if (pp->sloppyclockflag & CLK_FLAG1)
+ up->max_delta = 0;
+ else if (pp->fudgetime2 < 1. || pp->fudgetime2 > 86400.)
+ up->max_delta = 4*3600;
+ else
+ up->max_delta = (time_t)floor(pp->fudgetime2 + 0.5);
+}
+
+
+/*
+ * shm_shutdown - shut down the clock
+ */
+static void
+shm_shutdown(
+ int unit,
+ struct peer *peer
+ )
+{
+ struct refclockproc *pp;
+ struct shmunit *up;
+
+ pp = peer->procptr;
+ up = pp->unitptr;
+
+ if (NULL == up)
+ return;
+#ifndef SYS_WINNT
+ /* HMS: shmdt() wants char* or const void * */
+ (void) shmdt ((char *)up->shm);
+#else
+ UnmapViewOfFile (up->shm);
+#endif
+ free(up);
+}
+
+
+/*
+ * shm_timer - called every second
+ */
+static void
+shm_timer(int unit, struct peer *peer)
+{
+ shm_peek(unit, peer);
+}
+
+
+/*
+ * shm_poll - called by the transmit procedure
+ */
+static void
+shm_poll(
+ int unit,
+ struct peer *peer
+ )
+{
+ struct refclockproc *pp;
+ struct shmunit *up;
+ int major_error;
+
+ pp = peer->procptr;
+ up = pp->unitptr;
+
+ pp->polls++;
+
+ /* get dominant reason if we have no samples at all */
+ major_error = max(up->notready, up->bad);
+ major_error = max(major_error, up->clash);
+
+ /*
+ * Process median filter samples. If none received, see what
+ * happened, tell the core and keep going.
+ */
+ if (pp->coderecv != pp->codeproc) {
+ /* have some samples, everything OK */
+ pp->lastref = pp->lastrec;
+ refclock_receive(peer);
+ } else if (NULL == up->shm) { /* is this possible at all? */
+ /* we're out of business without SHM access */
+ refclock_report(peer, CEVNT_FAULT);
+ } else if (major_error == up->clash) {
+ /* too many collisions is like a bad signal */
+ refclock_report(peer, CEVNT_PROP);
+ } else if (major_error == up->bad) {
+ /* too much stale/bad/garbled data */
+ refclock_report(peer, CEVNT_BADREPLY);
+ } else {
+ /* in any other case assume it's just a timeout */
+ refclock_report(peer, CEVNT_TIMEOUT);
+ }
+ /* shm_clockstats() clears the tallies, so it must be last... */
+ shm_clockstats(unit, peer);
+}
+
+/*
+ * shm_peek - try to grab a sample
+ */
+static void
+shm_peek(
+ int unit,
+ struct peer *peer
+ )
+{
+ struct refclockproc *pp;
+ struct shmunit *up;
+
+ /* access order is important for lock-free SHM access; we
+ ** enforce order by treating the whole structure volatile.
+ **
+ ** IMPORTANT NOTE: This does not protect from reordering on CPU
+ ** level, and it does nothing for cache consistency and
+ ** visibility of changes by other cores. We need atomic and/or
+ ** fence instructions for that.
+ */
+ volatile struct shmTime *shm;
+
+ struct timespec tvr;
+ struct timespec tvt;
+ l_fp tsrcv;
+ l_fp tsref;
+ unsigned int c;
+ unsigned int cns_new, rns_new;
+ int cnt;
+
+ /* for formatting 'a_lastcode': */
+ struct calendar cd;
+ time_t tt, now;
+ vint64 ts;
+
+ /*
+ * This is the main routine. It snatches the time from the shm
+ * board and tacks on a local timestamp.
+ */
+ pp = peer->procptr;
+ up = pp->unitptr;
+ up->ticks++;
+ if (up->shm == 0) {
+ /* try to map again - this may succeed if meanwhile some-
+ body has ipcrm'ed the old (unaccessible) shared mem segment */
+ up->shm = getShmTime(unit);
+ }
+ shm = up->shm;
+ if (shm == 0) {
+ DPRINTF(1, ("%s: no SHM segment\n",
+ refnumtoa(&peer->srcadr)));
+ return;
+ }
+ if ( ! shm->valid) {
+ DPRINTF(1, ("%s: SHM not ready\n",
+ refnumtoa(&peer->srcadr)));
+ up->notready++;
+ return;
+ }
+
+ switch (shm->mode) {
+ case 0:
+ tvr.tv_sec = shm->receiveTimeStampSec;
+ tvr.tv_nsec = shm->receiveTimeStampUSec * 1000;
+ rns_new = shm->receiveTimeStampNSec;
+ tvt.tv_sec = shm->clockTimeStampSec;
+ tvt.tv_nsec = shm->clockTimeStampUSec * 1000;
+ cns_new = shm->clockTimeStampNSec;
+
+ /* Since the following comparisons are between unsigned
+ ** variables they are always well defined, and any
+ ** (signed) underflow will turn into very large unsigned
+ ** values, well above the 1000 cutoff.
+ **
+ ** Note: The usecs *must* be a *truncated*
+ ** representation of the nsecs. This code will fail for
+ ** *rounded* usecs, and the logic to deal with
+ ** wrap-arounds in the presence of rounded values is
+ ** much more convoluted.
+ */
+ if ( ((cns_new - (unsigned)tvt.tv_nsec) < 1000)
+ && ((rns_new - (unsigned)tvr.tv_nsec) < 1000)) {
+ tvt.tv_nsec = cns_new;
+ tvr.tv_nsec = rns_new;
+ }
+ /* At this point tvr and tvt contains valid ns-level
+ ** timestamps, possibly generated by extending the old
+ ** us-level timestamps
+ */
+ DPRINTF(2, ("%s: SHM type 0 sample\n",
+ refnumtoa(&peer->srcadr)));
+ break;
+
+ case 1:
+ cnt = shm->count;
+
+ tvr.tv_sec = shm->receiveTimeStampSec;
+ tvr.tv_nsec = shm->receiveTimeStampUSec * 1000;
+ rns_new = shm->receiveTimeStampNSec;
+ tvt.tv_sec = shm->clockTimeStampSec;
+ tvt.tv_nsec = shm->clockTimeStampUSec * 1000;
+ cns_new = shm->clockTimeStampNSec;
+ if (cnt != shm->count) {
+ DPRINTF(1, ("%s: type 1 access clash\n",
+ refnumtoa(&peer->srcadr)));
+ msyslog (LOG_NOTICE, "SHM: access clash in shared memory");
+ up->clash++;
+ return;
+ }
+
+ /* See the case above for an explanation of the
+ ** following test.
+ */
+ if ( ((cns_new - (unsigned)tvt.tv_nsec) < 1000)
+ && ((rns_new - (unsigned)tvr.tv_nsec) < 1000)) {
+ tvt.tv_nsec = cns_new;
+ tvr.tv_nsec = rns_new;
+ }
+ /* At this point tvr and tvt contains valid ns-level
+ ** timestamps, possibly generated by extending the old
+ ** us-level timestamps
+ */
+ DPRINTF(2, ("%s: SHM type 1 sample\n",
+ refnumtoa(&peer->srcadr)));
+ break;
+
+ default:
+ DPRINTF(1, ("%s: SHM type blooper, mode=%d\n",
+ refnumtoa(&peer->srcadr), shm->mode));
+ up->bad++;
+ msyslog (LOG_ERR, "SHM: bad mode found in shared memory: %d",
+ shm->mode);
+ return;
+ }
+ shm->valid = 0;
+
+ /* format the last time code in human-readable form into
+ * 'pp->a_lastcode'. Someone claimed: "NetBSD has incompatible
+ * tv_sec". I can't find a base for this claim, but we can work
+ * around that potential problem. BTW, simply casting a pointer
+ * is a receipe for disaster on some architectures.
+ */
+ tt = (time_t)tvt.tv_sec;
+ ts = time_to_vint64(&tt);
+ ntpcal_time_to_date(&cd, &ts);
+
+ /* add ntpq -c cv timecode in ISO 8601 format */
+ c = snprintf(pp->a_lastcode, sizeof(pp->a_lastcode),
+ "%04u-%02u-%02uT%02u:%02u:%02u.%09ldZ",
+ cd.year, cd.month, cd.monthday,
+ cd.hour, cd.minute, cd.second,
+ (long)tvt.tv_nsec);
+ pp->lencode = (c < sizeof(pp->a_lastcode)) ? c : 0;
+
+ /* check 1: age control of local time stamp */
+ time(&now);
+ tt = now - tvr.tv_sec;
+ if (tt < 0 || tt > up->max_delay) {
+ DPRINTF(1, ("%s:SHM stale/bad receive time, delay=%llds\n",
+ refnumtoa(&peer->srcadr), (long long)tt));
+ up->bad++;
+ msyslog (LOG_ERR, "SHM: stale/bad receive time, delay=%llds",
+ (long long)tt);
+ return;
+ }
+
+ /* check 2: delta check */
+ tt = tvr.tv_sec - tvt.tv_sec - (tvr.tv_nsec < tvt.tv_nsec);
+ if (tt < 0)
+ tt = -tt;
+ if (up->max_delta > 0 && tt > up->max_delta) {
+ DPRINTF(1, ("%s: SHM diff limit exceeded, delta=%llds\n",
+ refnumtoa(&peer->srcadr), (long long)tt));
+ up->bad++;
+ msyslog (LOG_ERR, "SHM: difference limit exceeded, delta=%llds\n",
+ (long long)tt);
+ return;
+ }
+
+ /* if we really made it to this point... we're winners! */
+ DPRINTF(2, ("%s: SHM feeding data\n",
+ refnumtoa(&peer->srcadr)));
+ tsrcv = tspec_stamp_to_lfp(tvr);
+ tsref = tspec_stamp_to_lfp(tvt);
+ pp->leap = shm->leap;
+ peer->precision = shm->precision;
+ refclock_process_offset(pp, tsref, tsrcv, pp->fudgetime1);
+ up->good++;
+}
+
+/*
+ * shm_clockstats - dump and reset counters
+ */
+static void shm_clockstats(
+ int unit,
+ struct peer *peer
+ )
+{
+ struct refclockproc *pp;
+ struct shmunit *up;
+ char logbuf[64];
+ unsigned int llen;
+
+ pp = peer->procptr;
+ up = pp->unitptr;
+
+ if (pp->sloppyclockflag & CLK_FLAG4) {
+ /* if snprintf() returns a negative values on errors
+ ** (some older ones do) make sure we are NUL
+ ** terminated. Using an unsigned result does the trick.
+ */
+ llen = snprintf(logbuf, sizeof(logbuf),
+ "%3d %3d %3d %3d %3d",
+ up->ticks, up->good, up->notready,
+ up->bad, up->clash);
+ logbuf[min(llen, sizeof(logbuf)-1)] = '\0';
+ record_clock_stats(&peer->srcadr, logbuf);
+ }
+ up->ticks = up->good = up->notready = up->bad = up->clash = 0;
+
+}
+
+#else
+NONEMPTY_TRANSLATION_UNIT
+#endif /* REFCLOCK */