CMOS: Difference between revisions

From OSDev.wiki
Jump to navigation Jump to search
[unchecked revision][unchecked revision]
Content added Content deleted
(reorganized sections for examples, sorry about last edit)
(Big rewrite -- added a lot of detail)
Line 1: Line 1:
"CMOS" is a tiny bit of very low power static memory that lives on the same chip as the Real-Time Clock. It
All the required settings about FDD, HDD, timers, etc. are stored in a small capacity low powered memory called the CMOS. The CMOS (Complementary Metal Oxide Semiconductor) memory is 65 or 128 byte RAM which is powered by a battery on the motherboard.
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 --
because there is a separate battery that keeps the Clock and the CMOS information active.


CMOS values are accessed a byte at a time, and each byte is individually addressable. Each CMOS address is
== Reading from and writing to the CMOS ==
traditionally called a "register". The first 14 CMOS registers access and control the Real-Time Clock. In
fact, the only truly useful registers remaining in CMOS are the Real-Time Clock registers, and register 0x10.
All other registers in CMOS are almost entirely obsolete (or are not standardized), and are therefore useless.


== Non-Maskable Interrupts ==
To access CMOS RAM, the index address (0 to 7F hex) is output to port 70h, and the data is then read or written at port 71h.

For frugality in the olden days, many functions were merged together on chips where there was "room" -- even
if they did not belong together. An example is putting the [[A20]] address enable on the PS2 keyboard controller.
In the same way, the "NMI disable" control was put together with the CMOS controller and the Real-Time Clock.

NMI is meant to communicate a "panic" status from the hardware to the CPU in a way that the CPU cannot
ignore. It is typically used to signal memory errors. For more information about NMI, see the [[NMI]] article.

Whenever you send a byte to IO port 0x70, the high order bit tells the hardware whether to disable NMIs from
reaching the CPU. If the bit is on, NMI is disabled (until the next time you send a byte to Port 0x70).
The low order 7 bits of any byte sent to Port 0x70 are used to address CMOS registers.

== CMOS Registers ==

The most updated CMOS register map, showing all of the conflicting register definitions between the various
BIOSes, is in [[RBIL]] in the file called CMOS.LST.

=== Accessing CMOS Registers ===

Accessing CMOS is extremely simple, but you always need to take into account how you want to handle NMI.
You "select" a CMOS register (for reading or writing) by sending the register number to IO Port 0x70.
Since the 0x80 bit of Port 0x70 controls NMI, you always end up setting that, too. So your CMOS controller
always needs to know whether your OS wants NMI to be enabled or not. Selecting a CMOS register is done
as follows:

* outb (0x70, (NMI_disable_bit << 7) | (selected CMOS register number));

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):

* val_8bit = inb (0x71);

Note1: Reading or writing Port 0x71 seems to default the "selected register" back to 0xD. So you need to
'''reselect''' the register every single time you want to access a CMOS register.

Note2: It is probably a good idea to have a reasonable delay after selecting a CMOS register on Port 0x70,
before reading/writing the value on Port 0x71.

==== Checksums ====

The proper functioning of the BIOS during bootup depends on the values in CMOS. So the values are protected
against random changes with checksums. It is very unwise ever to write a value into any of the CMOS registers
(except for the RTC) -- because when you change a value you also have to go fix a BIOS-specific checksum in a
different register -- or else the next boot will crash with an "invalid checksum" error. And since the checksum
is located at a proprietary BIOS-specific register number, good luck finding it.

=== Register 0x10 ===

This register contains the only CMOS value that an OS might ever find to be useful. It describes the "type"
of each of the two floppy drives that may be attached to the system. The high nibble describes the "master"
floppy drive on the primary bus, and the low nibble has an identical description for the "slave" floppy drive.

Value of each 4 bit nibble, and associated floppy drive type:
<pre>
Value Drive Type
00h no drive
01h 360 KB 5.25 Drive
02h 1.2 MB 5.25 Drive
03h 720 KB 3.5 Drive
04h 1.44 MB 3.5 Drive
05h 2.88 MB 3.5 drive
</pre>
Bits 0 to 3 = slave floppy type, bits 4 to 7 = master floppy type

=== Memory Size Registers ===

There are several CMOS registers that are standardized, and that seem to report useful information about the
total memory on the system. However, each of them is lacking vital information that your OS will need. It is
always better to use a BIOS function call to get information about memory than to use the information in CMOS.
See [[Detecting Memory (x86)]].

(Register 0x16 (high byte) | Register 0x15 (low byte)) << 10 = 640K = size of low memory (without taking the
[[Memory Map (x86)#EBDA|EBDA]] into account.

(Register 0x18 (high byte) | Register 0x17 (low byte)) << 10 = total memory between 1M and 16M, or maybe 65M
... usually. But this number is extra untrustworthy when the system has more than 64M, it ignores "memory
holes", it ignores memory mapped hardware, and it ignores memory reserved for important ACPI system tables.

(Register 0x35 (high byte) | Register 0x34 (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.

=== Hard Disk Registers ===

There are many CMOS registers in various locations, used by various ancient BIOSes, to store a "hard disk type" or
other hard disk information. Any such information is strictly for use on obsolete CHS-based disk drives.
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 ==

The RTC keeps track of the date and time, even when the computer's power is off. The only other way that a
computer used to be able to do this was to ask a human for the date/time on every bootup. Now, if the computer
has an internet connection, an OS has another (arguably better) way to get the same information.

The RTC also can generate clock ticks on IRQ8 (similarly to what the [[PIC]] does on IRQ0). The highest feasible
clock frequency is 8KHz. Using the RTC clock this way may actually generate more stable clock pulses than the PIC
can generate. It also frees up the PIC for timing events that really need near-microsecond accuracy.
Additionally, the RTC can generate an IRQ8 at a particular time of day. See the [[RTC]] article for more
detailed information about using RTC interrupts.

=== Getting Current Date and Time from RTC ===

To get each of the following date/time values from the RTC, select the associated "CMOS register" in the
[[#Accessing CMOS Registers|usual way]], and read the value from Port 0x71.

<pre>
Register Contents
0 Seconds
2 Minutes
4 Hours
6 Weekday
7 Day of Month
8 Month
9 Year
0x32 Century
0xb Status Register B
</pre>

==== Format of Bytes ====

There are 4 formats possible for the date/time RTC bytes:
* Binary or BCD Mode
* Hours in 12 hour format or 24 hour format

The format is controlled by Status Register B. On some CMOS/RTC chips, the format bits in Status Reg B
'''cannot be changed'''. So your code needs to be able to handle all four possibilities, and it should
not try to modify Status Register B's settings. So you always need to read Status Register B first, to
find out what format your date/time bytes will arrive in.

* Status Register B, Bit 1 (value = 2): Enables 24 hour format if set
* Status Register B, Bit 2 (value = 4): Enables Binary mode if set

Binary mode is exactly what you would expect the value to be. If the time is 1:59:59 AM, then the value
of hours would be 1, minutes would be 59 = 0x3b, and seconds would also be 59 = 0x3b.

In BCD mode, each of the two hex nibbles of the byte is modified to "display" a '''decimal''' number.
So 1:59:59 has hours = 1, minutes = 0x59 = 89, seconds = 0x59 = 89. It is slightly annoying to convert
BCD back into a "good" binary value.

24 hour time is exactly what you would expect. Hour 0 is midnight to 1am, hour 23 is 11pm.

12 hour time is annoying. If the hour is pm, then the 0x80 bit is set on the hour byte. So you need to
mask that off. (This is true for '''both''' binary and BCD modes.) Then, midnight is 12, 1am is 1, etc.
Note that carefully: midnight is not 0 -- it is 12 -- this needs to be handled in the calculation from
12 hour format to 24 hour format!

For the weekday format: Sunday = 1, Saturday = 7.


== Examples ==
== Examples ==

All these examples use Intel-style [[assembly]].
=== Reading from the CMOS ===
=== Reading from the CMOS ===
<pre>
<pre>
ReadFromCMOS (unsigned char array [])
ReadFromCMOS (unsigned char array [])
{
{
int index;
unsigned char tvalue, index;
unsigned char tvalue, bindex;


for(index = 0; index < 128; index++)
for(index = 0; index < 128; index++)
{
{
char AL_ = (unsigned char) index;
_asm
_asm
{
{
xor ax,ax /* Clear accumulator*/
cli /* Disable interrupts*/
cli /* Disable interrupts*/
mov al,AL_ /* Move index address*/
mov al, index /* Move index address*/
out 0x70,al /* Copy address to CMOS register*/
out 0x70,al /* Copy address to CMOS register*/
/* some kind of real delay here is probably best */
nop
nop
nop /* Wait a bit for response*/
in al,0x71 /* Fetch 1 byte to al*/
in al,0x71 /* Fetch 1 byte to al*/
sti /* Enable interrupts*/
sti /* Enable interrupts*/
Line 35: Line 186:
}
}
</pre>
</pre>

=== Writing to the CMOS ===
=== Writing to the CMOS ===
<pre>
<pre>
Line 46: Line 198:
_asm
_asm
{
{
xor ax,ax /* zero register*/
cli /* Clear interrupts*/
cli /* Clear interrupts*/
mov al,index /* move index address*/
mov al,index /* move index address*/
mov ah,tvalue /* copy value to ah*/
out 0x70,al /* copy address to CMOS register*/
out 0x70,al /* copy address to CMOS register*/
/* some kind of real delay here is probably best */
nop
mov al,tvalue /* move value to al*/
nop
nop /* Wait a bit */
mov al,ah /* move value to al*/
out 0x71,al /* write 1 byte to CMOS*/
out 0x71,al /* write 1 byte to CMOS*/
sti /* Enable interrupts*/
sti /* Enable interrupts*/
Line 61: Line 209:
}
}
</pre>
</pre>

== See Also ==
== See Also ==
[http://ivs.cs.uni-magdeburg.de/~zbrog/asm/cmos.html CMOS Memory Map]
[http://ivs.cs.uni-magdeburg.de/~zbrog/asm/cmos.html Old CMOS Map]


[[Category:X86]]
[[Category:X86]]

Revision as of 03:04, 30 January 2010

"CMOS" is a tiny bit of very low power static memory that lives on the same chip as the Real-Time Clock. It 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 -- because there is a separate battery that keeps the Clock and the CMOS information active.

CMOS values are accessed a byte at a time, and each byte is individually addressable. Each CMOS address is traditionally called a "register". The first 14 CMOS registers access and control the Real-Time Clock. In fact, the only truly useful registers remaining in CMOS are the Real-Time Clock registers, and register 0x10. All other registers in CMOS are almost entirely obsolete (or are not standardized), and are therefore useless.

Non-Maskable Interrupts

For frugality in the olden days, many functions were merged together on chips where there was "room" -- even if they did not belong together. An example is putting the A20 address enable on the PS2 keyboard controller. In the same way, the "NMI disable" control was put together with the CMOS controller and the Real-Time Clock.

NMI is meant to communicate a "panic" status from the hardware to the CPU in a way that the CPU cannot ignore. It is typically used to signal memory errors. For more information about NMI, see the NMI article.

Whenever you send a byte to IO port 0x70, the high order bit tells the hardware whether to disable NMIs from reaching the CPU. If the bit is on, NMI is disabled (until the next time you send a byte to Port 0x70). The low order 7 bits of any byte sent to Port 0x70 are used to address CMOS registers.

CMOS Registers

The most updated CMOS register map, showing all of the conflicting register definitions between the various BIOSes, is in RBIL in the file called CMOS.LST.

Accessing CMOS Registers

Accessing CMOS is extremely simple, but you always need to take into account how you want to handle NMI. You "select" a CMOS register (for reading or writing) by sending the register number to IO Port 0x70. Since the 0x80 bit of Port 0x70 controls NMI, you always end up setting that, too. So your CMOS controller always needs to know whether your OS wants NMI to be enabled or not. Selecting a CMOS register is done as follows:

  • outb (0x70, (NMI_disable_bit << 7) | (selected CMOS register number));

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):

  • val_8bit = inb (0x71);

Note1: Reading or writing Port 0x71 seems to default the "selected register" back to 0xD. So you need to reselect the register every single time you want to access a CMOS register.

Note2: It is probably a good idea to have a reasonable delay after selecting a CMOS register on Port 0x70, before reading/writing the value on Port 0x71.

Checksums

The proper functioning of the BIOS during bootup depends on the values in CMOS. So the values are protected against random changes with checksums. It is very unwise ever to write a value into any of the CMOS registers (except for the RTC) -- because when you change a value you also have to go fix a BIOS-specific checksum in a different register -- or else the next boot will crash with an "invalid checksum" error. And since the checksum is located at a proprietary BIOS-specific register number, good luck finding it.

Register 0x10

This register contains the only CMOS value that an OS might ever find to be useful. It describes the "type" of each of the two floppy drives that may be attached to the system. The high nibble describes the "master" floppy drive on the primary bus, and the low nibble has an identical description for the "slave" floppy drive.

Value of each 4 bit nibble, and associated floppy drive type:

Value   Drive Type
 00h	no drive
 01h	360 KB 5.25 Drive
 02h	1.2 MB 5.25 Drive
 03h	720 KB 3.5 Drive
 04h	1.44 MB 3.5 Drive
 05h	2.88 MB 3.5 drive

Bits 0 to 3 = slave floppy type, bits 4 to 7 = master floppy type

Memory Size Registers

There are several CMOS registers that are standardized, and that seem to report useful information about the total memory on the system. However, each of them is lacking vital information that your OS will need. It is always better to use a BIOS function call to get information about memory than to use the information in CMOS. See Detecting Memory (x86).

(Register 0x16 (high byte) | Register 0x15 (low byte)) << 10 = 640K = size of low memory (without taking the EBDA into account.

(Register 0x18 (high byte) | Register 0x17 (low byte)) << 10 = total memory between 1M and 16M, or maybe 65M ... usually. But this number is extra untrustworthy when the system has more than 64M, it ignores "memory holes", it ignores memory mapped hardware, and it ignores memory reserved for important ACPI system tables.

(Register 0x35 (high byte) | Register 0x34 (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.

Hard Disk Registers

There are many CMOS registers in various locations, used by various ancient BIOSes, to store a "hard disk type" or other hard disk information. Any such information is strictly for use on obsolete CHS-based disk drives. 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

The RTC keeps track of the date and time, even when the computer's power is off. The only other way that a computer used to be able to do this was to ask a human for the date/time on every bootup. Now, if the computer has an internet connection, an OS has another (arguably better) way to get the same information.

The RTC also can generate clock ticks on IRQ8 (similarly to what the PIC does on IRQ0). The highest feasible clock frequency is 8KHz. Using the RTC clock this way may actually generate more stable clock pulses than the PIC can generate. It also frees up the PIC for timing events that really need near-microsecond accuracy. Additionally, the RTC can generate an IRQ8 at a particular time of day. See the RTC article for more detailed information about using RTC interrupts.

Getting Current Date and Time from RTC

To get each of the following date/time values from the RTC, select the associated "CMOS register" in the usual way, and read the value from Port 0x71.

Register   Contents
   0       Seconds
   2       Minutes
   4       Hours
   6       Weekday
   7       Day of Month
   8       Month
   9       Year
  0x32     Century
  0xb      Status Register B

Format of Bytes

There are 4 formats possible for the date/time RTC bytes:

  • Binary or BCD Mode
  • Hours in 12 hour format or 24 hour format

The format is controlled by Status Register B. On some CMOS/RTC chips, the format bits in Status Reg B cannot be changed. So your code needs to be able to handle all four possibilities, and it should not try to modify Status Register B's settings. So you always need to read Status Register B first, to find out what format your date/time bytes will arrive in.

  • Status Register B, Bit 1 (value = 2): Enables 24 hour format if set
  • Status Register B, Bit 2 (value = 4): Enables Binary mode if set

Binary mode is exactly what you would expect the value to be. If the time is 1:59:59 AM, then the value of hours would be 1, minutes would be 59 = 0x3b, and seconds would also be 59 = 0x3b.

In BCD mode, each of the two hex nibbles of the byte is modified to "display" a decimal number. So 1:59:59 has hours = 1, minutes = 0x59 = 89, seconds = 0x59 = 89. It is slightly annoying to convert BCD back into a "good" binary value.

24 hour time is exactly what you would expect. Hour 0 is midnight to 1am, hour 23 is 11pm.

12 hour time is annoying. If the hour is pm, then the 0x80 bit is set on the hour byte. So you need to mask that off. (This is true for both binary and BCD modes.) Then, midnight is 12, 1am is 1, etc. Note that carefully: midnight is not 0 -- it is 12 -- this needs to be handled in the calculation from 12 hour format to 24 hour format!

For the weekday format: Sunday = 1, Saturday = 7.

Examples

Reading from the CMOS

ReadFromCMOS (unsigned char array [])
{
   unsigned char tvalue, index;

   for(index = 0; index < 128; index++)
   {
      _asm
      {
         cli             /* Disable interrupts*/
         mov al, index   /* Move index address*/
         out 0x70,al     /* Copy address to CMOS register*/
         /* some kind of real delay here is probably best */
         in al,0x71      /* Fetch 1 byte to al*/
         sti             /* Enable interrupts*/
         mov tvalue,al
       }

       array[index] = tvalue;
   }
}

Writing to the CMOS

WriteTOCMOS(unsigned char array[])
{
   unsigned char index;

   for(index = 0; index < 128; index++)
   {
      unsigned char tvalue = array[index];
      _asm
      {
         cli             /* Clear interrupts*/
         mov al,index    /* move index address*/
         out 0x70,al     /* copy address to CMOS register*/
         /* some kind of real delay here is probably best */
         mov al,tvalue   /* move value to al*/
         out 0x71,al     /* write 1 byte to CMOS*/
         sti             /* Enable interrupts*/
      }
   }
}

See Also

Old CMOS Map