Real Time Clock (RTC/CMOS)
The Real Time Clock (RTC), often called CMOS after the low-power memory technology its register bank shares a chip with, is the piece of hardware that keeps calendar date and time across a reboot or a full power cycle, battery-backed specifically so it keeps running when everything else on the board is off. None of the counters Timers already covers, the PIT, the Local APIC timer, the TSC, or HPET, solve this problem: every one of them measures elapsed time from an arbitrary starting point that resets to zero on every boot, while the RTC is specifically what a kernel reads once to learn what the actual wall-clock date and time are right now.
Registers and access
Section titled “Registers and access”The RTC’s registers are reached indirectly through a pair of I/O ports rather than being individually addressed: 0x70 is the index port, selecting which register the next access refers to, and 0x71 is the data port, through which that selected register is actually read or written.
mov al, 0x00 ; select register 0 (seconds)out 0x70, alin al, 0x71 ; AL now holds the seconds valueRegisters 0 through 9 hold the actual time and date fields, seconds, minutes, hours, day of week, day of month, month, and a two-digit year, each in its own byte; a full date read has to read all of them in the same pass rather than one at a time spread across separate calls, since the clock keeps advancing between reads and a seconds value read before midnight paired with a date read after it would silently produce a wrong, self-inconsistent result.
BCD versus binary
Section titled “BCD versus binary”By default, and for backward compatibility with decades of software written against it, the RTC’s time and date registers are encoded in binary-coded decimal: each byte holds two decimal digits, one packed into each nibble, rather than the value’s ordinary binary representation, so the seconds value 42 is stored as the byte 0x42, not 0x2A. Status Register B, at index 0x0B, controls this directly: bit 2 clear (the default) means BCD, bit 2 set means the registers instead hold plain binary values, a mode some firmware supports and some does not, which is why a driver reading a clear bit 2 has to convert explicitly rather than assuming the value it read is already usable as an ordinary integer.
uint8_t bcd_to_binary(uint8_t bcd) { return (bcd & 0x0F) + ((bcd >> 4) * 10);}Register B also carries the 12-hour/24-hour selection (bit 1) and, when 12-hour mode is active, packs the AM/PM indicator into the hour byte’s own top bit rather than as a separate field, a detail easy to miss and one that produces an hour value that looks plausible but is silently wrong by twelve if a driver assumes 24-hour format without actually checking.
The update-in-progress race
Section titled “The update-in-progress race”The RTC’s registers are refreshed from its internal counter roughly once per second, and reading a register in the middle of that refresh can return a transient, inconsistent value, neither the time before the update nor cleanly the time after it. Status Register A, at index 0x0A, exposes an Update In Progress flag in bit 7 specifically so software can detect this window and avoid reading through it: a driver polls this bit and only proceeds with a read once it’s clear, or, more robustly, reads the full date twice in a row and discards the result if the two reads disagree, since a single clear-bit check still leaves a narrow race between the check and the actual read.
void wait_for_update_complete(void) { outb(0x70, 0x0A); while (inb(0x71) & 0x80) { outb(0x70, 0x0A); }}Reading once versus keeping a running clock
Section titled “Reading once versus keeping a running clock”A kernel typically reads the RTC exactly once, early in boot, to establish an initial wall-clock time, and from that point on advances its own software clock using whatever timer it already relies on for scheduling, rather than repeatedly polling the RTC itself for every subsequent time query. This split exists because the RTC’s one-second resolution and port-I/O access pattern are both too coarse and too slow for frequent queries, while a timer already ticking at a much finer granularity for scheduling purposes can track sub-second elapsed time far more cheaply once it has a real starting point to count forward from.
Implementation notes
Section titled “Implementation notes”Some systems additionally expose calendar time through the ACPI FADT table’s RTC-related fields (its century register location, for instance, since the RTC’s own two-digit year needs a century value from somewhere to become a full four-digit one), which a kernel already parsing ACPI tables for other reasons can read rather than guessing the century register’s port-space location, since it isn’t fixed identically across all chipsets the way the primary time and date registers are. Register C, at index 0x0C, has to be read (and is cleared as a side effect of that read) after any RTC-generated interrupt a kernel has enabled, since the RTC otherwise stops raising further interrupts of that type until the pending status in Register C is acknowledged this way.
References
Section titled “References”- ^ Motorola, MC146818A Real-Time Clock Plus RAM datasheet: the original RTC chip specification defining the register layout most PC-compatible implementations still follow.