/* readclock - read the real time clock Authors: T. Holm & E. Froese */ /************************************************************************/ /* */ /* readclock.c */ /* */ /* Read from the 64 byte CMOS RAM area, then write */ /* the time to standard output in a form usable by */ /* date(1). */ /* */ /* If the machine ID byte is 0xFC or 0xF8, the device */ /* /dev/mem exists and can be opened for reading, */ /* and no errors in the CMOS RAM are reported by the */ /* RTC, then the time is read from the clock RAM */ /* area maintained by the RTC. */ /* */ /* The clock RAM values are decoded and written to */ /* standard output in the form: */ /* */ /* mmddyyhhmmss */ /* */ /* If the machine ID does not match 0xFC or 0xF8, */ /* then ``-q'' is written to standard output. */ /* */ /* If the machine ID is 0xFC or 0xF8 and /dev/mem */ /* is missing, or cannot be accessed, */ /* then an error message is written to stderr, */ /* and ``-q'' is written to stdout. */ /* */ /* If the RTC reports errors in the CMOS RAM, */ /* then an error message is written to stderr, */ /* and ``-q'' is written to stdout. */ /* */ /* Readclock is used as follows when placed */ /* the ``/etc/rc'' script: */ /* */ /* /usr/bin/date `/usr/bin/readclock` #include #include #include #include #include #define CPU_TYPE_SEGMENT 0xFFFF /* Segment for Machine ID in BIOS */ #define CPU_TYPE_OFFSET 0x000E /* Offset for Machine ID */ #define PC_AT 0xFC /* Machine ID byte for PC/AT, PC/XT286, and PS/2 Models 50, 60 */ #define PS_386 0xF8 /* Machine ID byte for PS/2 Model 80 */ /* Manufacturers usually use the ID value of the IBM model they emulate. * However some manufacturers, notably HP and COMPAQ, have had different * ideas in the past. * * Machine ID byte information source: * _The Programmer's PC Sourcebook_ by Thom Hogan, * published by Microsoft Press */ #define CLK_ELE 0x70 /* CMOS RAM address register port (write only) * Bit 7 = 1 NMI disable * 0 NMI enable * Bits 6-0 = RAM address */ #define CLK_IO 0x71 /* CMOS RAM data register port (read/write) */ #define YEAR 9 /* Clock register addresses in CMOS RAM */ #define MONTH 8 #define DAY 7 #define WDAY 6 #define HOUR 4 #define MINUTE 2 #define SECOND 0 #define STATUS 0x0B /* Status register B: RTC configuration */ #define HEALTH 0x0E /* Diagnostic status: (should be set by Power * On Self-Test [POST]) * Bit 7 = RTC lost power * 6 = Checksum (for addr 0x10-0x2d) bad * 5 = Config. Info. bad at POST * 4 = Mem. size error at POST * 3 = I/O board failed initialization * 2 = CMOS time invalid * 1-0 = reserved */ #define DIAG_BADBATT 0x80 #define DIAG_MEMDIRT 0x40 #define DIAG_BADTIME 0x04 /* CMOS RAM and RTC information source: * _System BIOS for PC/XT/AT Computers and Compatibles_ * by Phoenix Technologies Ltd., published by Addison-Wesley */ #define BCD_TO_DEC(x) ( (x >> 4) * 10 + (x & 0x0f) ) #define DEC_TO_BCD(x) (( ((x/10) << 4) + (x % 10) ) & 0xff) #define errmsg(s) fputs( s, stderr ) struct time { unsigned year; unsigned month; unsigned day; unsigned hour; unsigned minute; unsigned second; }; _PROTOTYPE(int main, (int argc, char *argv[])); _PROTOTYPE(void get_time, (struct time *t)); _PROTOTYPE(void set_time, (struct tm *t)); _PROTOTYPE(int read_register, (int reg_addr)); _PROTOTYPE(void write_register, (int reg_addr, int reg_value)); /* I/O and memory functions. */ _PROTOTYPE(unsigned inb, (U16_t _port)); _PROTOTYPE(void outb, (U16_t _port, U8_t _value)); _PROTOTYPE(int peek, (int a, int b)); int main(argc, argv) int argc; char *argv[]; { struct time time1; struct time time2; struct tm *tm; int i; int utc = 0, setclock = 0; time_t now; int cpu_type, cmos_state; if(argv[0][0] == 's') setclock = 1; cpu_type = peek(CPU_TYPE_SEGMENT, CPU_TYPE_OFFSET); if (cpu_type < 0) { errmsg( "Memory I/O failed.\n" ); if(!setclock) printf("-q\n"); exit(1); } if (cpu_type != PS_386 && cpu_type != PC_AT) { /* This is probably an XT, exit without complaining. */ if(!setclock) printf("-q\n"); else errmsg("Not an AT-class machine, cannot set clock.\n"); exit(1); } cmos_state = read_register(HEALTH); if (cmos_state & (DIAG_BADBATT | DIAG_MEMDIRT | DIAG_BADTIME)) { errmsg( "\nCMOS RAM error(s) found... " ); fprintf( stderr, "CMOS state = 0x%02x\n", cmos_state ); if (cmos_state & DIAG_BADBATT) errmsg( "RTC lost power. Reset CMOS RAM with SETUP.\n" ); if (cmos_state & DIAG_MEMDIRT) errmsg( "CMOS RAM checksum is bad. Run SETUP.\n" ); if (cmos_state & DIAG_BADTIME) errmsg( "Time invalid in CMOS RAM. Reset clock with setclock.\n" ); errmsg( "\n" ); if(!setclock) printf("-q\n"); exit(1); } if(setclock) { if(argc == 2) if(argv[1][0] == '-') if(argv[1][1] == 'u') utc = 1; if(argc > 1 && !utc) { errmsg("Usage: setclock [-u]\n"); exit(1); } time(&now); if(utc) tm = gmtime(&now); else tm = localtime(&now); set_time(tm); exit(0); } for (i = 0; i < 10; i++) { get_time(&time1); get_time(&time2); if (time1.year == time2.year && time1.month == time2.month && time1.day == time2.day && time1.hour == time2.hour && time1.minute == time2.minute && time1.second == time2.second) { printf("%02d%02d%02d%02d%02d%02d\n", time1.month, time1.day, time1.year, time1.hour, time1.minute, time1.second); exit(0); } } errmsg( "Failed to get an accurate time.\n" ); printf("-q\n"); exit(1); } /***********************************************************************/ /* */ /* get_time( time ) */ /* */ /* Update the structure pointed to by time with the current time */ /* as read from CMOS RAM of the RTC. */ /* If necessary, the time is converted into a binary format before */ /* being stored in the structure. */ /* */ /***********************************************************************/ void get_time(t) struct time *t; { t->year = read_register(YEAR); t->month = read_register(MONTH); t->day = read_register(DAY); t->hour = read_register(HOUR); t->minute = read_register(MINUTE); t->second = read_register(SECOND); if ((read_register(STATUS) & 0x04) == 0) { /* Convert BCD to binary (default RTC mode) */ t->year = BCD_TO_DEC(t->year); t->month = BCD_TO_DEC(t->month); t->day = BCD_TO_DEC(t->day); t->hour = BCD_TO_DEC(t->hour); t->minute = BCD_TO_DEC(t->minute); t->second = BCD_TO_DEC(t->second); } } /************************************************************************/ /* set_time( time ) */ /* Update the hardware real-time clock of the AT. */ /* Set the clock mode to BCD format if mode is binary. */ /************************************************************************/ void set_time(t) struct tm *t; { int status; t->tm_year = (t->tm_year > 99) ? t->tm_year - 1900 : t->tm_year; t->tm_mon++; if (((status = read_register(STATUS)) & 0x04) == 0) { /* Convert binary to BCD if necessary */ t->tm_year = DEC_TO_BCD(t->tm_year); t->tm_mon = DEC_TO_BCD(t->tm_mon); t->tm_wday = DEC_TO_BCD(t->tm_wday); t->tm_mday = DEC_TO_BCD(t->tm_mday); t->tm_hour = DEC_TO_BCD(t->tm_hour); t->tm_min = DEC_TO_BCD(t->tm_min); t->tm_sec = DEC_TO_BCD(t->tm_sec); } write_register(STATUS, status | 0x80); /* update clock */ write_register(YEAR, t->tm_year); write_register(MONTH, t->tm_mon); write_register(DAY, t->tm_mday); write_register(WDAY, t->tm_wday); write_register(HOUR, t->tm_hour); write_register(MINUTE, t->tm_min); write_register(SECOND, t->tm_sec); write_register(STATUS, status); /* update finished */ } int read_register(reg_addr) char reg_addr; { outb(CLK_ELE, reg_addr); return inb(CLK_IO); } void write_register(reg_addr, reg_value) char reg_addr; int reg_value; { outb(CLK_ELE, reg_addr); outb(CLK_IO, reg_value); }