CMOS: Difference between revisions

11,495 bytes added ,  26 days ago
m
Bot: Replace deprecated source tag with syntaxhighlight
[unchecked revision][unchecked revision]
(Small wording changes regarding RTC)
m (Bot: Replace deprecated source tag with syntaxhighlight)
 
(37 intermediate revisions by 19 users not shown)
Line 1:
"CMOS" is a tiny bit of very low power static memory that lives on the same chip as the Real-Time Clock (RTC). It
It was introduced to IBM PC AT in 1984 which used Motorola MC146818A RTC.
is fairly convenient to actually think of the RTC as being "part" of CMOS.
 
CMOS (and the Real-Time Clock) can only be accessed through IO Ports 0x70 and 0x71. The function of the CMOS
memory is to store 50 (or 114) bytes of "Setup" information for the BIOS while the computer is turned off --
Line 36 ⟶ 37:
as follows:
 
* <tt>outb (0x70, (NMI_disable_bit << 7) | (selected CMOS register number));</tt>
 
Once a register is selected, you either read the value of that register on Port 0x71 (with inb or an equivalent
function), or you write a new value to that register -- also on Port 0x71 (with outb, for example):
 
* <tt>val_8bit = inb (0x71);</tt>
 
Note1: Reading or writing Port 0x71 seems to default the "selected register" back to 0xD. So you need to
Line 89 ⟶ 90:
holes", it ignores memory mapped hardware, and it ignores memory reserved for important ACPI system tables.
 
(Register 0x350x31 (high byte) | Register 0x340x30 (low byte)) << 16 = total memory between 16M and 4G ... usually.
But this number is untrustworthy when the system has more than 4G, it ignores "memory holes", it ignores
memory mapped hardware, and it ignores memory reserved for important ACPI system tables.
Line 99 ⟶ 100:
Better information can always be obtained via BIOS function INT13h AH=8, or by sending an ATA 'Identify'
command to the disk in [[ATA PIO Mode]].
 
 
== The Real-Time Clock ==
Line 115:
=== Getting Current Date and Time from RTC ===
 
To get each of the following date/time values from the RTC, you should first ensure that you won't be effected by an update ([[#RTC Update In Progress|see below]]). Then select the associated "CMOS register" in the
[[#Accessing CMOS Registers|usual way]], and read the value from Port 0x71.
 
<pre>
Register Contents Range
0x00 0 Seconds Seconds 0–59
0x02 2 Minutes Minutes 0–59
0x04 Hours 0–23 in 24-hour mode,
4 Hours
1–12 in 12-hour mode, highest bit set if pm
6 Weekday
0x06 7 Weekday Day of Month 1–7, Sunday = 1
0x07 8 Day of Month Month 1–31
0x08 9 Month Year 1–12
0x09 Year 0–99
0x32 Century (usually)
0x32 Century (maybe) 19–20?
0xb Status Register B
0x0A Status Register A
0x0B Status Register B
</pre>
 
==== FormatCentury ofRegister Bytes ====
 
Originally the RTC didn't have a century register at all. In the 1990s (as the year 2000 got closer) hardware manufacturers started realising that this might become a problem; so they starting adding century registers to their RTC. Unfortunately, because there was no official standard to follow, different manufacturers used different registers.
 
This meant that software didn't know if there was a century register, and (if there is) which register it might be. To fix that problem the ACPI specification included a "RTC century register" field at offset 108 in its "Fixed ACPI Description Table". If this field contains zero then the RTC doesn't have a century register, and if the field is non-zero then it contains the number of the RTC register to use for century.
 
If there is no century register then software can guess. For example, a piece of software written in 1990 can use the (2 digit) year register to determine the most likely century - if the RTC year register is higher than or equal to 90 then the year is probably be "19YY" and if the RTC year register is less than 90 than the year must be "20YY". In this way, software can correctly determine the century for up to 99 years after the software is written.
 
==== Century Register As Time and Date Sanity Check ====
 
If the CMOS/RTC has a century register, your software was released 2014, and the CMOS/RTC says the century and year are 2008; then obviously the CMOS/RTC must be wrong.
 
Similarly, people tend to update their OS occasionally. If the CMOS/RTC has a century register, your software was released 2014, and the CMOS/RTC says the century and year are 2154; then it's unlikely that the OS hasn't been updated for 140 years, and far more likely that the CMOS/RTC is wrong.
 
Essentially; the method (described above) for guessing the century when there is no century register is much more reliable than the CMOS/RTC century register (if it exists). This means that the century register (if/when present) can be used in reverse, as a way to check if the CMOS/RTC time and date are sane (or if the CMOS/RTC has a flat battery or something).
 
Basically, you'd guess the century (based on the software's release date and RTC's year), then check if the CMOS/RTC century is the same as your guess and if it's not then assume all CMOS/RTC time and date fields are invalid.
 
=== Weekday Register ===
 
The RTC chip is able to keep track of the current day of the week. All it does is increment its "Weekday" register at midnight and reset it to 1 if the incremented value would go above 7[https://groups.google.com/d/msg/alt.os.development/qPxWHKC48rw/yjAt-c8IAQAJ]. Unfortunately there is no guarantee that this register was ever set correctly by anything (including when the user changes the time and date using the BIOS configuration screen). It is entirely unreliable and should not be used.
 
The correct way to determine the current day of the week is to calculate it from the date (see [http://en.wikipedia.org/wiki/Weekday_determination the article on Wikipedia] for details of this calculation).
 
=== RTC Update In Progress ===
 
When the chip updates the time and date (once per second) it increases "seconds" and checks if it rolled over. If "seconds" did roll over it increases "minutes" and checks if that rolled over. This can continue through all the time and date registers (e.g. all the way up to "if year rolled over, increase century"). However, the RTC circuitry is typically relatively slow. This means that it's entirely possible to read the time and date while an update is in progress and get dodgy/inconsistent values (for example, at 9:00 o'clock you might read 8:59, or 8:60, or 8:00, or 9:00).
 
To help guard against this problem the RTC has an "Update in progress" flag (bit 7 of Status Register A). To read the time and date properly you have to wait until the "Update in progress" flag goes from "set" to "clear". This is not the same as checking that the "Update in progress" flag is clear.
 
For example, if code does "''while(update_in_progress_flag != clear)''" and then starts reading all the time and date registers, then the update could begin immediately after the "Update in progress" flag was checked and the code could still read dodgy/inconsistent values. To avoid this, code should wait until the flag becomes set and then wait until the flag becomes clear. That way there's almost 1 second of time to read all of the registers correctly.
 
Unfortunately, doing it correctly (waiting until the "Update in progress" flag becomes set and then waiting until it becomes clear) is very slow - it may take an entire second of waiting/polling before you can read the registers. There are 2 alternatives.
 
The first alternative is to rely on the "update interrupt". When the RTC finishes an update it generates an "update interrupt" (if it's enabled), and the IRQ handler can safely read the time and date registers without worrying about the update at all (and without checking the "Update in progress" flag); as long as the IRQ handler doesn't take almost a full second to do it. In this case you're not wasting up to 1 second of CPU time waiting/polling, but it may still take a full second before the time and date has been read. Despite this it can be a useful technique during OS boot - e.g. setup the "update interrupt" and its IRQ handler as early as you can and then do other things (e.g. loading files from disk), in the hope that the IRQ occurs before you need the time and date.
 
The second alternative is to be prepared for dodgy/inconsistent values and cope with them if they occur. To do this, make sure the "Update in progress" flag is clear (e.g. "''while(update_in_progress_flag != clear)''") then read all the time and date registers; then make sure the "Update in progress" flag is clear again (e.g. "''while(update_in_progress_flag != clear)''") and read all the time and date registers again. If the values that were read the first time are the same as the value that were read the second time then the values must be correct. If any of the values are different you need to do it again, and keep doing it again until the newest values are the same as the previous values.
 
=== Format of Bytes ===
 
There are 4 formats possible for the date/time RTC bytes:
Line 149 ⟶ 189:
 
In BCD mode, each of the two hex nibbles of the byte is modified to "display" a '''decimal''' number.
So 1:59:48 has hours = 1, minutes = 0x59 = 89, seconds = 0x48 = 72. To convert BCD back into a "good" binary value, use: binary = ((bcd / 16) * 10) + (bcd & 0xf) ''[Optimised: binary = ( (bcd & 0xF0) >> 1) + ( (bcd & 0xF0) >> 3) + (bcd & 0xf)]''.
 
24 hour time is exactly what you would expect. Hour 0 is midnight to 1am, hour 23 is 11pm.
Line 159 ⟶ 199:
 
For the weekday format: Sunday = 1, Saturday = 7.
 
=== Interpreting RTC Values ===
 
On the surface, these values from the RTC seem extremely obvious. The main difficulty comes in deciding which timezone the values represent. The two possibilities are usually UTC, or the system's timezone, including Daylight Savings. See the [[Time And Date]] article for a much more complete discussion of how to handle this issue.
 
== Examples ==
 
=== Reading from the CMOS ===
<sourcesyntaxhighlight lang="c">
ReadFromCMOS (unsigned char array [])
{
Line 185 ⟶ 229:
}
}
</syntaxhighlight>
</source>
 
=== Writing to the CMOS ===
<sourcesyntaxhighlight lang="c">
WriteTOCMOS(unsigned char array[])
{
Line 208 ⟶ 252:
}
}
</syntaxhighlight>
</source>
 
=== Reading All RTC Time and Date Registers ===
<syntaxhighlight lang="c">
#define CURRENT_YEAR 2023 // Change this each year!
 
int century_register = 0x00; // Set by ACPI table parsing code if possible
 
unsigned char second;
unsigned char minute;
unsigned char hour;
unsigned char day;
unsigned char month;
unsigned int year;
 
void out_byte(int port, int value);
int in_byte(int port);
 
enum {
cmos_address = 0x70,
cmos_data = 0x71
};
 
int get_update_in_progress_flag() {
out_byte(cmos_address, 0x0A);
return (in_byte(cmos_data) & 0x80);
}
 
unsigned char get_RTC_register(int reg) {
out_byte(cmos_address, reg);
return in_byte(cmos_data);
}
 
void read_rtc() {
unsigned char century;
unsigned char last_second;
unsigned char last_minute;
unsigned char last_hour;
unsigned char last_day;
unsigned char last_month;
unsigned char last_year;
unsigned char last_century;
unsigned char registerB;
 
// Note: This uses the "read registers until you get the same values twice in a row" technique
// to avoid getting dodgy/inconsistent values due to RTC updates
 
while (get_update_in_progress_flag()); // Make sure an update isn't in progress
second = get_RTC_register(0x00);
minute = get_RTC_register(0x02);
hour = get_RTC_register(0x04);
day = get_RTC_register(0x07);
month = get_RTC_register(0x08);
year = get_RTC_register(0x09);
if(century_register != 0) {
century = get_RTC_register(century_register);
}
 
do {
last_second = second;
last_minute = minute;
last_hour = hour;
last_day = day;
last_month = month;
last_year = year;
last_century = century;
 
while (get_update_in_progress_flag()); // Make sure an update isn't in progress
second = get_RTC_register(0x00);
minute = get_RTC_register(0x02);
hour = get_RTC_register(0x04);
day = get_RTC_register(0x07);
month = get_RTC_register(0x08);
year = get_RTC_register(0x09);
if(century_register != 0) {
century = get_RTC_register(century_register);
}
} while( (last_second != second) || (last_minute != minute) || (last_hour != hour) ||
(last_day != day) || (last_month != month) || (last_year != year) ||
(last_century != century) );
 
registerB = get_RTC_register(0x0B);
 
// Convert BCD to binary values if necessary
 
if (!(registerB & 0x04)) {
second = (second & 0x0F) + ((second / 16) * 10);
minute = (minute & 0x0F) + ((minute / 16) * 10);
hour = ( (hour & 0x0F) + (((hour & 0x70) / 16) * 10) ) | (hour & 0x80);
day = (day & 0x0F) + ((day / 16) * 10);
month = (month & 0x0F) + ((month / 16) * 10);
year = (year & 0x0F) + ((year / 16) * 10);
if(century_register != 0) {
century = (century & 0x0F) + ((century / 16) * 10);
}
}
 
// Convert 12 hour clock to 24 hour clock if necessary
 
if (!(registerB & 0x02) && (hour & 0x80)) {
hour = ((hour & 0x7F) + 12) % 24;
}
 
// Calculate the full (4-digit) year
 
if(century_register != 0) {
year += century * 100;
} else {
year += (CURRENT_YEAR / 100) * 100;
if(year < CURRENT_YEAR) year += 100;
}
}
</syntaxhighlight>
 
== See Also ==
* [[RTC]]
[http://ivs.cs.uni-magdeburg.de/~zbrog/asm/cmos.html Old CMOS Map]
 
== External Links ==
* [https://web.archive.org/web/20111209041013/http://www-ivs.cs.uni-magdeburg.de/~zbrog/asm/cmos.html Old CMOS Map]
* [http://www.bioscentral.com/misc/cmosmap.htm Better CMOS Map]
* [http://bitsavers.trailing-edge.com/pdf/ibm/pc/at/1502494_PC_AT_Technical_Reference_Mar84.pdf IBM PC AT Technical Reference]
* [http://web.stanford.edu/class/cs140/projects/pintos/specs/mc146818a.pdf MC146818A REAL-TIME CLOCK PLUS RAM (RTC)]
* [http://www.bitsavers.org/pdf/ibm/pc/ps2/PS2_Model_50_Technical_Reference_May88.pdf IBM PS/2 Technical Reference]
* [https://www.singlix.com/trdos/archive/pdf_archive/real-time-clock-nmi-enable-paper.pdf Intel Application Note: Accessing the Real Time Clock Registers and NMI Enable Bit]
 
[[Category:X86]]
[[Category:Time]]
[[de::CMOS]]