Reading the AT-Compatible CMOS RTC Without a Torn Timestamp
Read CMOS RTC registers coherently by respecting update-in-progress, SET and format bits, NMI selection, and the limits of legacy hardware access.
The real-time clock in an AT-compatible PC is not the BIOS tick counter. It is a battery-backed calendar device accessed through an index/data pair, traditionally ports 70h and 71h. A program that reads seconds, minutes, date, and month while the clock updates can assemble fields from two different instants. Direct reads also interact with the device’s update state, register format, and non-maskable-interrupt control. On a normal DOS application, BIOS time services are usually the safer interface; direct CMOS access belongs in diagnostics and hardware-specific utilities.
The Motorola MC146818 family describes the classic RTC/CMOS register model; the IBM PC/AT technical reference documents how that model was integrated into the machine. Clone chipsets and emulators may differ. Treat the port-level recipe below as a PC/AT-compatible target, not as a guarantee for arbitrary laptops, UEFI-only machines, or modern USB-attached systems.
Understand the index/data transaction
Software selects a CMOS register by writing its index to port 70h, then reads or writes the selected register through port 71h. On the PC/AT design, the high bit of the index port controls the NMI mask while the low bits select the CMOS register. A routine must preserve the prior NMI-mask state and avoid leaving the selection register in an unexpected state. Blindly writing an index value can alter NMI handling for the rest of the system.
The index and data access form a shared, stateful transaction. If an interrupt handler or another TSR also accesses CMOS between the index write and data access, the selected register can change. DOS generally has no global lock for arbitrary port I/O. A tiny critical section may be necessary in a controlled environment, but disabling interrupts has latency and correctness costs and does not coordinate with another CPU or external firmware. The safest choice is often not to compete for direct access at all.
Use an interface that preserves the existing NMI control bit and restores expected index state according to the hardware and application contract. Do not copy a port-I/O example from an unrelated chipset and assume it is portable. In particular, do not turn off NMI for a whole RTC read loop simply because it makes the code appear deterministic.
Update-in-progress and coherent snapshots
Register A includes the update-in-progress (UIP) status bit. While the RTC is transferring updated time and calendar fields, reads can observe a mixture of old and new values. A conservative reader waits until UIP is clear, reads the fields, then checks that the timestamp is stable; if a rollover may have occurred, it retries. A practical strategy reads seconds and calendar fields twice and accepts only a consistent snapshot, with a bounded retry count and timeout.
Do not busy-wait forever. A stopped oscillator, broken emulator, or absent device could leave the expected status transition unavailable. Bound the wait by a monotonic time source available to the program, or by a finite iteration budget with a clear error result. A timeout is an operational failure to report, not permission to return partially updated fields as valid.
The SET bit in register B controls whether updates are inhibited while software sets the clock. A reader should observe the documented state and avoid interpreting fields during a software-controlled update. Register B also selects binary versus BCD representation and 12-hour versus 24-hour mode. A parser that assumes binary 24-hour values can display plausible but wrong times on a machine configured for BCD or 12-hour mode.
Convert BCD only when the mode says the values are BCD. Validate every digit nibble before conversion; malformed values should produce an error rather than a nonsensical calendar date. In 12-hour mode, account for the PM indicator bit in the hour register. Do not treat a raw hour byte as a decimal integer until the encoding and mode bits are known.
Century and calendar assumptions
The classic RTC register map does not provide one universal, standardized century register across all PC-compatible BIOSes. Vendors have used CMOS locations beyond the basic clock fields, but their location and semantics are platform-specific. Do not derive a century from an undocumented offset or assume that every BIOS uses the same rule. Use DOS/BIOS date services for ordinary applications, and make century handling an explicit platform-specific policy in a diagnostic or setup tool.
Validate calendar values, including leap-year and month-length boundaries, after decoding. The RTC chip’s calendar behavior and the BIOS’s interpretation are related but not identical to application policy. If a clock reports an impossible date, report the raw fields and decoded mode rather than silently normalizing them into a different timestamp.
Prefer the BIOS for normal software
The BIOS exposes time and date services through INT 1Ah, and DOS provides its own application-level time/date functions. Those interfaces avoid direct ownership of the CMOS index port and give firmware or DOS a chance to handle platform-specific conversion. A utility should reach for port I/O only when it needs to diagnose the CMOS device or manage settings that have no suitable software API.
Never use RTC reads as a high-resolution timer. Calendar registers are for wall-clock time and can be adjusted; the BIOS periodic tick is a different mechanism. For elapsed intervals, use a monotonic timer appropriate to the machine and account for wrap and emulation. Mixing these interfaces creates subtle date/time and rollover errors.
A coherent-read loop can be expressed without exposing raw I/O details:
repeat up to a fixed retry limit:
wait for UIP clear, failing on timeout
read mode/status and all required calendar fields
read seconds and UIP state again
if UIP remains clear and a second snapshot matches: accept
return an unstable-snapshot error
The second comparison is deliberate. Seeing UIP clear before the first field does not freeze the clock; an update can begin during the multi-register read. A bounded stable-snapshot check handles that rollover race without asking the program to mask NMI or disable all interrupts for an unnecessarily long interval. If the target chipset documents a stronger latch mechanism, prefer that documented mechanism.
When returning a timestamp to a caller, preserve both the decoded result and metadata describing the observed register-B mode. That prevents later code from accidentally decoding a cached BCD value as binary after a configuration change. Include an explicit validity flag; a value such as 00:00:00 can be a valid instant, so it must not double as an error sentinel.
Safe diagnostic procedure
Before reading, identify the target as a PC/AT-compatible real-mode environment and check whether an existing DOS driver or TSR owns RTC access. Read the relevant mode/status registers without writing them, preserve NMI selection state, wait for UIP to clear with a timeout, capture the calendar fields, and verify stability. Log both raw bytes and decoded values. If the two snapshots disagree, retry within a bound and then report “unstable RTC snapshot.”
Do not change the time, alarm, or periodic-interrupt registers during a read-only diagnostic. Writes can affect platform state and interrupt behavior. If a setup utility intentionally writes clock fields, follow the chipset/BIOS documentation for setting the update-inhibit bit, writing all fields in the configured representation, and clearing the bit. That operation should be separate from observation and should have a recovery path.
Test around second, minute, day, month, and year rollover under an emulator configured for the exact machine model. Include both BCD and binary modes if the target supports them, as well as 12-hour and 24-hour modes. Test timeout handling with a mocked or intentionally unavailable status source; do not risk vintage hardware merely to induce a fault.
The conservative contract
Direct CMOS access is a low-level, shared hardware operation. Correctness requires more than reading a list of register numbers: preserve the NMI bit, serialize the index/data pair as far as the environment allows, respect UIP and SET, decode BCD and hour format correctly, and reject invalid data. For ordinary FreeDOS software, call the operating system or BIOS. For hardware diagnostics, state the compatibility target and report uncertainty instead of pretending every PC has the same RTC implementation.
Related:
- DOS BIOS Clock INT 1Ah: Tick Counts, Midnight Rollover, and Date Safety
- Fixing Incorrect Date and Time on FreeDOS
Sources: