summaryrefslogtreecommitdiff
path: root/hwclock/clock-ppc.c
blob: b73d0ad8b859865381a7830621b0ea856446c0f3 (plain)
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
/*
From t-matsuu@protein.osaka-u.ac.jp Sat Jan 22 13:43:20 2000
Date: Sat, 22 Jan 2000 21:42:54 +0900 (JST)
To: Andries.Brouwer@cwi.nl
Subject: Please merge the source for PPC
From: MATSUURA Takanori <t-matsuu@protein.osaka-u.ac.jp>

Even now, it is used clock-1.1 based source on Linux for PowerPC
architecture, attached on this mail.

Please merge this source in main util-linux source.

But I'm not an author of this source, but Paul Mackerras.
http://linuxcare.com.au/paulus/
shows details of him.

MATSUURA Takanori @ Division of Protein Chemistry, 
                     Institute for Protein Research, Osaka University, Japan
E-Mail: t-matsuu@protein.osaka-u.ac.jp
Web Page: http://www.protein.osaka-u.ac.jp/chemistry/matsuura/
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

#include <time.h>
#include <fcntl.h>
#include <getopt.h>
#include <sys/time.h>

#include <asm/cuda.h>

/*
 * Adapted for Power Macintosh by Paul Mackerras.
 */

/* V1.0
 * CMOS clock manipulation - Charles Hedrick, hedrick@cs.rutgers.edu, Apr 1992
 * 
 * clock [-u] -r  - read cmos clock
 * clock [-u] -w  - write cmos clock from system time
 * clock [-u] -s  - set system time from cmos clock
 * clock [-u] -a  - set system time from cmos clock, adjust the time to
 *                  correct for systematic error, and put it back to the cmos.
 *  -u indicates cmos clock is kept in universal time
 *
 * The program is designed to run setuid, since we need to be able to
 * write to the CUDA.
 *
 *********************
 * V1.1
 * Modified for clock adjustments - Rob Hooft, hooft@chem.ruu.nl, Nov 1992
 * Also moved error messages to stderr. The program now uses getopt.
 * Changed some exit codes. Made 'gcc 2.3 -Wall' happy.
 *
 * I think a small explanation of the adjustment routine should be given
 * here. The problem with my machine is that its CMOS clock is 10 seconds 
 * per day slow. With this version of clock.c, and my '/etc/rc.local' 
 * reading '/etc/clock -au' instead of '/etc/clock -u -s', this error 
 * is automatically corrected at every boot. 
 *
 * To do this job, the program reads and writes the file '/etc/adjtime' 
 * to determine the correction, and to save its data. In this file are 
 * three numbers: 
 *
 * 1) the correction in seconds per day (So if your clock runs 5 
 *    seconds per day fast, the first number should read -5.0)
 * 2) the number of seconds since 1/1/1970 the last time the program was
 *    used.
 * 3) the remaining part of a second which was leftover after the last 
 *    adjustment
 *
 * Installation and use of this program:
 *
 * a) create a file '/etc/adjtime' containing as the first and only line:
 *    '0.0 0 0.0'
 * b) run 'clock -au' or 'clock -a', depending on whether your cmos is in
 *    universal or local time. This updates the second number.
 * c) set your system time using the 'date' command.
 * d) update your cmos time using 'clock -wu' or 'clock -w'
 * e) replace the first number in /etc/adjtime by your correction.
 * f) put the command 'clock -au' or 'clock -a' in your '/etc/rc.local'
 *
 * If the adjustment doesn't work for you, try contacting me by E-mail.
 *
 ******
 * V1.2
 *
 * Applied patches by Harald Koenig (koenig@nova.tat.physik.uni-tuebingen.de)
 * Patched and indented by Rob Hooft (hooft@EMBL-Heidelberg.DE)
 * 
 * A free quote from a MAIL-message (with spelling corrections):
 *
 * "I found the explanation and solution for the CMOS reading 0xff problem
 *  in the 0.99pl13c (ALPHA) kernel: the RTC goes offline for a small amount
 *  of time for updating. Solution is included in the kernel source 
 *  (linux/kernel/time.c)."
 *
 * "I modified clock.c to fix this problem and added an option (now default,
 *  look for USE_INLINE_ASM_IO) that I/O instructions are used as inline
 *  code and not via /dev/port (still possible via #undef ...)."
 *
 * With the new code, which is partially taken from the kernel sources, 
 * the CMOS clock handling looks much more "official".
 * Thanks Harald (and Torsten for the kernel code)!
 *
 ******
 * V1.3
 * Canges from alan@spri.levels.unisa.edu.au (Alan Modra):
 * a) Fix a few typos in comments and remove reference to making
 *    clock -u a cron job.  The kernel adjusts cmos time every 11
 *    minutes - see kernel/sched.c and kernel/time.c set_rtc_mmss().
 *    This means we should really have a cron job updating
 *    /etc/adjtime every 11 mins (set last_time to the current time
 *    and not_adjusted to ???).
 * b) Swapped arguments of outb() to agree with asm/io.h macro of the
 *    same name.  Use outb() from asm/io.h as it's slightly better.
 * c) Changed CMOS_READ and CMOS_WRITE to inline functions.  Inserted
 *    cli()..sti() pairs in appropriate places to prevent possible
 *    errors, and changed ioperm() call to iopl() to allow cli.
 * d) Moved some variables around to localise them a bit.
 * e) Fixed bug with clock -ua or clock -us that cleared environment
 *    variable TZ.  This fix also cured the annoying display of bogus
 *    day of week on a number of machines. (Use mktime(), ctime()
 *    rather than asctime() )
 * f) Use settimeofday() rather than stime().  This one is important
 *    as it sets the kernel's timezone offset, which is returned by
 *    gettimeofday(), and used for display of MSDOS and OS2 file
 *    times.
 * g) faith@cs.unc.edu added -D flag for debugging
 *
 * V1.4: alan@SPRI.Levels.UniSA.Edu.Au (Alan Modra)
 *       Wed Feb  8 12:29:08 1995, fix for years > 2000.
 *       faith@cs.unc.edu added -v option to print version.
 *
 * August 1996 Tom Dyas (tdyas@eden.rutgers.edu)
 *       Converted to be compatible with the SPARC /dev/rtc driver.
 *
 */

#define VERSION "1.4"

/* Here the information for time adjustments is kept. */
#define ADJPATH "/etc/adjtime"

/* Apparently the RTC on PowerMacs stores seconds since 1 Jan 1904 */
#define RTC_OFFSET	2082844800

/* used for debugging the code. */
/*#define KEEP_OFF */

/* Globals */
int readit = 0;
int adjustit = 0;
int writeit = 0;
int setit = 0;
int universal = 0;
int debug = 0;

time_t mkgmtime(struct tm *);

volatile void 
usage ( void )
{
  (void) fprintf (stderr, 
    "clock [-u] -r|w|s|a|v\n"
    "  r: read and print CMOS clock\n"
    "  w: write CMOS clock from system time\n"
    "  s: set system time from CMOS clock\n"
    "  a: get system time and adjust CMOS clock\n"
    "  u: CMOS clock is in universal time\n"
    "  v: print version (" VERSION ") and exit\n"
  );
  exit(EXIT_FAILURE);
}

int adb_fd;

void
adb_init ( void )
{
  adb_fd = open ("/dev/adb", 2);
  if (adb_fd < 0)
    {
      perror ("unable to open /dev/adb read/write : ");
      exit(EXIT_FAILURE);
    }
}

unsigned char get_packet[2] = { (unsigned char) CUDA_PACKET, 
				(unsigned char) CUDA_GET_TIME };
unsigned char set_packet[6] = { (unsigned char) CUDA_PACKET, 
				(unsigned char) CUDA_SET_TIME };

int 
main (int argc, char **argv )
{
  struct tm tm, *tmp;
  time_t systime;
  time_t last_time;
  time_t clock_time;
  int i, arg;
  double factor;
  double not_adjusted;
  int adjustment = 0;
  /*   unsigned char save_control, save_freq_select; */
  unsigned char reply[16];

  while ((arg = getopt (argc, argv, "rwsuaDv")) != -1)
    {
      switch (arg)
	{
	case 'r':
	  readit = 1;
	  break;
	case 'w':
	  writeit = 1;
	  break;
	case 's':
	  setit = 1;
	  break;
	case 'u':
	  universal = 1;
	  break;
	case 'a':
	  adjustit = 1;
	  break;
        case 'D':
	  debug = 1;
	  break;
	case 'v':
	  (void) fprintf( stderr, "clock " VERSION "\n" );
	  exit(EXIT_SUCCESS);
	default:
	  usage ();
	}
    }

    /* If we are in MkLinux do not even bother trying to set the clock */
    if(!access("/proc/osfmach3/version", R_OK))
    {           /* We're running MkLinux */
     if ( readit | writeit | setit | adjustit )
	printf("You must change the clock setting in MacOS.\n");
     exit(0);
    }

  if (readit + writeit + setit + adjustit > 1)
    usage ();			/* only allow one of these */

  if (!(readit | writeit | setit | adjustit))	/* default to read */
    readit = 1;

  adb_init ();

  if (adjustit)
    {				/* Read adjustment parameters first */
      FILE *adj;
      if ((adj = fopen (ADJPATH, "r")) == NULL)
	{
	  perror (ADJPATH);
	  exit(EXIT_FAILURE);
	}
      if (fscanf (adj, "%lf %d %lf", &factor, (int *) (&last_time), 
		  &not_adjusted) < 0)
	{
	  perror (ADJPATH);
	  exit(EXIT_FAILURE);
	}
      (void) fclose (adj);
      if (debug) (void) printf(
			"Last adjustment done at %d seconds after 1/1/1970\n",
			(int) last_time);
    }

  if (readit || setit || adjustit)
    {
      int ii;

      if (write(adb_fd, get_packet, sizeof(get_packet)) < 0) {
	perror("write adb");
	exit(EXIT_FAILURE);
      }
      ii = (int) read(adb_fd, reply, sizeof(reply));
      if (ii < 0) {
	perror("read adb");
	exit(EXIT_FAILURE);
      }
      if (ii != 7)
	(void) fprintf(stderr, 
		       "Warning: bad reply length from CUDA (%d)\n", ii);
      clock_time = (time_t) ((reply[3] << 24) + (reply[4] << 16)
	                     + (reply[5] << 8)) + (time_t) reply[6];
      clock_time -= RTC_OFFSET;

      if (universal) {
	systime = clock_time;
      } else {
	tm = *gmtime(&clock_time);
	(void) printf("time in rtc is %s", asctime(&tm));
	tm.tm_isdst = -1;		/* don't know whether it's DST */
	systime = mktime(&tm);
      }
    }

  if (readit)
    {
      (void) printf ("%s", ctime (&systime ));
    }

  if (setit || adjustit)
    {
      struct timeval tv;
      struct timezone tz;

/* program is designed to run setuid, be secure! */

      if (getuid () != 0)
	{			
	  (void) fprintf (stderr, 
			  "Sorry, must be root to set or adjust time\n");
	  exit(EXIT_FAILURE);
	}

      if (adjustit)
	{			/* the actual adjustment */
	  double exact_adjustment;

	  exact_adjustment = ((double) (systime - last_time))
	    * factor / (24 * 60 * 60)
	    + not_adjusted;
	  if (exact_adjustment > 0.)
	    adjustment = (int) (exact_adjustment + 0.5);
	  else
	    adjustment = (int) (exact_adjustment - 0.5);
	  not_adjusted = exact_adjustment - (double) adjustment;
	  systime += adjustment;
	  if (debug) {
	     (void) printf ("Time since last adjustment is %d seconds\n",
		     (int) (systime - last_time));
	     (void) printf ("Adjusting time by %d seconds\n",
		     adjustment);
	     (void) printf ("remaining adjustment is %.3f seconds\n",
		     not_adjusted);
	  }
	}
#ifndef KEEP_OFF
      tv.tv_sec = systime;
      tv.tv_usec = 0;
      tz.tz_minuteswest = timezone / 60;
      tz.tz_dsttime = daylight;

      if (settimeofday (&tv, &tz) != 0)
        {
	  (void) fprintf (stderr,
		   "Unable to set time -- probably you are not root\n");
	  exit(EXIT_FAILURE);
	}
      
      if (debug) {
	 (void) printf( "Called settimeofday:\n" );
	 (void) printf( "\ttv.tv_sec = %ld, tv.tv_usec = %ld\n",
		 tv.tv_sec, tv.tv_usec );
	 (void) printf( "\ttz.tz_minuteswest = %d, tz.tz_dsttime = %d\n",
		 tz.tz_minuteswest, tz.tz_dsttime );
      }
#endif
    }
  
  if (writeit || (adjustit && adjustment != 0))
    {
      systime = time (NULL);

      if (universal) {
	clock_time = systime;

      } else {
	tmp = localtime(&systime);
	clock_time = mkgmtime(tmp);
      }

      clock_time += RTC_OFFSET;
      set_packet[2] = clock_time >> 24;
      set_packet[3] = clock_time >> 16;
      set_packet[4] = clock_time >> 8;
      set_packet[5] = (unsigned char) clock_time;

      if (write(adb_fd, set_packet, sizeof(set_packet)) < 0) {
	perror("write adb (set)");
	exit(EXIT_FAILURE);
      }
      i = (int) read(adb_fd, reply, sizeof(reply));
      if (debug) {
	int j;
	(void) printf("set reply %d bytes:", i);
	for (j = 0; j < i; ++j) 
	    (void) printf(" %.2x", (unsigned int) reply[j]);
	(void) printf("\n");
      }
      if (i != 3 || reply[1] != (unsigned char) 0)
	(void) fprintf(stderr, "Warning: error %d setting RTC\n", 
		       (int) reply[1]);

      if (debug) {
	clock_time -= RTC_OFFSET;
	(void) printf("set RTC to %s", asctime(gmtime(&clock_time)));
      }
    }
  else
    if (debug) (void) printf ("CMOS clock unchanged.\n");
  /* Save data for next 'adjustit' call */
  if (adjustit)
    {
      FILE *adj;
      if ((adj = fopen (ADJPATH, "w")) == NULL)
	{
	  perror (ADJPATH);
	  exit(EXIT_FAILURE);
	}
      (void) fprintf (adj, "%f %d %f\n", factor, (int) systime, not_adjusted);
      (void) fclose (adj);
    }
  exit(EXIT_SUCCESS);
}

/* Stolen from linux/arch/i386/kernel/time.c. */
/* Converts Gregorian date to seconds since 1970-01-01 00:00:00.
 * Assumes input in normal date format, i.e. 1980-12-31 23:59:59
 * => year=1980, mon=12, day=31, hour=23, min=59, sec=59.
 *
 * [For the Julian calendar (which was used in Russia before 1917,
 * Britain & colonies before 1752, anywhere else before 1582,
 * and is still in use by some communities) leave out the
 * -year/100+year/400 terms, and add 10.]
 *
 * This algorithm was first published by Gauss (I think).
 *
 * WARNING: this function will overflow on 2106-02-07 06:28:16 on
 * machines were long is 32-bit! (However, as time_t is signed, we
 * will already get problems at other places on 2038-01-19 03:14:08)
 */
time_t mkgmtime(struct tm *tm)
{
  int mon = tm->tm_mon + 1;
  int year = tm->tm_year + 1900;

  if (0 >= (int) (mon -= 2)) {	/* 1..12 -> 11,12,1..10 */
    mon += 12;	/* Puts Feb last since it has leap day */
    year -= 1;
  }
  return (((
	    (unsigned long)(year/4 - year/100 + year/400 + 367*mon/12) +
	      tm->tm_mday + year*365 - 719499
	    )*24 + tm->tm_hour /* now have hours */
	   )*60 + tm->tm_min /* now have minutes */
	  )*60 + tm->tm_sec; /* finally seconds */
}