Upload Tizen:Base source
[framework/base/util-linux-ng.git] / hwclock / clock-ppc.c
1 /*
2 From t-matsuu@protein.osaka-u.ac.jp Sat Jan 22 13:43:20 2000
3 Date: Sat, 22 Jan 2000 21:42:54 +0900 (JST)
4 To: Andries.Brouwer@cwi.nl
5 Subject: Please merge the source for PPC
6 From: MATSUURA Takanori <t-matsuu@protein.osaka-u.ac.jp>
7
8 Even now, it is used clock-1.1 based source on Linux for PowerPC
9 architecture, attached on this mail.
10
11 Please merge this source in main util-linux source.
12
13 But I'm not an author of this source, but Paul Mackerras.
14 http://linuxcare.com.au/paulus/
15 shows details of him.
16
17 MATSUURA Takanori @ Division of Protein Chemistry, 
18                      Institute for Protein Research, Osaka University, Japan
19 E-Mail: t-matsuu@protein.osaka-u.ac.jp
20 Web Page: http://www.protein.osaka-u.ac.jp/chemistry/matsuura/
21 */
22
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <errno.h>
27 #include <unistd.h>
28
29 #include <time.h>
30 #include <fcntl.h>
31 #include <getopt.h>
32 #include <sys/time.h>
33
34 #include <asm/cuda.h>
35
36 /*
37  * Adapted for Power Macintosh by Paul Mackerras.
38  */
39
40 /* V1.0
41  * CMOS clock manipulation - Charles Hedrick, hedrick@cs.rutgers.edu, Apr 1992
42  * 
43  * clock [-u] -r  - read cmos clock
44  * clock [-u] -w  - write cmos clock from system time
45  * clock [-u] -s  - set system time from cmos clock
46  * clock [-u] -a  - set system time from cmos clock, adjust the time to
47  *                  correct for systematic error, and put it back to the cmos.
48  *  -u indicates cmos clock is kept in universal time
49  *
50  * The program is designed to run setuid, since we need to be able to
51  * write to the CUDA.
52  *
53  *********************
54  * V1.1
55  * Modified for clock adjustments - Rob Hooft, hooft@chem.ruu.nl, Nov 1992
56  * Also moved error messages to stderr. The program now uses getopt.
57  * Changed some exit codes. Made 'gcc 2.3 -Wall' happy.
58  *
59  * I think a small explanation of the adjustment routine should be given
60  * here. The problem with my machine is that its CMOS clock is 10 seconds 
61  * per day slow. With this version of clock.c, and my '/etc/rc.local' 
62  * reading '/etc/clock -au' instead of '/etc/clock -u -s', this error 
63  * is automatically corrected at every boot. 
64  *
65  * To do this job, the program reads and writes the file '/etc/adjtime' 
66  * to determine the correction, and to save its data. In this file are 
67  * three numbers: 
68  *
69  * 1) the correction in seconds per day (So if your clock runs 5 
70  *    seconds per day fast, the first number should read -5.0)
71  * 2) the number of seconds since 1/1/1970 the last time the program was
72  *    used.
73  * 3) the remaining part of a second which was leftover after the last 
74  *    adjustment
75  *
76  * Installation and use of this program:
77  *
78  * a) create a file '/etc/adjtime' containing as the first and only line:
79  *    '0.0 0 0.0'
80  * b) run 'clock -au' or 'clock -a', depending on whether your cmos is in
81  *    universal or local time. This updates the second number.
82  * c) set your system time using the 'date' command.
83  * d) update your cmos time using 'clock -wu' or 'clock -w'
84  * e) replace the first number in /etc/adjtime by your correction.
85  * f) put the command 'clock -au' or 'clock -a' in your '/etc/rc.local'
86  *
87  * If the adjustment doesn't work for you, try contacting me by E-mail.
88  *
89  ******
90  * V1.2
91  *
92  * Applied patches by Harald Koenig (koenig@nova.tat.physik.uni-tuebingen.de)
93  * Patched and indented by Rob Hooft (hooft@EMBL-Heidelberg.DE)
94  * 
95  * A free quote from a MAIL-message (with spelling corrections):
96  *
97  * "I found the explanation and solution for the CMOS reading 0xff problem
98  *  in the 0.99pl13c (ALPHA) kernel: the RTC goes offline for a small amount
99  *  of time for updating. Solution is included in the kernel source 
100  *  (linux/kernel/time.c)."
101  *
102  * "I modified clock.c to fix this problem and added an option (now default,
103  *  look for USE_INLINE_ASM_IO) that I/O instructions are used as inline
104  *  code and not via /dev/port (still possible via #undef ...)."
105  *
106  * With the new code, which is partially taken from the kernel sources, 
107  * the CMOS clock handling looks much more "official".
108  * Thanks Harald (and Torsten for the kernel code)!
109  *
110  ******
111  * V1.3
112  * Canges from alan@spri.levels.unisa.edu.au (Alan Modra):
113  * a) Fix a few typos in comments and remove reference to making
114  *    clock -u a cron job.  The kernel adjusts cmos time every 11
115  *    minutes - see kernel/sched.c and kernel/time.c set_rtc_mmss().
116  *    This means we should really have a cron job updating
117  *    /etc/adjtime every 11 mins (set last_time to the current time
118  *    and not_adjusted to ???).
119  * b) Swapped arguments of outb() to agree with asm/io.h macro of the
120  *    same name.  Use outb() from asm/io.h as it's slightly better.
121  * c) Changed CMOS_READ and CMOS_WRITE to inline functions.  Inserted
122  *    cli()..sti() pairs in appropriate places to prevent possible
123  *    errors, and changed ioperm() call to iopl() to allow cli.
124  * d) Moved some variables around to localise them a bit.
125  * e) Fixed bug with clock -ua or clock -us that cleared environment
126  *    variable TZ.  This fix also cured the annoying display of bogus
127  *    day of week on a number of machines. (Use mktime(), ctime()
128  *    rather than asctime() )
129  * f) Use settimeofday() rather than stime().  This one is important
130  *    as it sets the kernel's timezone offset, which is returned by
131  *    gettimeofday(), and used for display of MSDOS and OS2 file
132  *    times.
133  * g) faith@cs.unc.edu added -D flag for debugging
134  *
135  * V1.4: alan@SPRI.Levels.UniSA.Edu.Au (Alan Modra)
136  *       Wed Feb  8 12:29:08 1995, fix for years > 2000.
137  *       faith@cs.unc.edu added -v option to print version.
138  *
139  * August 1996 Tom Dyas (tdyas@eden.rutgers.edu)
140  *       Converted to be compatible with the SPARC /dev/rtc driver.
141  *
142  */
143
144 #define VERSION "1.4"
145
146 /* Here the information for time adjustments is kept. */
147 #define ADJPATH "/etc/adjtime"
148
149 /* Apparently the RTC on PowerMacs stores seconds since 1 Jan 1904 */
150 #define RTC_OFFSET      2082844800
151
152 /* used for debugging the code. */
153 /*#define KEEP_OFF */
154
155 /* Globals */
156 int readit = 0;
157 int adjustit = 0;
158 int writeit = 0;
159 int setit = 0;
160 int universal = 0;
161 int debug = 0;
162
163 time_t mkgmtime(struct tm *);
164
165 volatile void 
166 usage ( void )
167 {
168   (void) fprintf (stderr, 
169     "clock [-u] -r|w|s|a|v\n"
170     "  r: read and print CMOS clock\n"
171     "  w: write CMOS clock from system time\n"
172     "  s: set system time from CMOS clock\n"
173     "  a: get system time and adjust CMOS clock\n"
174     "  u: CMOS clock is in universal time\n"
175     "  v: print version (" VERSION ") and exit\n"
176   );
177   exit(EXIT_FAILURE);
178 }
179
180 int adb_fd;
181
182 void
183 adb_init ( void )
184 {
185   adb_fd = open ("/dev/adb", 2);
186   if (adb_fd < 0)
187     {
188       perror ("unable to open /dev/adb read/write : ");
189       exit(EXIT_FAILURE);
190     }
191 }
192
193 unsigned char get_packet[2] = { (unsigned char) CUDA_PACKET, 
194                                 (unsigned char) CUDA_GET_TIME };
195 unsigned char set_packet[6] = { (unsigned char) CUDA_PACKET, 
196                                 (unsigned char) CUDA_SET_TIME };
197
198 int 
199 main (int argc, char **argv )
200 {
201   struct tm tm, *tmp;
202   time_t systime;
203   time_t last_time;
204   time_t clock_time;
205   int i, arg;
206   double factor;
207   double not_adjusted;
208   int adjustment = 0;
209   /*   unsigned char save_control, save_freq_select; */
210   unsigned char reply[16];
211
212   while ((arg = getopt (argc, argv, "rwsuaDv")) != -1)
213     {
214       switch (arg)
215         {
216         case 'r':
217           readit = 1;
218           break;
219         case 'w':
220           writeit = 1;
221           break;
222         case 's':
223           setit = 1;
224           break;
225         case 'u':
226           universal = 1;
227           break;
228         case 'a':
229           adjustit = 1;
230           break;
231         case 'D':
232           debug = 1;
233           break;
234         case 'v':
235           (void) fprintf( stderr, "clock " VERSION "\n" );
236           exit(EXIT_SUCCESS);
237         default:
238           usage ();
239         }
240     }
241
242     /* If we are in MkLinux do not even bother trying to set the clock */
243     if(!access("/proc/osfmach3/version", R_OK))
244     {           /* We're running MkLinux */
245      if ( readit | writeit | setit | adjustit )
246         printf("You must change the clock setting in MacOS.\n");
247      exit(0);
248     }
249
250   if (readit + writeit + setit + adjustit > 1)
251     usage ();                   /* only allow one of these */
252
253   if (!(readit | writeit | setit | adjustit))   /* default to read */
254     readit = 1;
255
256   adb_init ();
257
258   if (adjustit)
259     {                           /* Read adjustment parameters first */
260       FILE *adj;
261       if ((adj = fopen (ADJPATH, "r")) == NULL)
262         {
263           perror (ADJPATH);
264           exit(EXIT_FAILURE);
265         }
266       if (fscanf (adj, "%lf %d %lf", &factor, (int *) (&last_time), 
267                   &not_adjusted) < 0)
268         {
269           perror (ADJPATH);
270           exit(EXIT_FAILURE);
271         }
272       (void) fclose (adj);
273       if (debug) (void) printf(
274                         "Last adjustment done at %d seconds after 1/1/1970\n",
275                         (int) last_time);
276     }
277
278   if (readit || setit || adjustit)
279     {
280       int ii;
281
282       if (write(adb_fd, get_packet, sizeof(get_packet)) < 0) {
283         perror("write adb");
284         exit(EXIT_FAILURE);
285       }
286       ii = (int) read(adb_fd, reply, sizeof(reply));
287       if (ii < 0) {
288         perror("read adb");
289         exit(EXIT_FAILURE);
290       }
291       if (ii != 7)
292         (void) fprintf(stderr, 
293                        "Warning: bad reply length from CUDA (%d)\n", ii);
294       clock_time = (time_t) ((reply[3] << 24) + (reply[4] << 16)
295                              + (reply[5] << 8)) + (time_t) reply[6];
296       clock_time -= RTC_OFFSET;
297
298       if (universal) {
299         systime = clock_time;
300       } else {
301         tm = *gmtime(&clock_time);
302         (void) printf("time in rtc is %s", asctime(&tm));
303         tm.tm_isdst = -1;               /* don't know whether it's DST */
304         systime = mktime(&tm);
305       }
306     }
307
308   if (readit)
309     {
310       (void) printf ("%s", ctime (&systime ));
311     }
312
313   if (setit || adjustit)
314     {
315       struct timeval tv;
316       struct timezone tz;
317
318 /* program is designed to run setuid, be secure! */
319
320       if (getuid () != 0)
321         {                       
322           (void) fprintf (stderr, 
323                           "Sorry, must be root to set or adjust time\n");
324           exit(EXIT_FAILURE);
325         }
326
327       if (adjustit)
328         {                       /* the actual adjustment */
329           double exact_adjustment;
330
331           exact_adjustment = ((double) (systime - last_time))
332             * factor / (24 * 60 * 60)
333             + not_adjusted;
334           if (exact_adjustment > 0.)
335             adjustment = (int) (exact_adjustment + 0.5);
336           else
337             adjustment = (int) (exact_adjustment - 0.5);
338           not_adjusted = exact_adjustment - (double) adjustment;
339           systime += adjustment;
340           if (debug) {
341              (void) printf ("Time since last adjustment is %d seconds\n",
342                      (int) (systime - last_time));
343              (void) printf ("Adjusting time by %d seconds\n",
344                      adjustment);
345              (void) printf ("remaining adjustment is %.3f seconds\n",
346                      not_adjusted);
347           }
348         }
349 #ifndef KEEP_OFF
350       tv.tv_sec = systime;
351       tv.tv_usec = 0;
352       tz.tz_minuteswest = timezone / 60;
353       tz.tz_dsttime = daylight;
354
355       if (settimeofday (&tv, &tz) != 0)
356         {
357           (void) fprintf (stderr,
358                    "Unable to set time -- probably you are not root\n");
359           exit(EXIT_FAILURE);
360         }
361       
362       if (debug) {
363          (void) printf( "Called settimeofday:\n" );
364          (void) printf( "\ttv.tv_sec = %ld, tv.tv_usec = %ld\n",
365                  tv.tv_sec, tv.tv_usec );
366          (void) printf( "\ttz.tz_minuteswest = %d, tz.tz_dsttime = %d\n",
367                  tz.tz_minuteswest, tz.tz_dsttime );
368       }
369 #endif
370     }
371   
372   if (writeit || (adjustit && adjustment != 0))
373     {
374       systime = time (NULL);
375
376       if (universal) {
377         clock_time = systime;
378
379       } else {
380         tmp = localtime(&systime);
381         clock_time = mkgmtime(tmp);
382       }
383
384       clock_time += RTC_OFFSET;
385       set_packet[2] = clock_time >> 24;
386       set_packet[3] = clock_time >> 16;
387       set_packet[4] = clock_time >> 8;
388       set_packet[5] = (unsigned char) clock_time;
389
390       if (write(adb_fd, set_packet, sizeof(set_packet)) < 0) {
391         perror("write adb (set)");
392         exit(EXIT_FAILURE);
393       }
394       i = (int) read(adb_fd, reply, sizeof(reply));
395       if (debug) {
396         int j;
397         (void) printf("set reply %d bytes:", i);
398         for (j = 0; j < i; ++j) 
399             (void) printf(" %.2x", (unsigned int) reply[j]);
400         (void) printf("\n");
401       }
402       if (i != 3 || reply[1] != (unsigned char) 0)
403         (void) fprintf(stderr, "Warning: error %d setting RTC\n", 
404                        (int) reply[1]);
405
406       if (debug) {
407         clock_time -= RTC_OFFSET;
408         (void) printf("set RTC to %s", asctime(gmtime(&clock_time)));
409       }
410     }
411   else
412     if (debug) (void) printf ("CMOS clock unchanged.\n");
413   /* Save data for next 'adjustit' call */
414   if (adjustit)
415     {
416       FILE *adj;
417       if ((adj = fopen (ADJPATH, "w")) == NULL)
418         {
419           perror (ADJPATH);
420           exit(EXIT_FAILURE);
421         }
422       (void) fprintf (adj, "%f %d %f\n", factor, (int) systime, not_adjusted);
423       (void) fclose (adj);
424     }
425   exit(EXIT_SUCCESS);
426 }
427
428 /* Stolen from linux/arch/i386/kernel/time.c. */
429 /* Converts Gregorian date to seconds since 1970-01-01 00:00:00.
430  * Assumes input in normal date format, i.e. 1980-12-31 23:59:59
431  * => year=1980, mon=12, day=31, hour=23, min=59, sec=59.
432  *
433  * [For the Julian calendar (which was used in Russia before 1917,
434  * Britain & colonies before 1752, anywhere else before 1582,
435  * and is still in use by some communities) leave out the
436  * -year/100+year/400 terms, and add 10.]
437  *
438  * This algorithm was first published by Gauss (I think).
439  *
440  * WARNING: this function will overflow on 2106-02-07 06:28:16 on
441  * machines were long is 32-bit! (However, as time_t is signed, we
442  * will already get problems at other places on 2038-01-19 03:14:08)
443  */
444 time_t mkgmtime(struct tm *tm)
445 {
446   int mon = tm->tm_mon + 1;
447   int year = tm->tm_year + 1900;
448
449   if (0 >= (int) (mon -= 2)) {  /* 1..12 -> 11,12,1..10 */
450     mon += 12;  /* Puts Feb last since it has leap day */
451     year -= 1;
452   }
453   return (((
454             (unsigned long)(year/4 - year/100 + year/400 + 367*mon/12) +
455               tm->tm_mday + year*365 - 719499
456             )*24 + tm->tm_hour /* now have hours */
457            )*60 + tm->tm_min /* now have minutes */
458           )*60 + tm->tm_sec; /* finally seconds */
459 }