[PATCH v2 1/2] x86/time: split CMOS time fetching into wrapper

Roger Pau Monne posted 2 patches 2 months, 3 weeks ago
[PATCH v2 1/2] x86/time: split CMOS time fetching into wrapper
Posted by Roger Pau Monne 2 months, 3 weeks ago
Split the logic that deals with probing and fetching the CMOS time into a
separate helper.  While moving the code also take the opportunity to reduce the
scope of some local variables.

No functional change intended.

Signed-off-by: Roger Pau Monné <roger.pau@citrix.com>
---
Changes since v1:
 - New in this version.
---
 xen/arch/x86/time.c | 72 +++++++++++++++++++++++++--------------------
 1 file changed, 40 insertions(+), 32 deletions(-)

diff --git a/xen/arch/x86/time.c b/xen/arch/x86/time.c
index a97d78484105..272ca2468ea6 100644
--- a/xen/arch/x86/time.c
+++ b/xen/arch/x86/time.c
@@ -1245,29 +1245,14 @@ static void __get_cmos_time(struct rtc_time *rtc)
         rtc->year += 100;
 }
 
-static unsigned long get_cmos_time(void)
+/* Returns true when fetching time from CMOS is successful. */
+static bool read_cmos_time(struct rtc_time *rtc, bool cmos_rtc_probe)
 {
-    unsigned long res, flags;
-    struct rtc_time rtc;
     unsigned int seconds = 60;
-    static bool __read_mostly cmos_rtc_probe;
-    boolean_param("cmos-rtc-probe", cmos_rtc_probe);
-
-    if ( efi_enabled(EFI_RS) )
-    {
-        res = efi_get_time();
-        if ( res )
-            return res;
-    }
-
-    if ( likely(!(acpi_gbl_FADT.boot_flags & ACPI_FADT_NO_CMOS_RTC)) )
-        cmos_rtc_probe = false;
-    else if ( system_state < SYS_STATE_smp_boot && !cmos_rtc_probe )
-        panic("System with no CMOS RTC advertised must be booted from EFI"
-              " (or with command line option \"cmos-rtc-probe\")\n");
 
     for ( ; ; )
     {
+        unsigned long flags;
         s_time_t start, t1, t2;
 
         spin_lock_irqsave(&rtc_lock, flags);
@@ -1285,33 +1270,56 @@ static unsigned long get_cmos_time(void)
         } while ( (CMOS_READ(RTC_FREQ_SELECT) & RTC_UIP) &&
                   t2 < MILLISECS(3) );
 
-        __get_cmos_time(&rtc);
+        __get_cmos_time(rtc);
 
         spin_unlock_irqrestore(&rtc_lock, flags);
 
-        if ( likely(!cmos_rtc_probe) ||
-             t1 > SECONDS(1) || t2 >= MILLISECS(3) ||
-             rtc.sec >= 60 || rtc.min >= 60 || rtc.hour >= 24 ||
-             !rtc.day || rtc.day > 31 ||
-             !rtc.mon || rtc.mon > 12 )
-            break;
+        if ( likely(!cmos_rtc_probe) )
+            return true;
+
+        if ( t1 > SECONDS(1) || t2 >= MILLISECS(3) ||
+             rtc->sec >= 60 || rtc->min >= 60 || rtc->hour >= 24 ||
+             !rtc->day || rtc->day > 31 ||
+             !rtc->mon || rtc->mon > 12 )
+            return false;
 
         if ( seconds < 60 )
         {
-            if ( rtc.sec != seconds )
-            {
-                cmos_rtc_probe = false;
+            if ( rtc->sec != seconds )
                 acpi_gbl_FADT.boot_flags &= ~ACPI_FADT_NO_CMOS_RTC;
-            }
-            break;
+            return true;
         }
 
         process_pending_softirqs();
 
-        seconds = rtc.sec;
+        seconds = rtc->sec;
     }
 
-    if ( unlikely(cmos_rtc_probe) )
+    ASSERT_UNREACHABLE();
+    return false;
+}
+
+static unsigned long get_cmos_time(void)
+{
+    struct rtc_time rtc;
+    static bool __read_mostly cmos_rtc_probe;
+    boolean_param("cmos-rtc-probe", cmos_rtc_probe);
+
+    if ( efi_enabled(EFI_RS) )
+    {
+        unsigned long res = efi_get_time();
+
+        if ( res )
+            return res;
+    }
+
+    if ( likely(!(acpi_gbl_FADT.boot_flags & ACPI_FADT_NO_CMOS_RTC)) )
+        cmos_rtc_probe = false;
+    else if ( system_state < SYS_STATE_smp_boot && !cmos_rtc_probe )
+        panic("System with no CMOS RTC advertised must be booted from EFI"
+              " (or with command line option \"cmos-rtc-probe\")\n");
+
+    if ( unlikely(!read_cmos_time(&rtc, cmos_rtc_probe)) )
         panic("No CMOS RTC found - system must be booted from EFI\n");
 
     return mktime(rtc.year, rtc.mon, rtc.day, rtc.hour, rtc.min, rtc.sec);
-- 
2.46.0


Re: [PATCH v2 1/2] x86/time: split CMOS time fetching into wrapper
Posted by Jan Beulich 2 months, 2 weeks ago
On 30.08.2024 11:52, Roger Pau Monne wrote:
> @@ -1285,33 +1270,56 @@ static unsigned long get_cmos_time(void)
>          } while ( (CMOS_READ(RTC_FREQ_SELECT) & RTC_UIP) &&
>                    t2 < MILLISECS(3) );
>  
> -        __get_cmos_time(&rtc);
> +        __get_cmos_time(rtc);
>  
>          spin_unlock_irqrestore(&rtc_lock, flags);
>  
> -        if ( likely(!cmos_rtc_probe) ||
> -             t1 > SECONDS(1) || t2 >= MILLISECS(3) ||
> -             rtc.sec >= 60 || rtc.min >= 60 || rtc.hour >= 24 ||
> -             !rtc.day || rtc.day > 31 ||
> -             !rtc.mon || rtc.mon > 12 )
> -            break;
> +        if ( likely(!cmos_rtc_probe) )
> +            return true;
> +
> +        if ( t1 > SECONDS(1) || t2 >= MILLISECS(3) ||
> +             rtc->sec >= 60 || rtc->min >= 60 || rtc->hour >= 24 ||
> +             !rtc->day || rtc->day > 31 ||
> +             !rtc->mon || rtc->mon > 12 )
> +            return false;
>  
>          if ( seconds < 60 )
>          {
> -            if ( rtc.sec != seconds )
> -            {
> -                cmos_rtc_probe = false;

This clearing of the variable is lost, which looks wrong to me.

Jan

> +            if ( rtc->sec != seconds )
>                  acpi_gbl_FADT.boot_flags &= ~ACPI_FADT_NO_CMOS_RTC;
> -            }
> -            break;
> +            return true;
>          }
>  
>          process_pending_softirqs();
>  
> -        seconds = rtc.sec;
> +        seconds = rtc->sec;
>      }
>  
> -    if ( unlikely(cmos_rtc_probe) )
> +    ASSERT_UNREACHABLE();
> +    return false;
> +}
> +
> +static unsigned long get_cmos_time(void)
> +{
> +    struct rtc_time rtc;
> +    static bool __read_mostly cmos_rtc_probe;
> +    boolean_param("cmos-rtc-probe", cmos_rtc_probe);
> +
> +    if ( efi_enabled(EFI_RS) )
> +    {
> +        unsigned long res = efi_get_time();
> +
> +        if ( res )
> +            return res;
> +    }
> +
> +    if ( likely(!(acpi_gbl_FADT.boot_flags & ACPI_FADT_NO_CMOS_RTC)) )
> +        cmos_rtc_probe = false;
> +    else if ( system_state < SYS_STATE_smp_boot && !cmos_rtc_probe )
> +        panic("System with no CMOS RTC advertised must be booted from EFI"
> +              " (or with command line option \"cmos-rtc-probe\")\n");
> +
> +    if ( unlikely(!read_cmos_time(&rtc, cmos_rtc_probe)) )
>          panic("No CMOS RTC found - system must be booted from EFI\n");
>  
>      return mktime(rtc.year, rtc.mon, rtc.day, rtc.hour, rtc.min, rtc.sec);
Re: [PATCH v2 1/2] x86/time: split CMOS time fetching into wrapper
Posted by Roger Pau Monné 2 months, 2 weeks ago
On Tue, Sep 03, 2024 at 08:24:18AM +0200, Jan Beulich wrote:
> On 30.08.2024 11:52, Roger Pau Monne wrote:
> > @@ -1285,33 +1270,56 @@ static unsigned long get_cmos_time(void)
> >          } while ( (CMOS_READ(RTC_FREQ_SELECT) & RTC_UIP) &&
> >                    t2 < MILLISECS(3) );
> >  
> > -        __get_cmos_time(&rtc);
> > +        __get_cmos_time(rtc);
> >  
> >          spin_unlock_irqrestore(&rtc_lock, flags);
> >  
> > -        if ( likely(!cmos_rtc_probe) ||
> > -             t1 > SECONDS(1) || t2 >= MILLISECS(3) ||
> > -             rtc.sec >= 60 || rtc.min >= 60 || rtc.hour >= 24 ||
> > -             !rtc.day || rtc.day > 31 ||
> > -             !rtc.mon || rtc.mon > 12 )
> > -            break;
> > +        if ( likely(!cmos_rtc_probe) )
> > +            return true;
> > +
> > +        if ( t1 > SECONDS(1) || t2 >= MILLISECS(3) ||
> > +             rtc->sec >= 60 || rtc->min >= 60 || rtc->hour >= 24 ||
> > +             !rtc->day || rtc->day > 31 ||
> > +             !rtc->mon || rtc->mon > 12 )
> > +            return false;
> >  
> >          if ( seconds < 60 )
> >          {
> > -            if ( rtc.sec != seconds )
> > -            {
> > -                cmos_rtc_probe = false;
> 
> This clearing of the variable is lost, which looks wrong to me.

Note the code in get_cmos_time() is modified, so the variable is no
longer used past the call to read_cmos_time().  Instead the signaling
of whether the CMOS is functional or not is done using the return
value of the newly introduced read_cmos_time() function.

Thanks, Roger.
Re: [PATCH v2 1/2] x86/time: split CMOS time fetching into wrapper
Posted by Jan Beulich 2 months, 2 weeks ago
On 03.09.2024 09:35, Roger Pau Monné wrote:
> On Tue, Sep 03, 2024 at 08:24:18AM +0200, Jan Beulich wrote:
>> On 30.08.2024 11:52, Roger Pau Monne wrote:
>>> @@ -1285,33 +1270,56 @@ static unsigned long get_cmos_time(void)
>>>          } while ( (CMOS_READ(RTC_FREQ_SELECT) & RTC_UIP) &&
>>>                    t2 < MILLISECS(3) );
>>>  
>>> -        __get_cmos_time(&rtc);
>>> +        __get_cmos_time(rtc);
>>>  
>>>          spin_unlock_irqrestore(&rtc_lock, flags);
>>>  
>>> -        if ( likely(!cmos_rtc_probe) ||
>>> -             t1 > SECONDS(1) || t2 >= MILLISECS(3) ||
>>> -             rtc.sec >= 60 || rtc.min >= 60 || rtc.hour >= 24 ||
>>> -             !rtc.day || rtc.day > 31 ||
>>> -             !rtc.mon || rtc.mon > 12 )
>>> -            break;
>>> +        if ( likely(!cmos_rtc_probe) )
>>> +            return true;
>>> +
>>> +        if ( t1 > SECONDS(1) || t2 >= MILLISECS(3) ||
>>> +             rtc->sec >= 60 || rtc->min >= 60 || rtc->hour >= 24 ||
>>> +             !rtc->day || rtc->day > 31 ||
>>> +             !rtc->mon || rtc->mon > 12 )
>>> +            return false;
>>>  
>>>          if ( seconds < 60 )
>>>          {
>>> -            if ( rtc.sec != seconds )
>>> -            {
>>> -                cmos_rtc_probe = false;
>>
>> This clearing of the variable is lost, which looks wrong to me.
> 
> Note the code in get_cmos_time() is modified, so the variable is no
> longer used past the call to read_cmos_time().  Instead the signaling
> of whether the CMOS is functional or not is done using the return
> value of the newly introduced read_cmos_time() function.

I wasn't concerned of the further processing on the 1st invocation, but
of the behavior of the 2nd invocation. But yes, there the flag will end
up being cleared because of the FADT flag also having been cleared. Not
easily visible, though. Could minimally do with a remark in the
description.

Jan

Re: [PATCH v2 1/2] x86/time: split CMOS time fetching into wrapper
Posted by Roger Pau Monné 2 months, 2 weeks ago
On Tue, Sep 03, 2024 at 09:53:47AM +0200, Jan Beulich wrote:
> On 03.09.2024 09:35, Roger Pau Monné wrote:
> > On Tue, Sep 03, 2024 at 08:24:18AM +0200, Jan Beulich wrote:
> >> On 30.08.2024 11:52, Roger Pau Monne wrote:
> >>> @@ -1285,33 +1270,56 @@ static unsigned long get_cmos_time(void)
> >>>          } while ( (CMOS_READ(RTC_FREQ_SELECT) & RTC_UIP) &&
> >>>                    t2 < MILLISECS(3) );
> >>>  
> >>> -        __get_cmos_time(&rtc);
> >>> +        __get_cmos_time(rtc);
> >>>  
> >>>          spin_unlock_irqrestore(&rtc_lock, flags);
> >>>  
> >>> -        if ( likely(!cmos_rtc_probe) ||
> >>> -             t1 > SECONDS(1) || t2 >= MILLISECS(3) ||
> >>> -             rtc.sec >= 60 || rtc.min >= 60 || rtc.hour >= 24 ||
> >>> -             !rtc.day || rtc.day > 31 ||
> >>> -             !rtc.mon || rtc.mon > 12 )
> >>> -            break;
> >>> +        if ( likely(!cmos_rtc_probe) )
> >>> +            return true;
> >>> +
> >>> +        if ( t1 > SECONDS(1) || t2 >= MILLISECS(3) ||
> >>> +             rtc->sec >= 60 || rtc->min >= 60 || rtc->hour >= 24 ||
> >>> +             !rtc->day || rtc->day > 31 ||
> >>> +             !rtc->mon || rtc->mon > 12 )
> >>> +            return false;
> >>>  
> >>>          if ( seconds < 60 )
> >>>          {
> >>> -            if ( rtc.sec != seconds )
> >>> -            {
> >>> -                cmos_rtc_probe = false;
> >>
> >> This clearing of the variable is lost, which looks wrong to me.
> > 
> > Note the code in get_cmos_time() is modified, so the variable is no
> > longer used past the call to read_cmos_time().  Instead the signaling
> > of whether the CMOS is functional or not is done using the return
> > value of the newly introduced read_cmos_time() function.
> 
> I wasn't concerned of the further processing on the 1st invocation, but
> of the behavior of the 2nd invocation. But yes, there the flag will end
> up being cleared because of the FADT flag also having been cleared. Not
> easily visible, though. Could minimally do with a remark in the
> description.

Indeed, the variable gets clearer on further invocations, as the
adjustment to ACPI_FADT_NO_CMOS_RTC gets propagated.

Given Andrew comments, I've reworked all of this and I think it ended
up being clearer.

Thanks, Roger.

Re: [PATCH v2 1/2] x86/time: split CMOS time fetching into wrapper
Posted by Andrew Cooper 2 months, 3 weeks ago
On 30/08/2024 10:52 am, Roger Pau Monne wrote:
> Split the logic that deals with probing and fetching the CMOS time into a
> separate helper.  While moving the code also take the opportunity to reduce the
> scope of some local variables.
>
> No functional change intended.
>
> Signed-off-by: Roger Pau Monné <roger.pau@citrix.com>

This does look like a straight rearrangement, so

Acked-by: Andrew Cooper <andrew.cooper3@citrix.com>, although...

> +static unsigned long get_cmos_time(void)
> +{
> +    struct rtc_time rtc;
> +    static bool __read_mostly cmos_rtc_probe;
> +    boolean_param("cmos-rtc-probe", cmos_rtc_probe);
> +
> +    if ( efi_enabled(EFI_RS) )
> +    {
> +        unsigned long res = efi_get_time();
> +
> +        if ( res )
> +            return res;
> +    }
> +
> +    if ( likely(!(acpi_gbl_FADT.boot_flags & ACPI_FADT_NO_CMOS_RTC)) )
> +        cmos_rtc_probe = false;
> +    else if ( system_state < SYS_STATE_smp_boot && !cmos_rtc_probe )
> +        panic("System with no CMOS RTC advertised must be booted from EFI"
> +              " (or with command line option \"cmos-rtc-probe\")\n");
> +
> +    if ( unlikely(!read_cmos_time(&rtc, cmos_rtc_probe)) )
>          panic("No CMOS RTC found - system must be booted from EFI\n");

... this really does show some (preexisting) tangled logic.

All of this should live in an init function, and not be re-evaluated
each time we call get_wallclock_time(), not that we call it very often.

cmos_rtc_probe should be __initdata or at least __ro_after_init, but it
gets written on every pass through the function on most systems.

Lets focus on not crashing.  Cleanup can come later.

~Andrew