1 /* readclock - read the real time clock Authors: T. Holm & E. Froese
3 * Changed to be user-space driver.
6 /************************************************************************/
10 /* Read the clock value from the 64 byte CMOS RAM */
11 /* area, then set system time. */
13 /* If the machine ID byte is 0xFC or 0xF8, the device */
14 /* /dev/mem exists and can be opened for reading, */
15 /* and no errors in the CMOS RAM are reported by the */
16 /* RTC, then the time is read from the clock RAM */
17 /* area maintained by the RTC. */
19 /* The clock RAM values are decoded and fed to mktime */
20 /* to make a time_t value, then stime(2) is called. */
24 /* If the machine ID does not match 0xFC or 0xF8 (no */
27 /* If the machine ID is 0xFC or 0xF8 and /dev/mem */
28 /* is missing, or cannot be accessed. */
30 /* If the RTC reports errors in the CMOS RAM. */
32 /************************************************************************/
33 /* origination 1987-Dec-29 efth */
34 /* robustness 1990-Oct-06 C. Sylvain */
35 /* incorp. B. Evans ideas 1991-Jul-06 C. Sylvain */
36 /* set time & calibrate 1992-Dec-17 Kees J. Bot */
37 /* clock timezone 1993-Oct-10 Kees J. Bot */
38 /* set CMOS clock 1994-Jun-12 Kees J. Bot */
39 /************************************************************************/
42 #include <sys/types.h>
52 #include <minix/type.h>
53 #include <minix/syslib.h>
54 #include <minix/com.h>
55 #include <minix/portio.h>
57 #include <sys/svrctl.h>
59 int nflag
= 0; /* Tell what, but don't do it. */
60 int wflag
= 0; /* Set the CMOS clock. */
61 int Wflag
= 0; /* Also set the CMOS clock register bits. */
62 int y2kflag
= 0; /* Interpret 1980 as 2000 for clock with Y2K bug. */
64 char clocktz
[128]; /* Timezone of the clock. */
66 #define MACH_ID_ADDR 0xFFFFE /* BIOS Machine ID at FFFF:000E */
68 #define PC_AT 0xFC /* Machine ID byte for PC/AT,
69 PC/XT286, and PS/2 Models 50, 60 */
70 #define PS_386 0xF8 /* Machine ID byte for PS/2 Model 80 */
72 /* Manufacturers usually use the ID value of the IBM model they emulate.
73 * However some manufacturers, notably HP and COMPAQ, have had different
76 * Machine ID byte information source:
77 * _The Programmer's PC Sourcebook_ by Thom Hogan,
78 * published by Microsoft Press
82 void get_time(struct tm
*t
);
83 int read_register(int reg_addr
);
84 void set_time(struct tm
*t
);
85 void write_register(int reg_addr
, int value
);
86 int bcd_to_dec(int n
);
87 int dec_to_bcd(int n
);
90 int main(int argc
, char **argv
)
98 unsigned char mach_id
, cmos_state
;
99 struct sysgetenv sysgetenv
;
101 if((s
=sys_readbios(MACH_ID_ADDR
, &mach_id
, sizeof(mach_id
))) != OK
) {
102 printf("readclock: sys_readbios failed: %d.\n", s
);
106 if (mach_id
!= PS_386
&& mach_id
!= PC_AT
) {
107 errmsg("Machine ID unknown." );
108 printf("Machine ID byte = %02x\n", mach_id
);
113 cmos_state
= read_register(CMOS_STATUS
);
115 if (cmos_state
& (CS_LOST_POWER
| CS_BAD_CHKSUM
| CS_BAD_TIME
)) {
116 errmsg( "CMOS RAM error(s) found..." );
117 printf("CMOS state = 0x%02x\n", cmos_state
);
119 if (cmos_state
& CS_LOST_POWER
)
120 errmsg( "RTC lost power. Reset CMOS RAM with SETUP." );
121 if (cmos_state
& CS_BAD_CHKSUM
)
122 errmsg( "CMOS RAM checksum is bad. Run SETUP." );
123 if (cmos_state
& CS_BAD_TIME
)
124 errmsg( "Time invalid in CMOS RAM. Reset clock." );
128 /* Process options. */
132 if (*p
++ != '-') usage();
136 case 'n': nflag
= 1; break;
137 case 'w': wflag
= 1; break;
138 case 'W': Wflag
= 1; break;
139 case '2': y2kflag
= 1; break;
145 if (Wflag
) wflag
= 1; /* -W implies -w */
148 /* The hardware clock may run in a different time zone, likely GMT or
149 * winter time. Select that time zone.
151 strcpy(clocktz
, "TZ=");
152 sysgetenv
.key
= "TZ";
153 sysgetenv
.keylen
= 2+1;
154 sysgetenv
.val
= clocktz
+3;
155 sysgetenv
.vallen
= sizeof(clocktz
)-3;
156 if (svrctl(SYSGETENV
, &sysgetenv
) == 0) {
162 /* Read the CMOS real time clock. */
163 for (i
= 0; i
< 10; i
++) {
167 time1
.tm_isdst
= -1; /* Do timezone calculations. */
170 rtc
= mktime(&time1
); /* Transform to a time_t. */
171 if (rtc
!= -1) break;
174 "readclock: Invalid time read from CMOS RTC: %d-%02d-%02d %02d:%02d:%02d\n",
175 time2
.tm_year
+1900, time2
.tm_mon
+1, time2
.tm_mday
,
176 time2
.tm_hour
, time2
.tm_min
, time2
.tm_sec
);
179 if (i
== 10) exit(1);
182 /* Set system time. */
184 printf("stime(%lu)\n", (unsigned long) rtc
);
186 if (stime(&rtc
) < 0) {
187 errmsg( "Not allowed to set time." );
191 tmnow
= *localtime(&rtc
);
192 if (strftime(date
, sizeof(date
),
193 "%a %b %d %H:%M:%S %Z %Y", &tmnow
) != 0) {
194 if (date
[8] == '0') date
[8]= ' ';
195 printf("%s\n", date
);
198 /* Set the CMOS clock to the system time. */
199 tmnow
= *localtime(&now
);
201 printf("%04d-%02d-%02d %02d:%02d:%02d\n",
202 tmnow
.tm_year
+ 1900,
217 static char *prompt
= "readclock: ";
219 printf("%s%s\n", prompt
, s
);
224 /***********************************************************************/
226 /* get_time( time ) */
228 /* Update the structure pointed to by time with the current time */
229 /* as read from CMOS RAM of the RTC. */
230 /* If necessary, the time is converted into a binary format before */
231 /* being stored in the structure. */
233 /***********************************************************************/
236 void timeout(int sig
) { dead
= 1; }
238 void get_time(struct tm
*t
)
244 /* Start a timer to keep us from getting stuck on a dead clock. */
245 sigemptyset(&sa
.sa_mask
);
247 sa
.sa_handler
= timeout
;
248 sigaction(SIGALRM
, &sa
, NULL
);
257 printf("readclock: CMOS clock appears dead\n");
261 /* Clock update in progress? */
262 if (read_register(RTC_REG_A
) & RTC_A_UIP
) continue;
264 t
->tm_sec
= read_register(RTC_SEC
);
265 if (t
->tm_sec
!= osec
) {
266 /* Seconds changed. First from -1, then because the
267 * clock ticked, which is what we're waiting for to
268 * get a precise reading.
275 /* Read the other registers. */
276 t
->tm_min
= read_register(RTC_MIN
);
277 t
->tm_hour
= read_register(RTC_HOUR
);
278 t
->tm_mday
= read_register(RTC_MDAY
);
279 t
->tm_mon
= read_register(RTC_MONTH
);
280 t
->tm_year
= read_register(RTC_YEAR
);
283 } while (read_register(RTC_SEC
) != t
->tm_sec
284 || read_register(RTC_MIN
) != t
->tm_min
285 || read_register(RTC_HOUR
) != t
->tm_hour
286 || read_register(RTC_MDAY
) != t
->tm_mday
287 || read_register(RTC_MONTH
) != t
->tm_mon
288 || read_register(RTC_YEAR
) != t
->tm_year
);
290 if ((read_register(RTC_REG_B
) & RTC_B_DM_BCD
) == 0) {
291 /* Convert BCD to binary (default RTC mode). */
292 t
->tm_year
= bcd_to_dec(t
->tm_year
);
293 t
->tm_mon
= bcd_to_dec(t
->tm_mon
);
294 t
->tm_mday
= bcd_to_dec(t
->tm_mday
);
295 t
->tm_hour
= bcd_to_dec(t
->tm_hour
);
296 t
->tm_min
= bcd_to_dec(t
->tm_min
);
297 t
->tm_sec
= bcd_to_dec(t
->tm_sec
);
299 t
->tm_mon
--; /* Counts from 0. */
301 /* Correct the year, good until 2080. */
302 if (t
->tm_year
< 80) t
->tm_year
+= 100;
305 /* Clock with Y2K bug, interpret 1980 as 2000, good until 2020. */
306 if (t
->tm_year
< 100) t
->tm_year
+= 20;
311 int read_register(int reg_addr
)
315 if(sys_outb(RTC_INDEX
, reg_addr
) != OK
) {
316 printf("cmos: outb failed of %x\n", RTC_INDEX
);
319 if(sys_inb(RTC_IO
, &r
) != OK
) {
320 printf("cmos: inb failed of %x (index %x) failed\n", RTC_IO
, reg_addr
);
328 /***********************************************************************/
330 /* set_time( time ) */
332 /* Set the CMOS RTC to the time found in the structure. */
334 /***********************************************************************/
336 void set_time(struct tm
*t
)
341 /* Set A and B registers to their proper values according to the AT
342 * reference manual. (For if it gets messed up, but the BIOS doesn't
345 write_register(RTC_REG_A
, RTC_A_DV_OK
| RTC_A_RS_DEF
);
346 write_register(RTC_REG_B
, RTC_B_24
);
349 /* Inhibit updates. */
350 regB
= read_register(RTC_REG_B
);
351 write_register(RTC_REG_B
, regB
| RTC_B_SET
);
353 t
->tm_mon
++; /* Counts from 1. */
356 /* Set the clock back 20 years to avoid Y2K bug, good until 2020. */
357 if (t
->tm_year
>= 100) t
->tm_year
-= 20;
360 if ((regB
& 0x04) == 0) {
361 /* Convert binary to BCD (default RTC mode) */
362 t
->tm_year
= dec_to_bcd(t
->tm_year
% 100);
363 t
->tm_mon
= dec_to_bcd(t
->tm_mon
);
364 t
->tm_mday
= dec_to_bcd(t
->tm_mday
);
365 t
->tm_hour
= dec_to_bcd(t
->tm_hour
);
366 t
->tm_min
= dec_to_bcd(t
->tm_min
);
367 t
->tm_sec
= dec_to_bcd(t
->tm_sec
);
369 write_register(RTC_YEAR
, t
->tm_year
);
370 write_register(RTC_MONTH
, t
->tm_mon
);
371 write_register(RTC_MDAY
, t
->tm_mday
);
372 write_register(RTC_HOUR
, t
->tm_hour
);
373 write_register(RTC_MIN
, t
->tm_min
);
374 write_register(RTC_SEC
, t
->tm_sec
);
376 /* Stop the clock. */
377 regA
= read_register(RTC_REG_A
);
378 write_register(RTC_REG_A
, regA
| RTC_A_DV_STOP
);
380 /* Allow updates and restart the clock. */
381 write_register(RTC_REG_B
, regB
);
382 write_register(RTC_REG_A
, regA
);
386 void write_register(int reg_addr
, int value
)
388 if(sys_outb(RTC_INDEX
, reg_addr
) != OK
) {
389 printf("cmos: outb failed of %x\n", RTC_INDEX
);
392 if(sys_outb(RTC_IO
, value
) != OK
) {
393 printf("cmos: outb failed of %x (index %x)\n", RTC_IO
, reg_addr
);
398 int bcd_to_dec(int n
)
400 return ((n
>> 4) & 0x0F) * 10 + (n
& 0x0F);
403 int dec_to_bcd(int n
)
405 return ((n
/ 10) << 4) | (n
% 10);
410 printf("Usage: readclock [-nwW2]\n");