cleanup specfile for packaging
[profile/ivi/gpsd.git] / ntpshm.c
1 /* 
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
5  *
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.
8  */
9
10 #include <stdlib.h>
11 #include "gpsd_config.h"
12 #include <sys/types.h>
13 #ifndef S_SPLINT_S
14 #include <unistd.h>
15 #endif /* S_SPLINT_S */
16 #include <stdio.h>
17 #include <string.h>
18 #include <math.h>
19 #include <errno.h>
20
21 #include "gpsd.h"
22 #ifdef NTPSHM_ENABLE
23
24 #if defined(HAVE_SYS_TIME_H)
25 #include <sys/time.h>
26 #endif
27
28 #ifdef HAVE_SYS_IPC_H
29 #include <sys/ipc.h>
30 #endif /* HAVE_SYS_IPC_H */
31 #ifdef HAVE_SYS_SHM_H
32 #include <sys/shm.h>
33 #endif /* HAVE_SYS_SHM_H */
34
35 #define PPS_MAX_OFFSET  100000  /* microseconds the PPS can 'pull' */
36 #define PUT_MAX_OFFSET  1000000 /* microseconds for lost lock */
37
38 #define NTPD_BASE       0x4e545030      /* "NTP0" */
39 #define SHM_UNIT        0       /* SHM driver unit number (0..3) */
40
41 struct shmTime
42 {
43     int mode;                   /* 0 - if valid set
44                                  *       use values, 
45                                  *       clear valid
46                                  * 1 - if valid set 
47                                  *       if count before and after read of values is equal,
48                                  *         use values 
49                                  *       clear valid
50                                  */
51     int count;
52     time_t clockTimeStampSec;
53     int clockTimeStampUSec;
54     time_t receiveTimeStampSec;
55     int receiveTimeStampUSec;
56     int leap;
57     int precision;
58     int nsamples;
59     int valid;
60     int pad[10];
61 };
62
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.
65  *
66  * Ntpd always runs as root (to be able to control the system clock).
67  * Its logics for the creation of ntpshm segments are:
68  *
69  * Segments 0 and 1: permissions 0600, i.e. other programs can only
70  *                   read and write as root.
71  *
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.
77  *
78  * As gpsd can be started as both root and non-root, this behaviour is
79  * mimiced by:
80  *
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.)
84  *
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
87  * does not matter.
88  *
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.
94  *
95  * to debug, try looking at the live segments this way
96  *  ipcs -m
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
104  *
105  * For a bit more data try this:
106  *  cat /proc/sysvipc/shm
107  *
108  * If gpsd can not open the segments be sure you are not running SELinux
109  * or apparmor.
110  *
111  * if you see the shared segments (keys 1314148400 -- 1314148403), and
112  * no gpsd or ntpd is running then try removing them like this:
113  *
114  * ipcrm  -M 0x4e545030
115  * ipcrm  -M 0x4e545031
116  * ipcrm  -M 0x4e545032
117  * ipcrm  -M 0x4e545033
118  */
119 static /*@null@*/ struct shmTime *getShmTime(int unit)
120 {
121     int shmid;
122     unsigned int perms;
123     // set the SHM perms the way ntpd does
124     if (unit < 2) {
125         // we are root, be careful
126         perms = 0600;
127     } else {
128         // we are not root, try to work anyway
129         perms = 0666;
130     }
131
132     shmid = shmget((key_t) (NTPD_BASE + unit),
133                    sizeof(struct shmTime), (int)(IPC_CREAT | perms));
134     if (shmid == -1) {
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));
138         return NULL;
139     } else {
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",
144                         strerror(errno));
145             return NULL;
146         }
147         gpsd_report(LOG_PROG, "NTPD shmat(%d,0,0) succeeded, segment %d\n",
148                     shmid, unit);
149         return p;
150         /*@ +mustfreefresh */
151     }
152 }
153
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 */
157 {
158     int i;
159
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);
164         }
165     }
166     memset(context->shmTimeInuse, 0, sizeof(context->shmTimeInuse));
167 # ifdef PPS_ENABLE
168     context->shmTimePPS = enablepps;
169 # endif /* PPS_ENABLE */
170     context->enable_ntpshm = true;
171 }
172
173 int ntpshm_alloc(struct gps_context_t *context)
174 /* allocate NTP SHM segment.  return its segment number, or -1 */
175 {
176     int i;
177
178     for (i = 0; i < NTPSHMSEGS; i++)
179         if (context->shmTime[i] != NULL && !context->shmTimeInuse[i]) {
180             context->shmTimeInuse[i] = true;
181
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 */
186
187             return i;
188         }
189
190     return -1;
191 }
192
193 bool ntpshm_free(struct gps_context_t * context, int segment)
194 /* free NTP SHM segment */
195 {
196     if (segment < 0 || segment >= NTPSHMSEGS)
197         return false;
198
199     context->shmTimeInuse[segment] = false;
200     return true;
201 }
202
203
204 int ntpshm_put(struct gps_device_t *session, double fixtime, double fudge)
205 /* put a received fix time into shared memory for NTP */
206 {
207     struct shmTime *shmTime = NULL;
208     struct timeval tv;
209     double seconds, microseconds;
210
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");
215         return 0;
216     }
217
218     (void)gettimeofday(&tv, NULL);
219     fixtime += fudge;
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");
223         return 0;
224     }
225
226     /* we use the shmTime mode 1 protocol
227      *
228      * ntpd does this:
229      *
230      * reads valid.  
231      * IFF valid is 1
232      *    reads count
233      *    reads values
234      *    reads count
235      *    IFF count unchanged
236      *        use values
237      *    clear valid
238      *    
239      */
240     shmTime->valid = 0;
241     shmTime->count++;
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.
250      */
251     shmTime->count++;
252     shmTime->valid = 1;
253
254     gpsd_report(LOG_RAW,
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);
258
259     return 1;
260 }
261
262 #ifdef PPS_ENABLE
263 /* put NTP shared memory info based on received PPS pulse */
264
265 int ntpshm_pps(struct gps_device_t *session, struct timeval *tv)
266 {
267     struct shmTime *shmTime = NULL, *shmTimeP = NULL;
268     time_t seconds;
269     /* FIX-ME, microseconds needs to be set for 5Hz PPS */
270     int microseconds = 0;
271     int precision;
272     double offset;
273     long l_offset;
274
275     if (0 > session->shmindex || 0 > session->shmTimeP ||
276         (shmTime = session->context->shmTime[session->shmindex]) == NULL ||
277         (shmTimeP = session->context->shmTime[session->shmTimeP]) == NULL)
278         return 0;
279
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 */
283
284     /* FIX-ME, does not handle 5Hz yet */
285
286 #ifdef S_SPLINT_S               /* avoids an internal error in splint 3.1.1 */
287     l_offset = 0;
288 #else
289     l_offset = tv->tv_sec - shmTime->receiveTimeStampSec;
290 #endif
291     /*@ -ignorequals @*/
292     l_offset *= 1000000;
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",
296                     (long)l_offset);
297         return -1;
298     }
299
300     /*@+relaxtypes@*/
301     seconds = shmTime->clockTimeStampSec + 1;
302     offset = fabs((tv->tv_sec - seconds)
303                   + ((double)(tv->tv_usec - 0) / 1000000.0));
304     /*@-relaxtypes@*/
305
306
307     /* we use the shmTime mode 1 protocol
308      *
309      * ntpd does this:
310      *
311      * reads valid.  
312      * IFF valid is 1
313      *    reads count
314      *    reads values
315      *    reads count
316      *    IFF count unchanged
317      *        use values
318      *    clear valid
319      *    
320      */
321     shmTimeP->valid = 0;
322     shmTimeP->count++;
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;
330     shmTimeP->count++;
331     shmTimeP->valid = 1;
332
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,
339                 precision);
340     return 1;
341 }
342 #endif /* PPS_ENABLE */
343 #endif /* NTPSHM_ENABLE */