1 | /* readclock - read the real time clock Authors: T. Holm & E. Froese */
|
---|
2 |
|
---|
3 | /************************************************************************/
|
---|
4 | /* */
|
---|
5 | /* readclock.c */
|
---|
6 | /* */
|
---|
7 | /* Read the clock value from the 64 byte CMOS RAM */
|
---|
8 | /* area, then set system time. */
|
---|
9 | /* */
|
---|
10 | /* If the machine ID byte is 0xFC or 0xF8, the device */
|
---|
11 | /* /dev/mem exists and can be opened for reading, */
|
---|
12 | /* and no errors in the CMOS RAM are reported by the */
|
---|
13 | /* RTC, then the time is read from the clock RAM */
|
---|
14 | /* area maintained by the RTC. */
|
---|
15 | /* */
|
---|
16 | /* The clock RAM values are decoded and fed to mktime */
|
---|
17 | /* to make a time_t value, then stime(2) is called. */
|
---|
18 | /* */
|
---|
19 | /* This fails if: */
|
---|
20 | /* */
|
---|
21 | /* If the machine ID does not match 0xFC or 0xF8 (no */
|
---|
22 | /* error message.) */
|
---|
23 | /* */
|
---|
24 | /* If the machine ID is 0xFC or 0xF8 and /dev/mem */
|
---|
25 | /* is missing, or cannot be accessed. */
|
---|
26 | /* */
|
---|
27 | /* If the RTC reports errors in the CMOS RAM. */
|
---|
28 | /* */
|
---|
29 | /************************************************************************/
|
---|
30 | /* origination 1987-Dec-29 efth */
|
---|
31 | /* robustness 1990-Oct-06 C. Sylvain */
|
---|
32 | /* incorp. B. Evans ideas 1991-Jul-06 C. Sylvain */
|
---|
33 | /* set time & calibrate 1992-Dec-17 Kees J. Bot */
|
---|
34 | /* clock timezone 1993-Oct-10 Kees J. Bot */
|
---|
35 | /* set CMOS clock 1994-Jun-12 Kees J. Bot */
|
---|
36 | /************************************************************************/
|
---|
37 |
|
---|
38 |
|
---|
39 | #include <sys/types.h>
|
---|
40 | #include <sys/stat.h>
|
---|
41 | #include <stdlib.h>
|
---|
42 | #include <unistd.h>
|
---|
43 | #include <fcntl.h>
|
---|
44 | #include <stdio.h>
|
---|
45 | #include <string.h>
|
---|
46 | #include <time.h>
|
---|
47 | #include <errno.h>
|
---|
48 | #include <signal.h>
|
---|
49 | #include <ibm/portio.h>
|
---|
50 | #include <ibm/cmos.h>
|
---|
51 | #include <sys/svrctl.h>
|
---|
52 |
|
---|
53 | int nflag = 0; /* Tell what, but don't do it. */
|
---|
54 | int wflag = 0; /* Set the CMOS clock. */
|
---|
55 | int Wflag = 0; /* Also set the CMOS clock register bits. */
|
---|
56 | int y2kflag = 0; /* Interpret 1980 as 2000 for clock with Y2K bug. */
|
---|
57 |
|
---|
58 | char clocktz[128]; /* Timezone of the clock. */
|
---|
59 |
|
---|
60 | #define MACH_ID_ADDR 0xFFFFE /* BIOS Machine ID at FFFF:000E */
|
---|
61 |
|
---|
62 | #define PC_AT 0xFC /* Machine ID byte for PC/AT,
|
---|
63 | PC/XT286, and PS/2 Models 50, 60 */
|
---|
64 | #define PS_386 0xF8 /* Machine ID byte for PS/2 Model 80 */
|
---|
65 |
|
---|
66 | /* Manufacturers usually use the ID value of the IBM model they emulate.
|
---|
67 | * However some manufacturers, notably HP and COMPAQ, have had different
|
---|
68 | * ideas in the past.
|
---|
69 | *
|
---|
70 | * Machine ID byte information source:
|
---|
71 | * _The Programmer's PC Sourcebook_ by Thom Hogan,
|
---|
72 | * published by Microsoft Press
|
---|
73 | */
|
---|
74 |
|
---|
75 | void errmsg(char *s);
|
---|
76 | void get_time(struct tm *t);
|
---|
77 | int read_register(int reg_addr);
|
---|
78 | void set_time(struct tm *t);
|
---|
79 | void write_register(int reg_addr, int value);
|
---|
80 | int bcd_to_dec(int n);
|
---|
81 | int dec_to_bcd(int n);
|
---|
82 | void usage(void);
|
---|
83 |
|
---|
84 | int main(int argc, char **argv)
|
---|
85 | {
|
---|
86 | struct tm time1;
|
---|
87 | struct tm time2;
|
---|
88 | struct tm tmnow;
|
---|
89 | char date[64];
|
---|
90 | time_t now, rtc;
|
---|
91 | int i, mem;
|
---|
92 | unsigned char mach_id, cmos_state;
|
---|
93 | struct sysgetenv sysgetenv;
|
---|
94 |
|
---|
95 | /* Open /dev/mem to get access to physical memory. */
|
---|
96 | if ((mem = open("/dev/mem", O_RDONLY)) == -1) {
|
---|
97 | errmsg( "Permission denied." );
|
---|
98 | exit(1);
|
---|
99 | }
|
---|
100 | if (lseek(mem, (off_t) MACH_ID_ADDR, SEEK_SET) == -1
|
---|
101 | || read(mem, (void *) &mach_id, sizeof(mach_id)) < 0) {
|
---|
102 | mach_id = -1;
|
---|
103 | }
|
---|
104 | if (mach_id != PS_386 && mach_id != PC_AT) {
|
---|
105 | errmsg( "Machine ID unknown." );
|
---|
106 | fprintf( stderr, "Machine ID byte = %02x\n", mach_id );
|
---|
107 |
|
---|
108 | exit(1);
|
---|
109 | }
|
---|
110 | cmos_state = read_register(CMOS_STATUS);
|
---|
111 | if (cmos_state & (CS_LOST_POWER | CS_BAD_CHKSUM | CS_BAD_TIME)) {
|
---|
112 | errmsg( "CMOS RAM error(s) found..." );
|
---|
113 | fprintf( stderr, "CMOS state = 0x%02x\n", cmos_state );
|
---|
114 |
|
---|
115 | if (cmos_state & CS_LOST_POWER)
|
---|
116 | errmsg( "RTC lost power. Reset CMOS RAM with SETUP." );
|
---|
117 | if (cmos_state & CS_BAD_CHKSUM)
|
---|
118 | errmsg( "CMOS RAM checksum is bad. Run SETUP." );
|
---|
119 | if (cmos_state & CS_BAD_TIME)
|
---|
120 | errmsg( "Time invalid in CMOS RAM. Reset clock." );
|
---|
121 | exit(1);
|
---|
122 | }
|
---|
123 |
|
---|
124 | /* Process options. */
|
---|
125 | while (argc > 1) {
|
---|
126 | char *p = *++argv;
|
---|
127 |
|
---|
128 | if (*p++ != '-') usage();
|
---|
129 |
|
---|
130 | while (*p != 0) {
|
---|
131 | switch (*p++) {
|
---|
132 | case 'n': nflag = 1; break;
|
---|
133 | case 'w': wflag = 1; break;
|
---|
134 | case 'W': Wflag = 1; break;
|
---|
135 | case '2': y2kflag = 1; break;
|
---|
136 | default: usage();
|
---|
137 | }
|
---|
138 | }
|
---|
139 | argc--;
|
---|
140 | }
|
---|
141 | if (Wflag) wflag = 1; /* -W implies -w */
|
---|
142 |
|
---|
143 | /* The hardware clock may run in a different time zone, likely GMT or
|
---|
144 | * winter time. Select that time zone.
|
---|
145 | */
|
---|
146 | strcpy(clocktz, "TZ=");
|
---|
147 | sysgetenv.key = "TZ";
|
---|
148 | sysgetenv.keylen = 2+1;
|
---|
149 | sysgetenv.val = clocktz+3;
|
---|
150 | sysgetenv.vallen = sizeof(clocktz)-3;
|
---|
151 | if (svrctl(SYSGETENV, &sysgetenv) == 0) {
|
---|
152 | putenv(clocktz);
|
---|
153 | tzset();
|
---|
154 | }
|
---|
155 |
|
---|
156 | /* Read the CMOS real time clock. */
|
---|
157 | for (i = 0; i < 10; i++) {
|
---|
158 | get_time(&time1);
|
---|
159 | now = time(NULL);
|
---|
160 |
|
---|
161 | time1.tm_isdst = -1; /* Do timezone calculations. */
|
---|
162 | time2 = time1;
|
---|
163 |
|
---|
164 | rtc= mktime(&time1); /* Transform to a time_t. */
|
---|
165 | if (rtc != -1) break;
|
---|
166 |
|
---|
167 | fprintf(stderr,
|
---|
168 | "readclock: Invalid time read from CMOS RTC: %d-%02d-%02d %02d:%02d:%02d\n",
|
---|
169 | time2.tm_year+1900, time2.tm_mon+1, time2.tm_mday,
|
---|
170 | time2.tm_hour, time2.tm_min, time2.tm_sec);
|
---|
171 | sleep(5);
|
---|
172 | }
|
---|
173 | if (i == 10) exit(1);
|
---|
174 |
|
---|
175 | if (!wflag) {
|
---|
176 | /* Set system time. */
|
---|
177 | if (nflag) {
|
---|
178 | printf("stime(%lu)\n", (unsigned long) rtc);
|
---|
179 | } else {
|
---|
180 | if (stime(&rtc) < 0) {
|
---|
181 | errmsg( "Not allowed to set time." );
|
---|
182 | exit(1);
|
---|
183 | }
|
---|
184 | }
|
---|
185 | tmnow = *localtime(&rtc);
|
---|
186 | if (strftime(date, sizeof(date),
|
---|
187 | "%a %b %d %H:%M:%S %Z %Y", &tmnow) != 0) {
|
---|
188 | if (date[8] == '0') date[8]= ' ';
|
---|
189 | printf("Result: %s\n", date);
|
---|
190 | }
|
---|
191 | } else {
|
---|
192 | /* Set the CMOS clock to the system time. */
|
---|
193 | tmnow = *localtime(&now);
|
---|
194 | if (nflag) {
|
---|
195 | printf("%04d-%02d-%02d %02d:%02d:%02d\n",
|
---|
196 | tmnow.tm_year + 1900,
|
---|
197 | tmnow.tm_mon + 1,
|
---|
198 | tmnow.tm_mday,
|
---|
199 | tmnow.tm_hour,
|
---|
200 | tmnow.tm_min,
|
---|
201 | tmnow.tm_sec);
|
---|
202 | } else {
|
---|
203 | set_time(&tmnow);
|
---|
204 | }
|
---|
205 | }
|
---|
206 | exit(0);
|
---|
207 | }
|
---|
208 |
|
---|
209 | void errmsg(char *s)
|
---|
210 | {
|
---|
211 | static char *prompt = "readclock: ";
|
---|
212 |
|
---|
213 | fprintf(stderr, "%s%s\n", prompt, s);
|
---|
214 | prompt = "";
|
---|
215 | }
|
---|
216 |
|
---|
217 |
|
---|
218 | /***********************************************************************/
|
---|
219 | /* */
|
---|
220 | /* get_time( time ) */
|
---|
221 | /* */
|
---|
222 | /* Update the structure pointed to by time with the current time */
|
---|
223 | /* as read from CMOS RAM of the RTC. */
|
---|
224 | /* If necessary, the time is converted into a binary format before */
|
---|
225 | /* being stored in the structure. */
|
---|
226 | /* */
|
---|
227 | /***********************************************************************/
|
---|
228 |
|
---|
229 | int dead;
|
---|
230 | void timeout(int sig) { dead= 1; }
|
---|
231 |
|
---|
232 | void get_time(struct tm *t)
|
---|
233 | {
|
---|
234 | int osec, n;
|
---|
235 | unsigned long i;
|
---|
236 | struct sigaction sa;
|
---|
237 |
|
---|
238 | /* Start a timer to keep us from getting stuck on a dead clock. */
|
---|
239 | sigemptyset(&sa.sa_mask);
|
---|
240 | sa.sa_flags = 0;
|
---|
241 | sa.sa_handler = timeout;
|
---|
242 | sigaction(SIGALRM, &sa, NULL);
|
---|
243 | dead = 0;
|
---|
244 | alarm(5);
|
---|
245 |
|
---|
246 | do {
|
---|
247 | osec = -1;
|
---|
248 | n = 0;
|
---|
249 | do {
|
---|
250 | if (dead) {
|
---|
251 | fprintf(stderr, "readclock: CMOS clock appears dead\n");
|
---|
252 | exit(1);
|
---|
253 | }
|
---|
254 |
|
---|
255 | /* Clock update in progress? */
|
---|
256 | if (read_register(RTC_REG_A) & RTC_A_UIP) continue;
|
---|
257 |
|
---|
258 | t->tm_sec = read_register(RTC_SEC);
|
---|
259 | if (t->tm_sec != osec) {
|
---|
260 | /* Seconds changed. First from -1, then because the
|
---|
261 | * clock ticked, which is what we're waiting for to
|
---|
262 | * get a precise reading.
|
---|
263 | */
|
---|
264 | osec = t->tm_sec;
|
---|
265 | n++;
|
---|
266 | }
|
---|
267 | } while (n < 2);
|
---|
268 |
|
---|
269 | /* Read the other registers. */
|
---|
270 | t->tm_min = read_register(RTC_MIN);
|
---|
271 | t->tm_hour = read_register(RTC_HOUR);
|
---|
272 | t->tm_mday = read_register(RTC_MDAY);
|
---|
273 | t->tm_mon = read_register(RTC_MONTH);
|
---|
274 | t->tm_year = read_register(RTC_YEAR);
|
---|
275 |
|
---|
276 | /* Time stable? */
|
---|
277 | } while (read_register(RTC_SEC) != t->tm_sec
|
---|
278 | || read_register(RTC_MIN) != t->tm_min
|
---|
279 | || read_register(RTC_HOUR) != t->tm_hour
|
---|
280 | || read_register(RTC_MDAY) != t->tm_mday
|
---|
281 | || read_register(RTC_MONTH) != t->tm_mon
|
---|
282 | || read_register(RTC_YEAR) != t->tm_year);
|
---|
283 |
|
---|
284 | if ((read_register(RTC_REG_B) & RTC_B_DM_BCD) == 0) {
|
---|
285 | /* Convert BCD to binary (default RTC mode). */
|
---|
286 | t->tm_year = bcd_to_dec(t->tm_year);
|
---|
287 | t->tm_mon = bcd_to_dec(t->tm_mon);
|
---|
288 | t->tm_mday = bcd_to_dec(t->tm_mday);
|
---|
289 | t->tm_hour = bcd_to_dec(t->tm_hour);
|
---|
290 | t->tm_min = bcd_to_dec(t->tm_min);
|
---|
291 | t->tm_sec = bcd_to_dec(t->tm_sec);
|
---|
292 | }
|
---|
293 | t->tm_mon--; /* Counts from 0. */
|
---|
294 |
|
---|
295 | /* Correct the year, good until 2080. */
|
---|
296 | if (t->tm_year < 80) t->tm_year += 100;
|
---|
297 |
|
---|
298 | if (y2kflag) {
|
---|
299 | /* Clock with Y2K bug, interpret 1980 as 2000, good until 2020. */
|
---|
300 | if (t->tm_year < 100) t->tm_year += 20;
|
---|
301 | }
|
---|
302 | }
|
---|
303 |
|
---|
304 |
|
---|
305 | int read_register(int reg_addr)
|
---|
306 | {
|
---|
307 | int r;
|
---|
308 |
|
---|
309 | intr_disable();
|
---|
310 | outb(RTC_INDEX, reg_addr);
|
---|
311 | r= inb(RTC_IO);
|
---|
312 | intr_enable();
|
---|
313 | return r;
|
---|
314 | }
|
---|
315 |
|
---|
316 |
|
---|
317 |
|
---|
318 | /***********************************************************************/
|
---|
319 | /* */
|
---|
320 | /* set_time( time ) */
|
---|
321 | /* */
|
---|
322 | /* Set the CMOS RTC to the time found in the structure. */
|
---|
323 | /* */
|
---|
324 | /***********************************************************************/
|
---|
325 |
|
---|
326 | void set_time(struct tm *t)
|
---|
327 | {
|
---|
328 | int regA, regB;
|
---|
329 |
|
---|
330 | if (Wflag) {
|
---|
331 | /* Set A and B registers to their proper values according to the AT
|
---|
332 | * reference manual. (For if it gets messed up, but the BIOS doesn't
|
---|
333 | * repair it.)
|
---|
334 | */
|
---|
335 | write_register(RTC_REG_A, RTC_A_DV_OK | RTC_A_RS_DEF);
|
---|
336 | write_register(RTC_REG_B, RTC_B_24);
|
---|
337 | }
|
---|
338 |
|
---|
339 | /* Inhibit updates. */
|
---|
340 | regB= read_register(RTC_REG_B);
|
---|
341 | write_register(RTC_REG_B, regB | RTC_B_SET);
|
---|
342 |
|
---|
343 | t->tm_mon++; /* Counts from 1. */
|
---|
344 |
|
---|
345 | if (y2kflag) {
|
---|
346 | /* Set the clock back 20 years to avoid Y2K bug, good until 2020. */
|
---|
347 | if (t->tm_year >= 100) t->tm_year -= 20;
|
---|
348 | }
|
---|
349 |
|
---|
350 | if ((regB & 0x04) == 0) {
|
---|
351 | /* Convert binary to BCD (default RTC mode) */
|
---|
352 | t->tm_year = dec_to_bcd(t->tm_year % 100);
|
---|
353 | t->tm_mon = dec_to_bcd(t->tm_mon);
|
---|
354 | t->tm_mday = dec_to_bcd(t->tm_mday);
|
---|
355 | t->tm_hour = dec_to_bcd(t->tm_hour);
|
---|
356 | t->tm_min = dec_to_bcd(t->tm_min);
|
---|
357 | t->tm_sec = dec_to_bcd(t->tm_sec);
|
---|
358 | }
|
---|
359 | write_register(RTC_YEAR, t->tm_year);
|
---|
360 | write_register(RTC_MONTH, t->tm_mon);
|
---|
361 | write_register(RTC_MDAY, t->tm_mday);
|
---|
362 | write_register(RTC_HOUR, t->tm_hour);
|
---|
363 | write_register(RTC_MIN, t->tm_min);
|
---|
364 | write_register(RTC_SEC, t->tm_sec);
|
---|
365 |
|
---|
366 | /* Stop the clock. */
|
---|
367 | regA= read_register(RTC_REG_A);
|
---|
368 | write_register(RTC_REG_A, regA | RTC_A_DV_STOP);
|
---|
369 |
|
---|
370 | /* Allow updates and restart the clock. */
|
---|
371 | write_register(RTC_REG_B, regB);
|
---|
372 | write_register(RTC_REG_A, regA);
|
---|
373 | }
|
---|
374 |
|
---|
375 |
|
---|
376 | void write_register(int reg_addr, int value)
|
---|
377 | {
|
---|
378 | intr_disable();
|
---|
379 | outb(RTC_INDEX, reg_addr);
|
---|
380 | outb(RTC_IO, value);
|
---|
381 | intr_enable();
|
---|
382 | }
|
---|
383 |
|
---|
384 | int bcd_to_dec(int n)
|
---|
385 | {
|
---|
386 | return ((n >> 4) & 0x0F) * 10 + (n & 0x0F);
|
---|
387 | }
|
---|
388 |
|
---|
389 | int dec_to_bcd(int n)
|
---|
390 | {
|
---|
391 | return ((n / 10) << 4) | (n % 10);
|
---|
392 | }
|
---|
393 |
|
---|
394 | void usage(void)
|
---|
395 | {
|
---|
396 | fprintf(stderr, "Usage: readclock [-nwW2]\n");
|
---|
397 | exit(1);
|
---|
398 | }
|
---|