mrfioc2  2.3.0
ntpShm.cpp
Go to the documentation of this file.
1 /*************************************************************************\
2 * Copyright (c) 2013 Brookhaven Science Associates, as Operator of
3 * Brookhaven National Laboratory.
4 * mrfioc2 is distributed subject to a Software License Agreement found
5 * in file LICENSE that is included with this distribution.
6 \*************************************************************************/
7 /*
8  * Serve up EVR time to the shared memory driver (#28) of the NTP daemon.
9  *
10  * cf. http://www.eecis.udel.edu/~mills/ntp/html/drivers/driver28.html
11  *
12  * Author: Michael Davidsaver <mdavidsaver@gmail.com>
13  *
14  * To use, add to init script. Where 0<=N<=4. To use 0 or 1 the IOC
15  * must run as root.
16  *
17  * time2ntp("evrname", N)
18  *
19  * Add to NTP daemon config. Replace 'prefer' with 'noselect' when testing
20  *
21  * server 127.127.28.N minpoll 1 maxpoll 2 prefer
22  * fudge 127.127.28.N refid EVR
23  *
24  * Order of execution in this file.
25  * 1) User calls time2ntp() before iocInit()
26  * 2) ntpshmhooks() is called during iocInit()
27  * 3) ntpsetup() is called periodically until it succeeds
28  * 4) ntpshmupdate() is called once per second.
29  */
30 #include <stdio.h>
31 #include <errno.h>
32 
33 #include <sys/ipc.h>
34 #include <sys/shm.h>
35 #include <sys/time.h>
36 
37 #include <epicsTime.h>
38 #include <epicsVersion.h>
39 #include <epicsMutex.h>
40 #include <epicsThread.h>
41 #include <epicsStdio.h> /* redirects stdout/err */
42 #include <callback.h>
43 #include <drvSup.h>
44 #include <recGbl.h>
45 #include <longinRecord.h>
46 #include <aiRecord.h>
47 #include <menuConvert.h>
48 #include <dbScan.h>
49 #include <iocsh.h>
50 #include <initHooks.h>
51 
52 #include "evr/evr.h"
53 
54 // NTPD itself doesn't make much attempt at synchronizing
55 // but we try to do better
56 #if defined(__GNUC__)
57 # if ( ( __GNUC__ * 100 + __GNUC_MINOR__ ) >= 401 )
58 # define SYNC() __sync_synchronize()
59 # else
60 # define SYNC() __asm__ __volatile__ ("":::"memory")
61 # endif
62 #else
63  // oh well, we tried
64 # define SYNC() do{}while(0)
65 #endif
66 
67 // from a shell run 'ipcs -m' while NTPD is running w/ driver28 loaded.
68 #define NTPD_SEG0 0x4E545030
69 
70 #define RETRY_TIME 30.0
71 
72 // definition of shared segment, as described in
73 // http://www.eecis.udel.edu/~mills/ntp/html/drivers/driver28.html
74 typedef struct {
75  int mode;
76  int count;
77 
78  // The EVR timestamp
79  time_t stampSec;
80  int stampUsec;
81 
82  // The system time when it was acquired
83  time_t rxSec;
84  int rxUsec;
85 
86  int leap;
87  int precision;
88  int nsamples;
89  int valid;
90 
91  int pad[10];
92 } shmSegment;
93 
94 static epicsThreadOnceId ntponce = EPICS_THREAD_ONCE_INIT;
95 
96 typedef struct {
97 
98  epicsMutexId ntplock;
99 
100  CALLBACK ntp_cb;
101 
102  epicsUInt32 event;
103 
105  int segid;
106 
108 
111 
112  IOSCANPVT lastUpdate;
113 
114  bool lastValid;
115  epicsTimeStamp lastStamp;
116  epicsTimeStamp lastRx;
117 
118  unsigned int numOk;
119  unsigned int numFail;
120 } ntpShmPriv;
121 static ntpShmPriv ntpShm;
122 
123 
124 static void incFail()
125 {
126  epicsMutexMustLock(ntpShm.ntplock);
127  ntpShm.lastValid = false;
128  ntpShm.numFail++;
129  epicsMutexUnlock(ntpShm.ntplock);
130 }
131 
132 static void ntpshmupdate(void*, epicsUInt32 event)
133 {
134  if(event!=ntpShm.event) {
135  incFail(); return;
136  }
137 
138  epicsTimeStamp evrts;
139  if(!ntpShm.evr->getTimeStamp(&evrts, 0)) // read current wall clock time
140  {
141  // no valid device time
142  incFail(); return;
143  }
144 
145  struct timeval cputs;
146  if(gettimeofday(&cputs, 0))
147  {
148  // no valid cpu time?
149  incFail(); return;
150  }
151 
152  struct timeval evrts_posix;
153  evrts_posix.tv_sec = evrts.secPastEpoch + POSIX_TIME_AT_EPICS_EPOCH;
154  evrts_posix.tv_usec = evrts.nsec / 1000;
155  // correct for bias in truncation
156  if(evrts.nsec % 1000 >= 500) {
157  evrts_posix.tv_usec += 1;
158  if(evrts_posix.tv_usec>=1000000) {
159  evrts_posix.tv_sec += 1;
160  evrts_posix.tv_usec = 0;
161  }
162  }
163 
164  // volatile operations aren't really enough, but will have to do.
165  volatile shmSegment* seg=ntpShm.seg;
166 
167  seg->valid = 0;
168  SYNC();
169  int c1 = seg->count++;
170  seg->stampSec = evrts_posix.tv_sec;
171  seg->stampUsec = evrts_posix.tv_usec;
172  seg->rxSec = cputs.tv_sec;
173  seg->rxUsec = cputs.tv_usec;
174  int c2 = seg->count++;
175  if(c1+1!=c2) {
176  fprintf(stderr, "ntpshmupdate: possible collision with another writer!\n");
177  incFail(); return;
178  }
179  seg->valid = 1;
180  SYNC();
181 
182  epicsMutexMustLock(ntpShm.ntplock);
183  ntpShm.lastValid = true;
184  ntpShm.numOk++;
185  ntpShm.lastStamp = evrts;
186  ntpShm.lastRx = epicsTime(cputs);
187  epicsMutexUnlock(ntpShm.ntplock);
188 
189  scanIoRequest(ntpShm.lastUpdate);
190 
191  if(!ntpShm.notify_1strx) {
192  fprintf(stderr, "First update ready for NTPD\n");
193  ntpShm.notify_1strx = 1;
194  }
195 
196  return; // normal exit
197 }
198 
199 static void ntpsetup(CALLBACK *)
200 {
201  // We don't set IPC_CREAT, but instead wait for NTPD to start and initialize
202  // as it wants
203  int mode = ntpShm.segid <=1 ? 0600 : 0666;
204 
205  int shmid = shmget((key_t)(NTPD_SEG0+ntpShm.segid), sizeof(shmSegment), mode);
206 
207  if(shmid==-1) {
208  if(errno==ENOENT) {
209  if(!ntpShm.notify_nomap) {
210  fprintf(stderr, "Can't find shared memory segment. Either NTPD hasn't started,"
211  " or is not configured correctly. Will retry later.");
212  ntpShm.notify_nomap = 1;
213  }
214  callbackRequestDelayed(&ntpShm.ntp_cb, RETRY_TIME);
215  } else {
216  perror("ntpshmsetup: shmget");
217  }
218  return;
219  }
220 
221  ntpShm.seg = (shmSegment*)shmat(shmid, 0, 0);
222  if(ntpShm.seg==(shmSegment*)-1) {
223  perror("ntpshmsetup: shmat");
224  return;
225  }
226 
227  ntpShm.seg->mode = 1;
228  ntpShm.seg->valid = 0;
229  SYNC();
230  ntpShm.seg->leap = 0; //TODO: what does this do?
231  ntpShm.seg->precision = -19; // pow(2,-19) ~= 1e-6 sec
232  ntpShm.seg->nsamples = 3; //TODO: what does this do?
233  SYNC();
234 
235  try {
236  ntpShm.evr->eventNotifyAdd(ntpShm.event, &ntpshmupdate, 0);
237  } catch(std::exception& e) {
238  fprintf(stderr, "Error registering for 1Hz event: %s\n", e.what());
239  }
240 }
241 
242 static void ntpshminit(void*)
243 {
244  ntpShm.ntplock = epicsMutexMustCreate();
245 
246  callbackSetPriority(priorityLow, &ntpShm.ntp_cb);
247  callbackSetCallback(&ntpsetup, &ntpShm.ntp_cb);
248  callbackSetUser(0, &ntpShm.ntp_cb);
249 }
250 
251 static void ntpshmhooks(initHookState state)
252 {
253  if(state!=initHookAfterIocRunning)
254  return;
255 
256  epicsThreadOnce(&ntponce, &ntpshminit, 0);
257 
258  epicsMutexMustLock(ntpShm.ntplock);
259  if(ntpShm.evr) {
260  callbackRequest(&ntpShm.ntp_cb);
261  fprintf(stderr, "Starting NTP SHM writer for segment %d\n", ntpShm.segid);
262  }
263  epicsMutexUnlock(ntpShm.ntplock);
264 }
265 
266 void time2ntp(const char* evrname, int segid, int event)
267 {
268  try {
269  if(event==0)
270  event = MRF_EVENT_TS_COUNTER_RST;
271  else if(event<=0 || event >255) {
272  fprintf(stderr, "Invalid 1Hz event # %d\n", event);
273  return;
274  }
275  if(segid<0 || segid>4) {
276  fprintf(stderr, "Invalid segment ID %d\n", segid);
277  return;
278  }
279  mrf::Object *obj = mrf::Object::getObject(evrname);
280  if(!obj) {
281  fprintf(stderr, "Unknown EVR: %s\n", evrname);
282  return;
283  }
284 
285  EVR *evr = dynamic_cast<EVR*>(obj);
286  if(!evr) {
287  fprintf(stderr, "\"%s\" is not an EVR\n", evrname);
288  return;
289  }
290 
291  epicsThreadOnce(&ntponce, &ntpshminit, 0);
292 
293  epicsMutexMustLock(ntpShm.ntplock);
294 
295  if(ntpShm.evr) {
296  epicsMutexUnlock(ntpShm.ntplock);
297  fprintf(stderr, "ntpShm already initialized.\n");
298  return;
299  }
300 
301  ntpShm.event = event;
302  ntpShm.evr = evr;
303  ntpShm.segid = segid;
304 
305  epicsMutexUnlock(ntpShm.ntplock);
306  } catch(std::exception& e) {
307  fprintf(stderr, "Error: %s\n", e.what());
308  }
309 }
310 
311 static const iocshArg time2ntpArg0 = { "evr name",iocshArgString};
312 static const iocshArg time2ntpArg1 = { "NTP segment id",iocshArgInt};
313 static const iocshArg time2ntpArg2 = { "1Hz Event code",iocshArgInt};
314 static const iocshArg * const time2ntpArgs[3] =
315 {&time2ntpArg0,&time2ntpArg1,&time2ntpArg2};
316 static const iocshFuncDef time2ntpFuncDef =
317  {"time2ntp",3,time2ntpArgs};
318 static void time2ntpCallFunc(const iocshArgBuf *args)
319 {
320  time2ntp(args[0].sval,args[1].ival,args[2].ival);
321 }
322 
323 static long init_record(dbCommon*) { return 0; }
324 
325 static long get_ioint_info(int /*cmd*/, dbCommon */*pRec*/, IOSCANPVT *ppvt)
326 {
327  *ppvt = ntpShm.lastUpdate;
328  return 0;
329 }
330 
331 static long read_ok(longinRecord* prec)
332 {
333  epicsMutexMustLock(ntpShm.ntplock);
334  prec->val = ntpShm.numOk;
335  epicsMutexUnlock(ntpShm.ntplock);
336  return 0;
337 }
338 
339 static long read_fail(longinRecord* prec)
340 {
341  epicsMutexMustLock(ntpShm.ntplock);
342  prec->val = ntpShm.numFail;
343  epicsMutexUnlock(ntpShm.ntplock);
344  return 0;
345 }
346 
347 static long read_delta(aiRecord* prec)
348 {
349  epicsMutexMustLock(ntpShm.ntplock);
350  double val = 0.0;
351  if(ntpShm.lastValid)
352  val = epicsTimeDiffInSeconds(&ntpShm.lastStamp, &ntpShm.lastRx);
353  else
354  recGblSetSevr(prec, READ_ALARM, INVALID_ALARM);
355  if(prec->tse==epicsTimeEventDeviceTime) {
356  prec->time = ntpShm.lastStamp;
357  }
358  epicsMutexUnlock(ntpShm.ntplock);
359 
360  if(prec->linr==menuConvertLINEAR){
361  val-=prec->eoff;
362  if(prec->eslo!=0)
363  val/=prec->eslo;
364  }
365  val-=prec->aoff;
366  if(prec->aslo!=0)
367  val/=prec->aslo;
368  prec->val = val;
369  prec->udf = !isfinite(val);
370 
371  return 2;
372 }
373 
374 static void ntpShmReport(int)
375 {
376  epicsMutexMustLock(ntpShm.ntplock);
377  EVR *evr=ntpShm.evr;
378  unsigned int ok=ntpShm.numOk,
379  fail=ntpShm.numFail;
380  epicsMutexUnlock(ntpShm.ntplock);
381 
382  if(evr) {
383  printf("Driver is active\n ok#: %u\n fail#: %u\n", ok, fail);
384  } else {
385  printf("Driver is not active\n");
386  }
387 }
388 
389 static void ntpShmInit()
390 {
391  scanIoInit(&ntpShm.lastUpdate);
392 }
393 
394 static void ntpShmRegister()
395 {
396  initHookRegister(&ntpshmhooks);
397  iocshRegister(&time2ntpFuncDef,&time2ntpCallFunc);
398 }
399 
400 typedef struct {
401  dset common;
402  DEVSUPFUN read_fn;
403  DEVSUPFUN lin_convert;
404 } commonset;
405 
406 static commonset devNtpShmLiOk = {
407  {6, NULL, NULL, (DEVSUPFUN)&init_record, (DEVSUPFUN)&get_ioint_info},
408  (DEVSUPFUN)&read_ok,
409  NULL
410 };
411 
412 static commonset devNtpShmLiFail = {
413  {6, NULL, NULL, (DEVSUPFUN)&init_record, (DEVSUPFUN)&get_ioint_info},
414  (DEVSUPFUN)&read_fail,
415  NULL
416 };
417 
418 static commonset devNtpShmAiDelta = {
419  {6, NULL, NULL, (DEVSUPFUN)&init_record, (DEVSUPFUN)&get_ioint_info},
420  (DEVSUPFUN)&read_delta,
421  NULL
422 };
423 
424 static drvet ntpShared = {
425  2,
426  (DRVSUPFUN)&ntpShmReport,
427  (DRVSUPFUN)&ntpShmInit,
428 };
429 
430 #include <epicsExport.h>
431 
432 extern "C"{
433  epicsExportAddress(drvet, ntpShared);
434  epicsExportAddress(dset, devNtpShmLiOk);
435  epicsExportAddress(dset, devNtpShmLiFail);
436  epicsExportAddress(dset, devNtpShmAiDelta);
437  epicsExportRegistrar(ntpShmRegister);
438 }
epicsUInt32 event
Definition: ntpShm.cpp:102
shmSegment * seg
Definition: ntpShm.cpp:107
int precision
Definition: ntpShm.cpp:87
int nsamples
Definition: ntpShm.cpp:88
DEVSUPFUN read_fn
Definition: ntpShm.cpp:402
int leap
Definition: ntpShm.cpp:86
int notify_1strx
Definition: ntpShm.cpp:110
#define RETRY_TIME
Definition: ntpShm.cpp:70
#define isfinite
Definition: mrfCommon.h:326
epicsTimeStamp lastRx
Definition: ntpShm.cpp:116
unsigned int numFail
Definition: ntpShm.cpp:119
bool lastValid
Definition: ntpShm.cpp:114
epicsExportAddress(drvet, ntpShared)
#define SYNC()
Definition: ntpShm.cpp:64
epicsExportRegistrar(ntpShmRegister)
epicsMutexId ntplock
Definition: ntpShm.cpp:98
Definition: evrdump.c:37
int notify_nomap
Definition: ntpShm.cpp:109
void time2ntp(const char *evrname, int segid, int event)
Definition: ntpShm.cpp:266
int rxUsec
Definition: ntpShm.cpp:84
Base object inspection.
Definition: object.h:378
time_t rxSec
Definition: ntpShm.cpp:83
int mode
Definition: ntpShm.cpp:75
epicsTimeStamp lastStamp
Definition: ntpShm.cpp:115
DEVSUPFUN get_ioint_info
static Object * getObject(const std::string &name)
Definition: object.cpp:107
IOSCANPVT lastUpdate
Definition: ntpShm.cpp:112
unsigned int numOk
Definition: ntpShm.cpp:118
int segid
Definition: ntpShm.cpp:105
DEVSUPFUN lin_convert
Definition: ntpShm.cpp:403
CALLBACK ntp_cb
Definition: ntpShm.cpp:100
int valid
Definition: ntpShm.cpp:89
Base interface for EVRs.
Definition: evr.h:45
#define MRF_EVENT_TS_COUNTER_RST
Definition: mrfCommon.h:124
dset common
Definition: ntpShm.cpp:401
int count
Definition: ntpShm.cpp:76
#define NTPD_SEG0
Definition: ntpShm.cpp:68
DEVSUPFUN init_record
virtual void eventNotifyAdd(epicsUInt32 event, eventCallback, void *)=0
time_t stampSec
Definition: ntpShm.cpp:79
EVR * evr
Definition: ntpShm.cpp:104
int stampUsec
Definition: ntpShm.cpp:80
virtual bool getTimeStamp(epicsTimeStamp *ts, epicsUInt32 event)=0