2 * ntpshm.c - put time information in SHM segment for xntpd
3 * struct shmTime and getShmTime from file in the xntp distribution:
4 * sht.c - Testprogram for shared memory refclock
6 * This file is Copyright (c) 2010 by the GPSD project
7 * BSD terms apply: see the file COPYING in the distribution root for details.
11 #include "gpsd_config.h"
12 #include <sys/types.h>
15 #endif /* S_SPLINT_S */
24 #if defined(HAVE_SYS_TIME_H)
30 #endif /* HAVE_SYS_IPC_H */
33 #endif /* HAVE_SYS_SHM_H */
35 #define PPS_MAX_OFFSET 100000 /* microseconds the PPS can 'pull' */
36 #define PUT_MAX_OFFSET 1000000 /* microseconds for lost lock */
38 #define NTPD_BASE 0x4e545030 /* "NTP0" */
39 #define SHM_UNIT 0 /* SHM driver unit number (0..3) */
43 int mode; /* 0 - if valid set
47 * if count before and after read of values is equal,
52 time_t clockTimeStampSec;
53 int clockTimeStampUSec;
54 time_t receiveTimeStampSec;
55 int receiveTimeStampUSec;
63 /* Note: you can start gpsd as non-root, and have it work with ntpd.
64 * However, it will then only use the ntpshm segments 2 and 3.
66 * Ntpd always runs as root (to be able to control the system clock).
67 * Its logics for the creation of ntpshm segments are:
69 * Segments 0 and 1: permissions 0600, i.e. other programs can only
70 * read and write as root.
72 * Segments 2 and 3: permissions 0666, i.e. other programs can read
73 * and write as any user. I.e.: if ntpd has been
74 * configured to use these segments, any
75 * unpriviliged user is allowed to provide data
76 * for synchronisation.
78 * As gpsd can be started as both root and non-root, this behaviour is
81 * Started as root: do as ntpd when attaching (creating) the segments.
82 * (In contrast to ntpd, which only attaches (creates) configured
83 * segments, gpsd creates all segments.)
85 * Started as non-root: only attach (create) segments 2 and 3 with
86 * permissions 0666. As the permissions are for any user, the creator
89 * For each GPS module gpsd controls, it will use the attached ntpshm
90 * segments in pairs (for coarse clock and pps source, respectively)
91 * starting from the first found segments. I.e. started as root, one
92 * GPS will deliver data on segments 0 and 1, and as non-root data
93 * will be delivered on segments 2 and 3.
95 * to debug, try looking at the live segments this way
97 * results should look like this:
98 * ------ Shared Memory Segments --------
99 * key shmid owner perms bytes nattch status
100 * 0x4e545030 0 root 700 96 2
101 * 0x4e545031 32769 root 700 96 2
102 * 0x4e545032 163842 root 666 96 1
103 * 0x4e545033 196611 root 666 96 1
105 * For a bit more data try this:
106 * cat /proc/sysvipc/shm
108 * If gpsd can not open the segments be sure you are not running SELinux
111 * if you see the shared segments (keys 1314148400 -- 1314148403), and
112 * no gpsd or ntpd is running then try removing them like this:
114 * ipcrm -M 0x4e545030
115 * ipcrm -M 0x4e545031
116 * ipcrm -M 0x4e545032
117 * ipcrm -M 0x4e545033
119 static /*@null@*/ struct shmTime *getShmTime(int unit)
123 // set the SHM perms the way ntpd does
125 // we are root, be careful
128 // we are not root, try to work anyway
132 shmid = shmget((key_t) (NTPD_BASE + unit),
133 sizeof(struct shmTime), (int)(IPC_CREAT | perms));
135 gpsd_report(LOG_ERROR, "NTPD shmget(%ld, %zd, %o) fail: %s\n",
136 (long int)(NTPD_BASE + unit), sizeof(struct shmTime),
137 (int)perms, strerror(errno));
140 struct shmTime *p = (struct shmTime *)shmat(shmid, 0, 0);
141 /*@ -mustfreefresh */
142 if ((int)(long)p == -1) {
143 gpsd_report(LOG_ERROR, "NTPD shmat failed: %s\n",
147 gpsd_report(LOG_PROG, "NTPD shmat(%d,0,0) succeeded, segment %d\n",
150 /*@ +mustfreefresh */
154 void ntpshm_init(struct gps_context_t *context, bool enablepps)
155 /* attach all NTP SHM segments.
156 * called once at startup, while still root, although root not required */
160 for (i = 0; i < NTPSHMSEGS; i++) {
161 // Only grab the first two when running as root.
162 if (2 <= i || 0 == getuid()) {
163 context->shmTime[i] = getShmTime(i);
166 memset(context->shmTimeInuse, 0, sizeof(context->shmTimeInuse));
168 context->shmTimePPS = enablepps;
169 # endif /* PPS_ENABLE */
170 context->enable_ntpshm = true;
173 int ntpshm_alloc(struct gps_context_t *context)
174 /* allocate NTP SHM segment. return its segment number, or -1 */
178 for (i = 0; i < NTPSHMSEGS; i++)
179 if (context->shmTime[i] != NULL && !context->shmTimeInuse[i]) {
180 context->shmTimeInuse[i] = true;
182 memset((void *)context->shmTime[i], 0, sizeof(struct shmTime));
183 context->shmTime[i]->mode = 1;
184 context->shmTime[i]->precision = -1; /* initially 0.5 sec */
185 context->shmTime[i]->nsamples = 3; /* stages of median filter */
193 bool ntpshm_free(struct gps_context_t * context, int segment)
194 /* free NTP SHM segment */
196 if (segment < 0 || segment >= NTPSHMSEGS)
199 context->shmTimeInuse[segment] = false;
204 int ntpshm_put(struct gps_device_t *session, double fixtime, double fudge)
205 /* put a received fix time into shared memory for NTP */
207 struct shmTime *shmTime = NULL;
209 double seconds, microseconds;
211 // gpsd_report(LOG_PROG, "NTP: doing ntpshm_put(,%g, %g)\n", fixtime, fudge);
212 if (session->shmindex < 0 ||
213 (shmTime = session->context->shmTime[session->shmindex]) == NULL) {
214 gpsd_report(LOG_RAW, "NTPD missing shm\n");
218 (void)gettimeofday(&tv, NULL);
220 microseconds = 1000000.0 * modf(fixtime, &seconds);
221 if (shmTime->clockTimeStampSec == (time_t) seconds) {
222 gpsd_report(LOG_RAW, "NTPD ntpshm_put: skipping duplicate second\n");
226 /* we use the shmTime mode 1 protocol
235 * IFF count unchanged
242 shmTime->clockTimeStampSec = (time_t) seconds;
243 shmTime->clockTimeStampUSec = (int)microseconds;
244 shmTime->receiveTimeStampSec = (time_t) tv.tv_sec;
245 shmTime->receiveTimeStampUSec = (int)tv.tv_usec;
246 /* setting the precision here does not seem to help anything, too
247 * hard to calculate properly anyway. Let ntpd figure it out.
248 * Any NMEA will be about -1 or -2.
249 * Garmin GPS-18/USB is around -6 or -7.
255 "NTPD ntpshm_put: Clock: %lu.%06lu @ %lu.%06lu, fudge: %0.3f\n",
256 (unsigned long)seconds, (unsigned long)microseconds,
257 (unsigned long)tv.tv_sec, (unsigned long)tv.tv_usec, fudge);
263 /* put NTP shared memory info based on received PPS pulse */
265 int ntpshm_pps(struct gps_device_t *session, struct timeval *tv)
267 struct shmTime *shmTime = NULL, *shmTimeP = NULL;
269 /* FIX-ME, microseconds needs to be set for 5Hz PPS */
270 int microseconds = 0;
275 if (0 > session->shmindex || 0 > session->shmTimeP ||
276 (shmTime = session->context->shmTime[session->shmindex]) == NULL ||
277 (shmTimeP = session->context->shmTime[session->shmTimeP]) == NULL)
280 /* PPS has no seconds attached to it.
281 * check to see if we have a fresh timestamp from the
282 * GPS serial input then use that */
284 /* FIX-ME, does not handle 5Hz yet */
286 #ifdef S_SPLINT_S /* avoids an internal error in splint 3.1.1 */
289 l_offset = tv->tv_sec - shmTime->receiveTimeStampSec;
293 l_offset += tv->tv_usec - shmTime->receiveTimeStampUSec;
294 if (0 > l_offset || 1000000 < l_offset) {
295 gpsd_report(LOG_RAW, "PPS ntpshm_pps: no current GPS seconds: %ld\n",
301 seconds = shmTime->clockTimeStampSec + 1;
302 offset = fabs((tv->tv_sec - seconds)
303 + ((double)(tv->tv_usec - 0) / 1000000.0));
307 /* we use the shmTime mode 1 protocol
316 * IFF count unchanged
323 shmTimeP->clockTimeStampSec = seconds;
324 shmTimeP->clockTimeStampUSec = (int)microseconds;
325 shmTimeP->receiveTimeStampSec = (time_t) tv->tv_sec;
326 shmTimeP->receiveTimeStampUSec = (int)tv->tv_usec;
327 /* precision is a placebo, ntpd does not really use it
328 * real world accuracty is around 16uS, thus -16 precision */
329 shmTimeP->precision = -16;
333 /* this is more an offset jitter/dispersion than precision,
334 * but still useful for debug */
335 precision = offset != 0 ? (int)(ceil(log(offset) / M_LN2)) : -20;
336 gpsd_report(LOG_RAW, "PPS ntpshm_pps %lu.%03lu @ %lu.%06lu, preci %d\n",
337 (unsigned long)seconds, (unsigned long)microseconds / 1000,
338 (unsigned long)tv->tv_sec, (unsigned long)tv->tv_usec,
342 #endif /* PPS_ENABLE */
343 #endif /* NTPSHM_ENABLE */