-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathadjtimex.c
2266 lines (2058 loc) · 62.9 KB
/
adjtimex.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
#define DEBUG
adjtimex - display or set the kernel time variables
AUTHORS
ssd at nevets.oau.org (Steven S. Dick)
jrvz at comcast.net (Jim Van Zandt)
TODO: review code controlled by DEBUG and possibly
change control to verbose flag.
*/
#define _GNU_SOURCE /* strptime is a GNU extension */
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <mat.h>
#include <math.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/timex.h>
#include <sys/types.h>
#include <syscall.h>
#include <time.h>
#include <signal.h>
#include <unistd.h>
#include <utmp.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include "config.h"
#ifdef HAVE_LINUX_RTC_H
#include <linux/rtc.h>
#else
struct rtc_time {
int tm_sec;
int tm_min;
int tm_hour;
int tm_mday;
int tm_mon;
int tm_year;
int tm_wday;
int tm_yday;
int tm_isdst;
};
#define RTC_UIE_ON _IO('p', 0x03) /* Update int. enable on */
#define RTC_UIE_OFF _IO('p', 0x04) /* ... off */
#define RTC_PIE_OFF _IO('p', 0x06) /* Periodic int. enable off */
#define RTC_RD_TIME _IOR('p', 0x09, struct rtc_time) /* Read RTC time */
#endif /* HAVE_LINUX_RTC_H */
int F_print = 0;
#ifndef LOG_PATH
#define LOG_PATH "/var/log/clocks.log"
#endif
#ifndef WTMP_PATH
#define WTMP_PATH "/var/log/wtmp"
#endif
/* Here the information for CMOS clock adjustments is kept. */
#define ADJPATH "/etc/adjtime"
/* used for debugging the code. */
/* #define DEBUG */
#define RTC_JITTER .000025 /* assumed error in reading CMOS clock (sec) */
#define SECONDSPERDAY 86400
#define BUFLEN 128
#ifndef USE_INLINE_ASM_IO
static int port_fd = -1; /* used to access the RTC via /dev/port I/O */
#endif
static char *cmos_device; /* filename of the RTC device */
static int cmos_fd = -1;
struct hack {
double ref; /* reference time for time hack */
double sigma_ref; /* expected error in above (or zero if
no reference time available) */
time_t log; /* reference time as time_t */
double sys; /* system time */
int tick; /* "tick" system parameter */
int freq; /* "freq" system parameter */
char sys_ok; /* nonzero if system clock undisturbed
during previous period */
double cmos; /* CMOS time */
char cmos_ok; /* nonzero if cmos clock undisturbed
during previous period */
char valid; /* bit flags (see below) */
double sys_rate, sys_sigma;
double cmos_rate, cmos_sigma;
double relative_rate, relative_sigma;
} prev;
/* constants for `valid' member of struct hack */
#define CMOS_VALID 1
#define SYS_VALID 2
#define REF_VALID 4
struct cmos_adj
{
double ca_factor;
long ca_adj_time;
double ca_remainder;
} ca;
struct timex txc;
/* command line option variables */
static int using_dev_rtc = -1; /* 0 = not using /dev/rtc
1 = using /dev/rtc
-1 = will use /dev/rtc if available,
but have not tried to open it yet */
static int nointerrupt = 0; /* 0 = try to detect RTC tick via interrupt
1 = via another method */
int adjusting = 0;
int force_adjust;
int comparing = 0;
int logging = 0;
int reviewing = 0;
int resetting = 0; /* nonzero if need to call
reset_time_status() */
int interval = 10;
int count = 8;
int marked;
int universal = 0;
int verbose = 0;
int watch; /* nonzero if time specified on command line */
int undisturbed_sys = 0;
int undisturbed_cmos = 0;
char *log_path = LOG_PATH;
char *timeserver; /* points to name of timeserver */
enum {HELP=131};
struct option longopt[]=
{
{"adjust", 2, NULL, 'a'},
{"force-adjust", 0, &force_adjust, 1},
{"compare", 2, NULL, 'c'},
{"log", 2, NULL, 'l'},
{"esterror", 1, NULL, 'e'},
{"frequency", 1, NULL, 'f'},
{"host", 1, NULL, 'h'},
{"help", 0, NULL, HELP},
{"interval", 1, NULL, 'i'},
{"maxerror", 1, NULL, 'm'},
{"offset", 1, NULL, 'o'},
{"print", 0, NULL, 'p'},
{"review", 2, NULL, 'r'},
{"singleshot", 1, NULL, 's'},
{"status", 1, NULL, 'S'},
{"reset", 0, NULL, 'R'},
{"timeconstant", 1, NULL, 'T'},
{"tick", 1, NULL, 't'},
{"utc", 0, NULL, 'u'},
{"directisa", 0, NULL, 'd'},
{"nointerrupt", 0, NULL, 'n'},
{"version", 0, NULL, 'v'},
{"verbose", 0, NULL, 'V'},
{"watch", 0, NULL, 'w'},
{0,0,0,0}
};
static void usage(void);
static inline void outb (short port, char val);
static inline void outb (short port, char val);
static inline unsigned char inb (short port);
static void cmos_init ();
static void cmos_init_directisa ();
static inline int cmos_read_bcd (int addr);
static void cmos_read_time (time_t *cmos_timep, double *sysp);
static void busywait_uip_fall(struct timeval *timestamp);
static void busywait_second_change(struct tm *cmos, struct timeval *timestamp);
static void compare(void);
static void failntpdate();
static void reset_time_status(void);
static struct cmos_adj *get_cmos_adjustment(void);
static void log_times(void);
static int valid_system_rate(double ftime_sys, double ftime_ref, double sigma_ref);
static int valid_cmos_rate(double ftime_cmos, double ftime_ref, double sigma_ref);
static void sethackent(void);
static void endhackent(void);
static struct hack *gethackent(void);
static void puthackent(struct hack *ph);
static time_t mkgmtime(struct tm *tp);
static int compare_tm(struct tm *first, struct tm *second);
static void *xmalloc(int n);
static void *xrealloc(void *pv, int n);
static void review(void);
static void kalman_update(double *x, int xr, double *p, double *h,
double *z, int zr, double *r);
void
usage(void)
{
char msg[]=
"\n"
"Usage: adjtimex [OPTION]... \n"
"Mandatory or optional arguments to long options are mandatory or optional\n"
"for short options too.\n"
"\n"
"Get/Set Kernel Time Parameters:\n"
" -p, --print print values of kernel time variables\n"
" -t, --tick val set the kernel tick interval in usec\n"
" -f, --frequency newfreq set system clock frequency offset\n"
" -s, --singleshot adj slew the system clock by adj usec\n"
" -S, --status val set kernel clock status\n"
" -R, --reset reset status after setting parameters\n"
" (needed for early kernels)\n"
" -o, --offset adj add a time offset of adj usec\n"
" -m, --maxerror val set maximum error (usec)\n"
" -e, --esterror val set estimated error (usec)\n"
" -T, --timeconstant val set phase locked loop time constant\n"
" -a, --adjust[=count] set system clock parameters per CMOS \n"
" clock or (with --review) log file\n"
" --force-adjust override +-1 percent sanity check\n"
"\n"
"Estimate Systematic Drifts:\n"
" -c, --compare[=count] compare system and CMOS clocks\n"
" -i, --interval tim set clock comparison interval (sec)\n"
" -l, --log[=file] log current times to file\n"
" -h, --host timeserver query the timeserver\n"
" -w, --watch get current time from user\n"
" -r, --review[=file] review clock log file, estimate drifts\n"
" -u, --utc the CMOS clock is set to UTC\n"
" -d, --directisa access the CMOS clock directly\n"
" -n, --nointerrupt bypass the CMOS clock interrupt access method\n"
"\n"
"Informative Output:\n"
" --help print this help, then exit\n"
" -v, --version print adjtimex program version, then exit\n"
" -V, --verbose increase verbosity\n"
;
fputs(msg, stdout);
exit(0);
}
/* return apparent value of USER_HZ in HZ, minimum nominal and maximum
values for tick in TICK_MIN TICK_MID and TICK_MAX, and maximum
frequency offset in MAXFREQ */
static
void probe_time(int *hz, int *tick_min, int *tick_mid, int *tick_max,
long *maxfreq)
{
struct timex txc;
int tick_orig, tick_lo, tick_try, tick_hi, i;
txc.modes = 0;
adjtimex(&txc); /* fetch original value */
txc.modes = ADJ_TICK;
if (adjtimex(&txc) == -1) /* try setting with no change */
{
perror("adjtimex");
exit(1);
}
*maxfreq = txc.tolerance;
tick_orig = tick_hi = txc.tick;
tick_lo = tick_hi*2/3;
for (i = 0; i < 15; i++)
{ /* conduct binary search for minimum
accepted tick value */
// printf(" %d < minimum accepted tick value <= %d\n", tick_lo, tick_hi);
txc.tick = tick_try = (tick_lo + tick_hi)/2;
txc.modes = ADJ_TICK;
if (adjtimex(&txc) == -1) tick_lo = tick_try;
else tick_hi = tick_try;
}
*tick_min = tick_hi;
tick_lo = tick_orig;
tick_hi = tick_lo*4/3;
for (i = 0; i < 15; i++)
{ /* conduct binary search for maximum
accepted tick value */
// printf(" %d <= maximum accepted tick value < %d\n", tick_lo, tick_hi);
txc.tick = tick_try = (tick_lo + tick_hi)/2;
txc.modes = ADJ_TICK;
if (adjtimex(&txc) == -1) tick_hi = tick_try;
else tick_lo = tick_try;
}
*tick_max = tick_lo;
*tick_mid = (*tick_min + *tick_max)/2;
*hz = (900000/ *tick_min + 1100000/ *tick_max)/2;
txc.tick = tick_orig;
txc.modes = ADJ_TICK;
adjtimex(&txc); /* reset to original value */
}
int
main(int argc, char *argv[])
{
int ret, saveerr, changes;
extern char *optarg;
int c;
txc.modes = 0;
while((c = getopt_long_only(argc, argv,
"a::c::l::e:f:h:i:m:o:pr::s:S:RT:t:udvVw",
longopt, NULL)) != -1)
{
switch(c)
{
case 0: break; /* options that just set a variable
are handled in longopt */
case 'a':
adjusting = 1;
if (optarg)
count = atoi(optarg);
break;
case 'c':
comparing = 1;
if (optarg)
count = atoi(optarg);
break;
case 'l':
logging = 1;
if (optarg)
log_path = strdup(optarg);
if (!log_path)
{
fprintf (stderr, "insufficient memory\n");
exit(1);
}
break;
case 'h':
timeserver = strdup(optarg);
if (!timeserver)
{
fprintf (stderr, "insufficient memory\n");
exit(1);
}
logging = 1;
break;
case 'r':
reviewing = 1;
if (optarg)
log_path = strdup(optarg);
if (!log_path)
{
fprintf (stderr, "insufficient memory\n");
exit(1);
}
break;
case 'i':
interval = atoi (optarg);
if (interval <= 1 || interval > 1000)
{
fprintf (stderr, "repeat interval out of range\n");
exit (1);
}
break;
case 'p':
F_print = 1;
break;
case 'o':
txc.offset = atol(optarg);
txc.modes |= ADJ_OFFSET;
break;
case 's':
txc.offset = atol(optarg);
txc.modes |= ADJ_OFFSET_SINGLESHOT;
break;
case 'S':
txc.status = atol(optarg);
txc.modes |= ADJ_STATUS;
break;
case 'R': resetting = 1; break;
case 'f':
txc.freq = atol(optarg);
txc.modes |= ADJ_FREQUENCY;
break;
case 'm':
txc.maxerror = atol(optarg);
txc.modes |= ADJ_MAXERROR;
break;
case 'e':
txc.esterror = atol(optarg);
txc.modes |= ADJ_ESTERROR;
break;
case 'T':
txc.constant = atol(optarg);
txc.modes |= ADJ_TIMECONST;
break;
case 't':
txc.tick = atol(optarg);
txc.modes |= ADJ_TICK;
break;
case 'u':
universal = 1;
break;
case 'd':
using_dev_rtc = 0;
break;
case 'n':
nointerrupt = 1;
break;
case 'v':
{
printf("adjtimex %s\n", VERSION);
exit(0);
}
case 'V':
verbose++;
break;
case 'w':
watch = 1;
logging = 1;
break;
case HELP:
usage();
break;
case '?':
default:
fprintf(stderr, "Unrecognized option %s\nFor valid "
"options, try 'adjtimex --help'\n", argv[optind-1]);
exit(1);
}
}
changes = txc.modes;
if (count <= 0 ) {
fprintf(stderr, "loop count out of range\n");
exit(1);
}
if (reviewing)
{
review();
exit(0);
}
if (adjusting || comparing)
{
if (changes || F_print)
{
fprintf(stderr,
"-adjust or -compare cannot be used with any other options that"
" set values\n");
exit(1);
}
compare();
}
if (logging)
{
/*
if (geteuid() != 0)
{
fprintf(stderr, "sorry, only root can record clock comparisons\n");
exit(1);
}
*/
log_times();
exit(0);
}
if ((txc.modes & ADJ_OFFSET_SINGLESHOT) == ADJ_OFFSET_SINGLESHOT &&
txc.modes != ADJ_OFFSET_SINGLESHOT)
{
fprintf(stderr, "-singleshot cannot be used with "
"any other option except -print\n");
usage();
}
errno = 0;
ret = adjtimex(&txc);
saveerr = errno;
if (F_print) {
printf(" mode: %d\n"
" offset: %ld\n"
" frequency: %ld\n"
" maxerror: %ld\n"
" esterror: %ld\n"
" status: %d\n"
"time_constant: %ld\n"
" precision: %ld\n"
" tolerance: %ld\n"
" tick: %ld\n"
" raw time: %ds %dus = %d.%06d\n",
txc.modes,
txc.offset,
txc.freq,
txc.maxerror,
txc.esterror,
txc.status,
txc.constant,
txc.precision,
txc.tolerance,
txc.tick,
(int)txc.time.tv_sec,
(int)txc.time.tv_usec,
(int)txc.time.tv_sec,
(int)txc.time.tv_usec);
if (saveerr == 0 && ret != 0)
printf(" return value = %d\n", ret);
}
if (ret != 0 && saveerr != 0) {
if (ret != -1)
fprintf(stderr, "%d ", ret);
errno = saveerr;
perror("adjtimex");
{
int hz, tick_min, tick_mid, tick_max;
long maxfreq;
probe_time(&hz, &tick_min, &tick_mid, &tick_max, &maxfreq);
printf("for this kernel:\n"
" USER_HZ = %d (nominally %d ticks per second)\n"
" %d <= tick <= %d\n"
" %ld <= frequency <= %ld\n",
hz, hz, tick_min, tick_max, -maxfreq, maxfreq);
}
exit(1);
}
if (changes && resetting)
reset_time_status();
return 0;
}
static inline void
outb (short port, char val)
{
#ifdef USE_INLINE_ASM_IO
__asm__ volatile ("out%B0 %0,%1"::"a" (val), "d" (port));
#else
lseek (port_fd, port, 0);
write (port_fd, &val, 1);
#endif
}
static inline unsigned char
inb (short port)
{
unsigned char ret;
#ifdef USE_INLINE_ASM_IO
__asm__ volatile ("in%B0 %1,%0":"=a" (ret):"d" (port));
#else
lseek (port_fd, port, 0);
read (port_fd, &ret, 1);
#endif
return ret;
}
/*
* Main initialisation of CMOS clock access methods, for all modes.
* Set the global variable cmos_device to the first available RTC device
* driver filename (/dev/rtc, /dev/rtc0, etc.), and set cmos_fd to
* a file descriptor for it.
* Failing that, select and initialize direct I/O ports mode.
*/
static
void cmos_init ()
{
/*
following explanation taken from hwclock sources:
/dev/rtc is conventionally chardev 10/135
ia64 uses /dev/efirtc, chardev 10/136
devfs (obsolete) used /dev/misc/... for miscdev
new RTC framework + udev uses dynamic major and /dev/rtc0.../dev/rtcN
*/
char *fls[] = {
#ifdef __ia64__
"/dev/efirtc",
"/dev/misc/efirtc",
#endif
"/dev/rtc",
"/dev/rtc0",
"/dev/misc/rtc",
NULL
};
char **p=fls;
if (using_dev_rtc < 0)
{
while ((cmos_device=*p++))
{
cmos_fd = open (cmos_device, O_RDONLY);
if (cmos_fd >= 0)
{
if (verbose)
fprintf (stdout, "opened %s for reading\n", cmos_device);
using_dev_rtc = 1;
return;
}
if (verbose)
{
int saveerr=errno;
fprintf (stdout, "adjtimex: cannot open %s for reading\n",
cmos_device);
errno = saveerr;
perror("adjtimex");
}
}
using_dev_rtc = 0;
}
else if (using_dev_rtc > 0)
return;
/* otherwise do direct I/O */
cmos_init_directisa();
}
/*
* Initialise CMOS clock access method, only for direct I/O ports mode.
* Set the global variable port_fd to a file descriptor for /dev/port.
* This function can safely be called repeatedly (does nothing the
* second and following times).
*/
static
void cmos_init_directisa ()
{
#ifdef USE_INLINE_ASM_IO
if (verbose)
fprintf (stdout, "using I/O ports\n");
if (ioperm (0x70, 2, 1))
{
fprintf (stderr, "adjtimex: unable to get I/O port access\n");
exit (1);
}
#else
if (port_fd >= 0) /* already initialised */
return;
if (verbose)
fprintf (stdout, "using /dev/port I/O\n");
if (port_fd < 0)
port_fd = open ("/dev/port", O_RDWR);
if (port_fd < 0)
{
perror ("adjtimex: unable to open /dev/port read/write");
exit (1);
}
if (verbose)
fprintf (stdout, "opened /dev/port for reading\n");
if (lseek (port_fd, 0x70, 0) < 0 || lseek (port_fd, 0x71, 0) < 0)
{
perror ("adjtimex: unable to seek port 0x70 in /dev/port");
exit (1);
}
#endif
}
#define CMOS_READ(addr) ({outb(0x70,(addr)|0x80); inb(0x71);})
static inline int
cmos_read_bcd (int addr)
{
int b;
b = CMOS_READ (addr);
return (b & 15) + (b >> 4) * 10;
}
static int timeout; /* An alarm signal has occurred */
static void
alarm_handler (int const dummy) {
timeout = 1;
}
/*
* starts (or cancels) a 2 second timeout period
* the global boolean timeout indicates that the timeout has been reached
*/
static void
alarm_timeout (int onoff) {
static struct sigaction oldaction;
if (onoff)
{
struct sigaction action;
action.sa_handler = &alarm_handler;
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
sigaction(SIGALRM, &action, &oldaction); /* Install our signal handler */
timeout = 0; /* reset global timeout flag */
alarm(2); /* generate SIGALARM 2 seconds from now */
}
else
{
alarm(0); /* cancel alarm */
sigaction(SIGALRM, &oldaction, NULL); /* remove our signal handler */
}
}
/* return CMOS time in CMOS_TIMEP and sytem time in SYSP. cmos_init()
must have been called before this function. */
static void
cmos_read_time (time_t *cmos_timep, double *sysp)
{
int rc;
struct tm tm;
static int summertime_correction=0;
static int sanity_checked=0;
time_t cmos_time;
struct timeval now;
int noint_fallback = 1; /* detect tick by 0 => uip, 1 => time change */
int got_tick = 0;
int got_time = 0;
int saveerr;
int type_uie, count;
double update_delay;
if (using_dev_rtc > 0) /* access the CMOS clock thru /dev/rtc */
{
if (!nointerrupt) /* get RTC tick via update interrupt */
{
struct timeval before;
gettimeofday(&before, NULL);
/* Ordinarily, reading /dev/rtc does not wait until the
beginning of the next second. It only returns the current timer
value, so it's only accurate to 1 sec which isn't good enough for
us. I see this comment in drivers/char/rtc.c, function
rtc_get_rtc_time(), in the kernel sources:
* read RTC once any update in progress is done. The update
* can take just over 2ms. We wait 10 to 20ms. There is no need to
* to poll-wait (up to 1s - eeccch) for the falling edge of RTC_UIP.
* If you need to know *exactly* when a second has started, enable
* periodic update complete interrupts, (via ioctl) and then
* immediately read /dev/rtc which will block until you get the IRQ.
* Once the read clears, read the RTC time (again via ioctl). Easy.
*/
ioctl (cmos_fd, RTC_PIE_OFF, NULL); /* disable periodic interrupts */
rc = ioctl (cmos_fd, RTC_UIE_ON, NULL); /* enable update
complete interrupts */
if (rc == -1) /* no interrupts? fallback to busywait */
{
if (verbose)
fprintf(stdout,
"%s doesn't allow user access to update interrupts\n"
" - using busy wait instead\n", cmos_device);
nointerrupt = 1;
}
else /* wait for update-ended interrupt */
{
unsigned long interrupt_info;
if (verbose)
fprintf (stdout, "waiting for CMOS update-ended interrupt\n");
do {
{
/* avoid blocking even if /dev/rtc never becomes readable */
fd_set readfds;
struct timeval tv;
int retval;
FD_ZERO(&readfds);
FD_SET(cmos_fd, &readfds);
tv.tv_sec=2; tv.tv_usec=0; /* wait up to 2 sec */
retval = select(cmos_fd+1, &readfds, NULL, NULL, &tv);
if (retval <= 0)
{
ioctl (cmos_fd, RTC_UIE_OFF, NULL); /* disable update complete
interrupts */
if (retval == -1)
perror("select()");
if (!retval)
if (verbose)
fprintf(stdout,
"timeout waiting for a CMOS update interrupt from %s\n"
" - using busy wait instead\n", cmos_device);
nointerrupt = 1;
goto directisa;
}
}
rc = read(cmos_fd, &interrupt_info, sizeof(interrupt_info));
saveerr = errno;
gettimeofday(&now, NULL);
if (rc == -1)
{
/* no timeout, but read(/dev/rtc) failed for another reason */
char message[128];
snprintf(message, sizeof(message),
"adjtimex: "
"read() from %s to wait for clock tick failed",
cmos_device);
perror(message);
exit(1);
}
type_uie = (int)(interrupt_info & 0x80);
count = (int)(interrupt_info >> 8);
} while (((type_uie == 0) || (count > 1)) && !nointerrupt);
/* The low-order byte holds
the interrupt type. The first read may succeed
immediately, but in that case the byte is zero, so we
know to try again. If there has been more than one
interrupt, then presumably periodic interrupts were
enabled. We need to try again for just the update
interrupt. */
update_delay = (now.tv_sec + .000001*now.tv_usec) -
(before.tv_sec + .000001*before.tv_usec);
if ((type_uie) && (count == 1) && (update_delay > .001))
got_tick = 1;
else
{
if (verbose)
fprintf(stdout, "CMOS interrupt with status "
"0x%2x came in only %8.6f sec\n"
" - using busy wait instead\n",
(unsigned int)interrupt_info&0xff, update_delay);
nointerrupt = 1;
}
} /* end of successful RTC_UIE_ON case */
} /* end of interrupt tryouts */
/* At this stage, we either just detected the clock tick via an
update interrupt, or detected nothing yet (interrupts were
bypassed, unavailable, or timeouted). Fallback to busywaiting
for the tick. */
directisa:
if (!got_tick)
{
if (noint_fallback)
{
busywait_second_change(&tm, &now);
got_time = 1;
}
else
busywait_uip_fall(&now);
got_tick = 1;
}
/* At this stage, we just detected the clock tick, by any method.
Now get this just beginning RTC second, unless we already have it */
if (!got_time)
{
rc = ioctl (cmos_fd, RTC_RD_TIME, &tm);
/* RTC_RD_TIME can fail when the device driver detects
that the RTC isn't running or contains invalid data.
Such failure has been detected earlier, unless: We used
noint_fallback=1 to get busywait_uip_fall() as fallback.
Or: UIE interrupts do beat, but RTC is invalid. */
if (rc == -1)
{
char message[128];
snprintf(message, sizeof(message),
"adjtimex: "
"ioctl(%s, RTC_RD_TIME) to read the CMOS clock failed",
cmos_device);
perror(message);
exit(1);
}
}
/* disable update complete interrupts
It could seem more natural to do this above, just after we
actually got the interrupt. But better do it here at the end,
after all time-critical operations including the RTC_RD_TIME. */
ioctl (cmos_fd, RTC_UIE_OFF, NULL);
}
else /* access the CMOS clock thru I/O ports */
{
/* The "do" loop is "low-risk programming" */
/* In theory it should never run more than once */
do
{
busywait_uip_fall(&now);
tm.tm_sec = cmos_read_bcd (0);
tm.tm_min = cmos_read_bcd (2);
tm.tm_hour = cmos_read_bcd (4);
tm.tm_wday = cmos_read_bcd(6)-1;/* RTC uses 1 - 7 for day of the week, 1=Sunday */
tm.tm_mday = cmos_read_bcd (7);
tm.tm_mon = cmos_read_bcd(8)-1; /* RTC uses 1 base */
/* we assume we're not running on a PS/2, where century is
in byte 55 */
tm.tm_year = cmos_read_bcd(9)+100*cmos_read_bcd(50)-1900;
} while (tm.tm_sec != cmos_read_bcd (0));
}
tm.tm_isdst = -1; /* don't know whether it's summer time */
if (universal)
cmos_time = mkgmtime(&tm);
else
cmos_time = mktime(&tm);
cmos_time += summertime_correction;
if (verbose)
printf ("CMOS time %s (%s) = %ld\n", asctime (&tm),
universal?"assuming UTC":
(summertime_correction?"assuming local time with summer time adjustment":
"assuming local time without summer time adjustment"),
cmos_time);
if (!sanity_checked)
{
/* There are clues to whether the CMOS clock is set to
summer time, which could be used as suggested by Alain
Guibert <alguibert at free.fr>:
Since version 2.5, hwclock records CMOS timezone UTC or
LOCAL as 1st item of 3rd line of /etc/adjtime. If it's
"UTC", take offset 0 as if --utc was used. Otherwise: Since
version 2.20, hwclock records CMOS timezone offset in 3rd
item of 3rd line of /etc/adjtime. If it's there, take
it. Otherwise if last adjustment date (2nd item of 1st line)
exists and is not zero, take timezone offset at this last
write time. Otherwise take the possibly false current
offset.
However, /etc/adjtime cannot be relied on. The user
might have booted Windows, which could have adjusted the
CMOS clock (including a summer time correction) without
updating /etc/adjtime. Instead, we use a heuristic: if
the CMOS and system times differ by more than six
minutes, try shifting the CMOS time by some multiple of
one hour.
*/
if (now.tv_sec<cmos_time)
summertime_correction = -((cmos_time-now.tv_sec+6*60)/(60*60))*(60*60);
else
summertime_correction = (((now.tv_sec-cmos_time+6*60)/(60*60))*(60*60));
if (abs(summertime_correction) > 13*60*60)
{
printf("WARNING: CMOS time %ld differs from system time %ld by %3.2f hours\n",
cmos_time, now.tv_sec, (summertime_correction)/3600.);
if(logging)
{
printf("logging suppressed\n");
logging=0;
}
}
if (verbose && summertime_correction)
printf("adjusting CMOS times by %d hour\n",
summertime_correction/(60*60));
cmos_time += summertime_correction; /* assume start/end of
summer time or local
time/UTC difference */
if (abs(cmos_time-now.tv_sec)>6*60) /* still different by more than 6 min? */
{
if (cmos_time>now.tv_sec)
printf("WARNING: CMOS time is %3.2f min ahead of system clock\n",
(cmos_time-now.tv_sec)/60.);
else
printf("WARNING: CMOS time is %3.2f min behind system clock\n",
(now.tv_sec-cmos_time)/60.);
if(logging)
{
printf("logging suppressed\n");
logging=0;
}
}
sanity_checked=1;
}
*cmos_timep=cmos_time;
*sysp = now.tv_sec + .000001*now.tv_usec;
}
/*
* busywait for UIP fall and timestamp this event
*
* Maintainance note: In order to detect its fall, we first have to
* detect the update-in-progress flag up state. This UIP up state lasts
* for 2 ms each second. Detecting a 2 ms event, when other processes
* can steal us one or several 10 ms timeslices, this is something that