home *** CD-ROM | disk | FTP | other *** search
/ Source Code 1992 March / Source_Code_CD-ROM_Walnut_Creek_March_1992.iso / usenet / altsrcs / 2 / 2943 / gc1000.c
Encoding:
C/C++ Source or Header  |  1991-03-03  |  10.8 KB  |  452 lines

  1. /*
  2.  *   gc1000.c
  3.  *
  4.  *   This program will connect to the Hath/Zenith GC-1000
  5.  *   "Most Accurate Clock" to collect the date and time.
  6.  *
  7.  *   The assumptions are that some form of System V is being
  8.  *   used or that there is a kernel variable named "one_sec"
  9.  *   that counts down from HZ to zero which is when the
  10.  *   time of day clock changes to the next second (and one_sec
  11.  *   is reloaded with HZ.
  12.  *
  13.  *   It is further assumed that the GC-1000 clock is set for
  14.  *   9600bps (inducing a 25ms delay) and NOT in auto-mode, i.e.
  15.  *   a time line is sent when RTS (pin 5) goes high.  This
  16.  *   should be wired to DTR since it goes high when the port
  17.  *   is opened.  Note that the GC-1000 waits one second after
  18.  *   the RTS (DTR) transition before it sends a time line.
  19.  *
  20.  *   Copyright (copr) 1991, William L. Kennedy Jr.  You may do
  21.  *   anything with this intellectual property other than claim
  22.  *   that you wrote it.  There is no expressed or implied
  23.  *   warranty, if you break it or it breaks something of yours,
  24.  *   that's just too bad.
  25.  */
  26. #include <sys/types.h>
  27. #include <sys/param.h>
  28. #include <stdio.h>
  29. #include <termio.h>
  30. #include <fcntl.h>
  31. #include <setjmp.h>
  32. #include <signal.h>
  33. #include <time.h>
  34. #include <nlist.h>
  35.  
  36. /*
  37.  *   This is the structure which gets filled in
  38.  *   with the ASCII data received from the GC-1000
  39.  */
  40. struct rs232 {
  41.     char hour[2];
  42.     char colon_1;
  43.     char minute[2];
  44.     char colon_2;
  45.     char second[2];
  46.     char point;
  47.     char tenths;
  48.     char spaces[5];
  49.     char month[2];
  50.     char slash_1;
  51.     char day[2];
  52.     char slash_2;
  53.     char year[2];
  54.     char cr;
  55.     char eos;
  56. };
  57.  
  58. int wwv_fd, mem_fd, debug;
  59. jmp_buf timed_out;
  60.  
  61. int time_out();
  62.  
  63. /*
  64.  *   The termio structure might need some adjusting to fit
  65.  *   various hardware but this (and the and 0x7f on each
  66.  *   character read) seems to fit every place I've tried it.
  67.  */
  68. struct termio wwv_tty = {
  69.     0,
  70.     ONLCR,
  71.     B9600 | CREAD | CS8 | HUPCL,
  72.     0,
  73.     0,
  74.     { 0377, 0377, 0377, 0377, 0, 0, 0, 0 }
  75. };
  76.  
  77. /*
  78.  *   This structure is used to get the location of the kernel
  79.  *   variable one_sec.  When one_sec goes to zero (or negative)
  80.  *   the time of day value (what's returned by time(2) ) gets
  81.  *   incremented and one_sec is reloaded with HZ.  The hardware
  82.  *   timer interrupt decrements one_sec.  Note that this value
  83.  *   is rewritten, as HZ, to /dev/kmem when root is setting the
  84.  *   time to agree with the GC-1000.
  85.  */
  86. struct nlist nl[] = {
  87.     { "one_sec" },
  88.     { (char *) 0 }
  89. };
  90.  
  91. /*
  92.  *   The DEF_TTY is what is used if no -t argument is
  93.  *   provided on the command line.  The DEF_KMEM and
  94.  *   DEF_UNIX defaults should not need changing but you
  95.  *   can point at something else with the -k and -u
  96.  *   command line arguments.
  97.  */
  98. #define DEF_TTY "/dev/tty05"
  99. #define DEF_KMEM "/dev/kmem"
  100. #define DEF_UNIX "/unix"
  101. #define MAX_TRIES 3        /* Number of read failures to tolerate  */
  102. #define MAX_DIFF  45L        /* Maximum seconds in error to tolerate */
  103.  
  104. main ( argc, argv )
  105. int argc;
  106. char **argv;
  107. {
  108.  
  109.     extern int optind;
  110.     extern char *optarg;
  111.     long sys_time, time(), wwv_time, now_time, diff_secs;
  112.     int ret, set_time, kmem_mode, one_sec, tries;
  113.     char time_buf[32], tty_name[32], kmem_name[32], unix_name[32];
  114.     char *t;
  115.     struct rs232 char_time, *wwv_buf;
  116.     struct tm *time_now, *localtime();
  117.  
  118.     strcpy(tty_name,DEF_TTY);
  119.     strcpy(kmem_name,DEF_KMEM);
  120.     strcpy(unix_name,DEF_UNIX);
  121.     wwv_buf = (struct rs232 *) &char_time.hour[0];
  122.     mem_fd = tries = set_time = debug = 0;
  123.  
  124.     /*
  125.      *   Process the command line arguments.  Note that if the
  126.      *   program is run where stdout is not a tty (like out of
  127.      *   cron), the debug display is disabled even if you enable
  128.      *   it on the command line.
  129.      */
  130.     while ( -1 != (ret = getopt(argc,argv,"t:k:u:sx")) )
  131.         switch ( ret ) {
  132.  
  133.             case 'k':
  134.                 strcpy(kmem_name,optarg);
  135.                 break;
  136.  
  137.             case 's':
  138.                 set_time++;
  139.                 break;
  140.  
  141.             case 't':
  142.                 strcpy(tty_name,optarg);
  143.                 break;
  144.  
  145.             case 'u':
  146.                 strcpy(unix_name,optarg);
  147.                 break;
  148.  
  149.             case 'x':
  150.                 debug++;
  151.                 break;
  152.  
  153.             case '?':
  154.                 printf("Usage: %s -t tty -k kmem -u unix -s\n");
  155.                 exit(0);
  156.         }
  157.  
  158.     /*
  159.      *   If isatty(1) is false and debugging has been set,
  160.      *   defeat debugging.
  161.      */
  162.     if ( ! isatty(1) && debug )
  163.         debug--;
  164.  
  165.     if ( debug ) {
  166.         one_sec = HZ;
  167.         printf("tty=%s, kmem=%s, unix=%s, set time=%c HZ=%d\n",
  168.             tty_name,kmem_name,unix_name,
  169.             (set_time) ? 'y' : 'n',one_sec );
  170.     }
  171.  
  172.     /*
  173.      *   Now try and open kernel memory.  If we're
  174.      *   superuser, we're eligible to write, else
  175.      *   read only.  We'll use this to determine the
  176.      *   count in the one_sec variable, i.e. the
  177.      *   number of HZ ticks until the next even second.
  178.      *
  179.      *   Note that this isn't necessary unless we're
  180.      *   going to try to set the time.  Only super user
  181.      *   can actually set it, but anyone can look at the
  182.      *   one_sec count.
  183.      */
  184.     kmem_mode = O_RDONLY;
  185.     if ( ! geteuid() && set_time )
  186.         kmem_mode = O_RDWR;
  187.  
  188.     if ( set_time ) {
  189.         mem_fd = open(kmem_name,kmem_mode,0);
  190.         if ( -1 == mem_fd ) {
  191.             perror(kmem_name);
  192.             exit(1);
  193.         }
  194.         if ( -1 == nlist(unix_name,nl) ) {
  195.             fprintf(stderr,"No name list for %s in %s\n",
  196.                 nl[0].n_name,unix_name);
  197.             exit(1);
  198.         }
  199.         if ( -1L == lseek(mem_fd,nl[0].n_value,0) ) {
  200.             perror("kmem lseek");
  201.             exit(1);
  202.         }
  203.         if ( sizeof one_sec !=
  204.             read(mem_fd,(char *) &one_sec,sizeof one_sec) ) {
  205.                 fprintf(stderr,"Bad read on %s\n",kmem_name);
  206.                 exit(1);
  207.         }
  208.         if ( debug )
  209.             printf("At offset %x, %s is %d\n",nl[0].n_value,
  210.                 nl[0].n_name,one_sec);
  211.         /*
  212.          *   Seek back to the one_sec location in case
  213.          *   we're going to write.
  214.          */
  215.         (void) lseek(mem_fd,nl[0].n_value,0);
  216.     }
  217.  
  218.     /*
  219.      *   Now read the GC-1000 and display what it reports
  220.      *   along with the system time until an even second
  221.      *   is reached.  At that point the system time will
  222.      *   be reset (if you have root privileges) and any
  223.      *   difference will be displayed.
  224.      */
  225.     if ( isatty(1) )
  226.         printf("GC-1000                  System\n");
  227.     do {
  228.         ret = rd_gc1000(tty_name,wwv_buf);
  229.  
  230.         /*
  231.          *   Tolerate MAX_TRIES failed attempts to
  232.          *   read the clock, then give up.
  233.          */
  234.         tries += ret;
  235.         if ( MAX_TRIES == tries )
  236.             char_time.tenths = '0';
  237.  
  238.         /*
  239.          *   There is no need to display the reading
  240.          *   if stdout isn't a tty or if there was
  241.          *   an error reading the clock.
  242.          */
  243.         if ( ! isatty(1) || ret )
  244.             continue;
  245.  
  246.         char_time.cr = ' ';
  247.         char_time.eos = '\0';
  248.         sys_time = time ( (long *) 0 );
  249.         time_now = localtime(&sys_time);
  250.         (void) ascftime(time_buf,"%H:%M:%S       %D",time_now);
  251.         printf("\r%s %s",char_time.hour,time_buf);
  252.         if ( set_time ) {
  253.             (void) read(mem_fd,(char *) &one_sec,
  254.                 sizeof one_sec);
  255.             printf(" one_sec %d ",one_sec);
  256.             (void) lseek(mem_fd,nl[0].n_value,0);
  257.         }
  258.         fflush(stdout);
  259.  
  260.     } while ( char_time.tenths != '0' );
  261.  
  262.     fputc('\n',stdout);
  263.  
  264.     /*
  265.      *   Bail out if the clock didn't read OK
  266.      */
  267.     if ( MAX_TRIES == tries )
  268.         exit(1);
  269.  
  270.     /*
  271.      *   We're through with the ASCII representation of
  272.      *   the WWV time, convert it to a time of day so
  273.      *   that we can report any difference or reset the
  274.      *   system time.  Change the punctuation marks to
  275.      *   null characters.
  276.      */
  277.     char_time.colon_1 = char_time.colon_2 = char_time.point = '\0';
  278.     wwv_time = atol(char_time.hour) * 3600L + atol(char_time.minute) * 60L;
  279.     wwv_time += atol(char_time.second);
  280.  
  281.     /*
  282.      *   Now get the local time so that we can see
  283.      *   whether or not we're on time or how far off
  284.      */
  285.     time_now = localtime(&sys_time);
  286.     now_time = (long) time_now->tm_hour * 3600L;
  287.     now_time += (long) time_now->tm_min * 60L;
  288.     now_time += (long) time_now->tm_sec;
  289.     if ( wwv_time > now_time ) {
  290.         diff_secs = wwv_time - now_time;
  291.         t = "slow";
  292.         sys_time += diff_secs;
  293.     }
  294.     if ( now_time > wwv_time ) {
  295.         diff_secs = now_time - wwv_time;
  296.         t = "fast";
  297.         sys_time -= diff_secs;
  298.     }
  299.     if ( wwv_time == now_time ) {
  300.         diff_secs = 0L;
  301.         t = "\007on time!";
  302.     }
  303.  
  304.     /*
  305.      *   Reset the system time and one_sec if authorized.
  306.      *   Don't reset the clock if the time difference is
  307.      *   greater than or equal to MAX_DIFF.  It's quite
  308.      *   possible for the GC-1000 to be off by several
  309.      *   hours and still claim to be correct.  If the system
  310.      *   time is actually off by more than MAX_DIFF, reset
  311.      *   it by hand using date(1).
  312.      */
  313.     if ( kmem_mode && diff_secs && diff_secs < MAX_DIFF ) {
  314.         one_sec = HZ;
  315.         (void) write(mem_fd,(char *) &one_sec, sizeof one_sec);
  316.         (void) stime(&sys_time);
  317.         t = "System time set to WWV";
  318.         diff_secs = 0L;
  319.     }
  320.  
  321.     if ( mem_fd )
  322.         (void) close(mem_fd);
  323.  
  324.     /*
  325.      *   Now display the difference if any
  326.      */
  327.     if ( diff_secs )
  328.         printf("%ld second(s) ",diff_secs);
  329.     printf("%s\n",t);
  330.     exit(0);
  331. }
  332.  
  333. /*
  334.  *   Arrive here on SIGALRM, the various time out setjmps
  335.  *   will receive a 1 return from this catcher and succeed.
  336.  */
  337. int time_out(sig_caught)
  338. int sig_caught;
  339. {
  340.     if ( SIGALRM != sig_caught ) {
  341.         fprintf(stderr,"Caught bogus signal %d\n",sig_caught);
  342.         exit(1);
  343.     }
  344.  
  345.     (void) signal(SIGALRM,SIG_IGN);
  346.     (void) longjmp(timed_out,1);
  347. }
  348.  
  349. /*
  350.  *   Subroutine to open and read the GC-1000 and
  351.  *   fill in the rs232 structure passed in.
  352.  *
  353.  *   Note that the GC-1000 doesn't generate any signal
  354.  *   that is useful as DCD so O_NDELAY is used.  It is
  355.  *   possible to invert and level shift the HI SPEC
  356.  *   signal (the one that lights the LED when synchronized
  357.  *   to within 10ms of WWV) from X101 pin 19 so that you
  358.  *   will simply time out if the clock isn't in HI SPEC.
  359.  *   If you do that, define HI_SPEC_IS_DCD and a blocking
  360.  *   open (with a five second time out) will handle it.
  361.  */
  362. int rd_gc1000(tty_name,wwv_buf)
  363. char *tty_name;
  364. struct rs232 *wwv_buf;
  365. {
  366.     char *s, c;
  367.     int open_mode, count;
  368.  
  369.     open_mode = O_RDONLY;
  370.     s = &wwv_buf->hour[0];
  371.     *s = '\0';
  372.     count = 1;        /* Does double duty as an error return */
  373.  
  374. #ifdef HI_SPEC_IS_DCD
  375.     /*
  376.      *   Wrap a time out around a blocking open five
  377.      *   seconds seems reasonable.
  378.      */
  379.     (void) alarm(5);
  380.     (void) signal(SIGALRM,time_out);
  381.     if ( setjmp(timed_out) ) {
  382.         fprintf(stderr,"Open time out on %s\n",tty_name);
  383.         (void) alarm(0);
  384.         return(1);
  385.     }
  386. #else
  387.     open_mode |= O_NDELAY;
  388. #endif
  389.  
  390.     wwv_fd = open(tty_name,open_mode,0);
  391.  
  392. #ifdef HI_SPEC_IS_DCD
  393.     (void) alarm(0);
  394.     (void) signal(SIGALRM,SIG_IGN);
  395. #endif
  396.  
  397.     if ( -1 == wwv_fd ) {
  398.         perror("gc1000 open");
  399.         return(1);
  400.     }
  401.  
  402.     /*
  403.      *   The tty port is open, set the baud rate
  404.      */
  405.     if ( -1 == ioctl(wwv_fd, TCSETA, &wwv_tty) ) {
  406.         perror("gc1000 TCSETA");
  407.         goto read_exit;
  408.     }
  409.  
  410.     /*
  411.      *   Allow two seconds for the read to complete
  412.      *   although it will only take 25ms at 9600bps
  413.      */
  414.     (void) alarm(2);
  415.     (void) signal(SIGALRM,time_out);
  416.  
  417.     if ( setjmp(timed_out) ) {
  418.         fprintf(stderr,"\nRead time out on %s\n",tty_name);
  419.         count = 1;
  420.         goto read_exit;
  421.     }
  422.     count = 0;
  423.  
  424.     do {
  425.         if ( read(wwv_fd,&c,1) ) {
  426.             count++;
  427.             *s++ = c & 0x7f;
  428.             if ( '\r' == c )
  429.                 s--;
  430.  
  431.             *s = '\0';
  432.         }
  433.     } while ( count < 23 );
  434.  
  435.     s = &wwv_buf->hour[0];
  436.     count = 0;
  437.  
  438.     if ( strlen(s) < 23 ) {
  439.         fprintf(stderr,"HH:MM:SS.T     MM/DD/YY\n%s|\n",s);
  440.         count++;
  441.     }
  442.  
  443. read_exit:
  444.     /*
  445.      *   Close everything and stop up signals
  446.      */
  447.     (void) alarm(0);
  448.     (void) signal(SIGALRM,SIG_IGN);
  449.     (void) close(wwv_fd);
  450.     return(count);
  451. }
  452.