[9] | 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 | }
|
---|