[PATCH v2 1/4] x86/time: CMOS RTC may run in binary mode

Jan Beulich posted 4 patches 1 month ago
[PATCH v2 1/4] x86/time: CMOS RTC may run in binary mode
Posted by Jan Beulich 1 month ago
Indicating it would always use BCD mode is just wrong (and then the
comment there said the opposite). All halfway recent (and really all 64-
bit capable) systems having a CMOS RTC should properly indicate the mode
in control register B.

Make use of the flag, but provide a fallback mechanism in case people run
into systems not matching the above assumption. Additionally, when binary
mode is indicated and when "cmos-rtc-probe" is in use (but "cmos-rtc-bcd"
isn't), probe whether the clock really runs in binary mode. (This probing,
sadly, can take up to 10 seconds.)

Signed-off-by: Jan Beulich <jbeulich@suse.com>
---
v2: New.

--- a/docs/misc/xen-command-line.pandoc
+++ b/docs/misc/xen-command-line.pandoc
@@ -339,6 +339,14 @@ parameter to "stable:socket".
 Specify the event count threshold for raising Corrected Machine Check
 Interrupts.  Specifying zero disables CMCI handling.
 
+### cmos-rtc-bcd (x86)
+> `= <boolean>`
+
+> Default: `false`
+
+Flag to indicate the CMOS Real Time Clock uses BCD mode irrespective of
+control register B indicating binary mode.
+
 ### cmos-rtc-probe (x86)
 > `= <boolean>`
 
--- a/xen/arch/x86/include/asm/mc146818rtc.h
+++ b/xen/arch/x86/include/asm/mc146818rtc.h
@@ -96,7 +96,6 @@ bool is_cmos_port(unsigned int port, uns
 
 #ifndef RTC_PORT
 #define RTC_PORT(x)	(0x70 + (x))
-#define RTC_ALWAYS_BCD	1	/* RTC operates in binary mode */
 #endif
 
 /*
--- a/xen/arch/x86/time.c
+++ b/xen/arch/x86/time.c
@@ -1250,6 +1250,9 @@ mktime (unsigned int year, unsigned int
         )*60 + sec; /* finally seconds */
 }
 
+static bool __ro_after_init opt_cmos_rtc_bcd;
+boolean_param("cmos-rtc-bcd", opt_cmos_rtc_bcd);
+
 struct rtc_time {
     unsigned int year, mon, day, hour, min, sec;
 };
@@ -1285,7 +1288,7 @@ static bool __get_cmos_time(struct rtc_t
     if ( acpi_gbl_FADT.century && acpi_gbl_FADT.century < 0x80 )
         century = CMOS_READ(acpi_gbl_FADT.century);
 
-    bcd = RTC_ALWAYS_BCD || !(CMOS_READ(RTC_CONTROL) & RTC_DM_BINARY);
+    bcd = opt_cmos_rtc_bcd || !(CMOS_READ(RTC_CONTROL) & RTC_DM_BINARY);
 
     spin_unlock_irqrestore(&rtc_lock, flags);
 
@@ -1353,6 +1356,48 @@ static bool __init cmos_rtc_probe(void)
     return false;
 }
 
+static inline bool __init attr_const is_bcd(unsigned int x)
+{
+    return (x & 0xf) < 10 && (x >> 4) < 10;
+}
+
+static void __init cmos_rtc_probe_bcd(void)
+{
+    bool bcd;
+    unsigned long flags;
+
+    if ( opt_cmos_rtc_bcd )
+        return;
+
+    spin_lock_irqsave(&rtc_lock, flags);
+    bcd = !(CMOS_READ(RTC_CONTROL) & RTC_DM_BINARY);
+    spin_unlock_irqrestore(&rtc_lock, flags);
+
+    if ( bcd )
+        return;
+
+    for ( unsigned int seclo = 0; ; )
+    {
+        struct rtc_time rtc;
+
+        if ( !__get_cmos_time(&rtc) ||
+             !is_bcd(rtc.sec) ||
+             !is_bcd(rtc.min) ||
+             !is_bcd(rtc.hour) ||
+             !is_bcd(rtc.day) ||
+             !is_bcd(rtc.mon) )
+            return;
+
+        if ( seclo > (rtc.sec & 0xf) )
+            break;
+
+        seclo = rtc.sec & 0xf;
+    }
+
+    printk(XENLOG_WARNING "CMOS RTC indicates binary mode but uses BCD\n");
+
+    opt_cmos_rtc_bcd = true;
+}
 
 static unsigned long cmos_rtc_read(void)
 {
@@ -1614,6 +1659,10 @@ static void __init probe_wallclock(void)
     if ( cmos_rtc_probe() )
     {
         wallclock_source = WALLCLOCK_CMOS;
+
+        if ( opt_cmos_rtc_probe )
+            cmos_rtc_probe_bcd();
+
         return;
     }
     if ( efi_enabled(EFI_RS) && efi_get_time() )