diff mbox series

[v2,2/2] x86/time: prefer CMOS over EFI_GET_TIME

Message ID 20240830095220.49473-3-roger.pau@citrix.com (mailing list archive)
State New
Headers show
Series x86/time: prefer CMOS over EFI_GET_TIME | expand

Commit Message

Roger Pau Monné Aug. 30, 2024, 9:52 a.m. UTC
The EFI_GET_TIME implementation is well known to be broken for many firmware
implementations, for Xen the result on such implementations are:

----[ Xen-4.19-unstable  x86_64  debug=y  Tainted:   C    ]----
CPU:    0
RIP:    e008:[<0000000062ccfa70>] 0000000062ccfa70
[...]
Xen call trace:
   [<0000000062ccfa70>] R 0000000062ccfa70
   [<00000000732e9a3f>] S 00000000732e9a3f
   [<ffff82d04034f34f>] F arch/x86/time.c#get_cmos_time+0x1b3/0x26e
   [<ffff82d04045926f>] F init_xen_time+0x28/0xa4
   [<ffff82d040454bc4>] F __start_xen+0x1ee7/0x2578
   [<ffff82d040203334>] F __high_start+0x94/0xa0

Pagetable walk from 0000000062ccfa70:
 L4[0x000] = 000000207ef1c063 ffffffffffffffff
 L3[0x001] = 000000005d6c0063 ffffffffffffffff
 L2[0x116] = 8000000062c001e3 ffffffffffffffff (PSE)

****************************************
Panic on CPU 0:
FATAL PAGE FAULT
[error_code=0011]
Faulting linear address: 0000000062ccfa70
****************************************

Swap the preference to default to CMOS first, and EFI later, in an attempt to
use EFI_GET_TIME as a last resort option only.  Note that Linux for example
doesn't allow calling the get_time method, and instead provides a dummy handler
that unconditionally returns EFI_UNSUPPORTED on x86-64.

Such change in the preferences requires some re-arranging of the function
logic, so that panic messages with workaround suggestions are suitably printed.

Signed-off-by: Roger Pau Monné <roger.pau@citrix.com>
---
 xen/arch/x86/time.c | 26 +++++++++++++++++++-------
 1 file changed, 19 insertions(+), 7 deletions(-)

Comments

Andrew Cooper Aug. 30, 2024, 4:31 p.m. UTC | #1
On 30/08/2024 10:52 am, Roger Pau Monne wrote:
> The EFI_GET_TIME implementation is well known to be broken for many firmware
> implementations, for Xen the result on such implementations are:
>
> ----[ Xen-4.19-unstable  x86_64  debug=y  Tainted:   C    ]----
> CPU:    0
> RIP:    e008:[<0000000062ccfa70>] 0000000062ccfa70
> [...]
> Xen call trace:
>    [<0000000062ccfa70>] R 0000000062ccfa70
>    [<00000000732e9a3f>] S 00000000732e9a3f
>    [<ffff82d04034f34f>] F arch/x86/time.c#get_cmos_time+0x1b3/0x26e
>    [<ffff82d04045926f>] F init_xen_time+0x28/0xa4
>    [<ffff82d040454bc4>] F __start_xen+0x1ee7/0x2578
>    [<ffff82d040203334>] F __high_start+0x94/0xa0
>
> Pagetable walk from 0000000062ccfa70:
>  L4[0x000] = 000000207ef1c063 ffffffffffffffff
>  L3[0x001] = 000000005d6c0063 ffffffffffffffff
>  L2[0x116] = 8000000062c001e3 ffffffffffffffff (PSE)
>
> ****************************************
> Panic on CPU 0:
> FATAL PAGE FAULT
> [error_code=0011]
> Faulting linear address: 0000000062ccfa70
> ****************************************
>
> Swap the preference to default to CMOS first, and EFI later, in an attempt to
> use EFI_GET_TIME as a last resort option only.  Note that Linux for example
> doesn't allow calling the get_time method, and instead provides a dummy handler
> that unconditionally returns EFI_UNSUPPORTED on x86-64.
>
> Such change in the preferences requires some re-arranging of the function
> logic, so that panic messages with workaround suggestions are suitably printed.
>
> Signed-off-by: Roger Pau Monné <roger.pau@citrix.com>
> ---
>  xen/arch/x86/time.c | 26 +++++++++++++++++++-------
>  1 file changed, 19 insertions(+), 7 deletions(-)
>
> diff --git a/xen/arch/x86/time.c b/xen/arch/x86/time.c
> index 272ca2468ea6..0eee954809a9 100644
> --- a/xen/arch/x86/time.c
> +++ b/xen/arch/x86/time.c
> @@ -1305,24 +1305,36 @@ static unsigned long get_cmos_time(void)
>      static bool __read_mostly cmos_rtc_probe;
>      boolean_param("cmos-rtc-probe", cmos_rtc_probe);
>  
> +    if ( likely(!(acpi_gbl_FADT.boot_flags & ACPI_FADT_NO_CMOS_RTC)) )
> +        cmos_rtc_probe = false;
> +
> +    if ( (!(acpi_gbl_FADT.boot_flags & ACPI_FADT_NO_CMOS_RTC) ||
> +          cmos_rtc_probe) && read_cmos_time(&rtc, cmos_rtc_probe) )
> +        return mktime(rtc.year, rtc.mon, rtc.day, rtc.hour, rtc.min, rtc.sec);
> +
>      if ( efi_enabled(EFI_RS) )
>      {
>          unsigned long res = efi_get_time();
>  
>          if ( res )
>              return res;
> +
> +        panic("Broken EFI_GET_TIME %s\n",
> +              !cmos_rtc_probe ? "try booting with \"cmos-rtc-probe\" option"
> +                              : "and no CMOS RTC found");
>      }
>  
> -    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 )
> +    /*
> +     * No viable clock source found.  Attempt to provide some guidance to the
> +     * user about possible workarounds to boot Xen on the system.
> +     */
> +    ASSERT(system_state < SYS_STATE_smp_boot);
> +
> +    if ( !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);
> +    panic("No CMOS RTC found - system must be booted from EFI\n");
>  }
>  
>  static unsigned int __ro_after_init cmos_alias_mask;

Hmm, I know I said "fix the crash first, cleanup later" on the prior
patch, but I'm tempted to retract that.  This is very hard to follow.

Going back to first principles, at runtime we need USE_{XEN,RTC,EFI} to
select between the various methods available.

At boot, we need to figure out what to do.  I think we want an
init_wallclock_time() split out of get_cmos_time() (which is badly named
anyway, given EFI), and called from init_xen_time() only.  In
particular, the init stuff should not be re-evaluated in
time_{suspend,resume}().

Various details we have to work with:

 * ACPI_FADT_NO_CMOS_RTC, although we know this is falsely set on some
platforms, hence the whole probing logic.

 * Booted on an EFI system, although we know EFI_GET_TIME is broken on
millions of systems, and we only find this out with a #PF in the middle
of EFI-RS.  Furthermore, more-major-OSes-than-us strictly veto the use
of this service, and it's not only Linux; it's Windows too.

Personally, I think "cmos-rtc-probe" is inappropriate/insufficient.  It
ought to be wc-time=guess|xen|rtc|efi.  We should be able to influence
the behaviour more than a boolean "probe or not".  The Xen case might be
better as "hypervisor", although I can't see any evidence of Viridian
having a virtualised wallclock interface.

I think the init logic wants to be:
 * If ACPI says we have an RTC, use it.
 * If ACPI says we have no RTC, probe to see if it's really there.
 * If we genuinely seem to not have an RTC, probe EFI.  This would be
quite invasive in the #PF handler, but not impossible.


That said, I'm still holding out hope that we can simply delete Xen's
need for wallclock time.  Xen only uses wallclock time for the
non-default console_timestamps=date, but even then it's uncorrelate with
dom0 changing the platform time, leading to actively-misleading log
files.  There's a reason why "time since boot" is the norm.

~Andrew
Jan Beulich Sept. 2, 2024, 8:13 a.m. UTC | #2
On 30.08.2024 18:31, Andrew Cooper wrote:
> I think the init logic wants to be:
>  * If ACPI says we have an RTC, use it.
>  * If ACPI says we have no RTC, probe to see if it's really there.
>  * If we genuinely seem to not have an RTC, probe EFI.  This would be
> quite invasive in the #PF handler, but not impossible.

And would not necessarily be reliable: We can't exclude that paths taken
are (slightly) different for multiple invocations.

Jan
Roger Pau Monné Sept. 2, 2024, 8:45 a.m. UTC | #3
On Fri, Aug 30, 2024 at 05:31:04PM +0100, Andrew Cooper wrote:
> On 30/08/2024 10:52 am, Roger Pau Monne wrote:
> > The EFI_GET_TIME implementation is well known to be broken for many firmware
> > implementations, for Xen the result on such implementations are:
> >
> > ----[ Xen-4.19-unstable  x86_64  debug=y  Tainted:   C    ]----
> > CPU:    0
> > RIP:    e008:[<0000000062ccfa70>] 0000000062ccfa70
> > [...]
> > Xen call trace:
> >    [<0000000062ccfa70>] R 0000000062ccfa70
> >    [<00000000732e9a3f>] S 00000000732e9a3f
> >    [<ffff82d04034f34f>] F arch/x86/time.c#get_cmos_time+0x1b3/0x26e
> >    [<ffff82d04045926f>] F init_xen_time+0x28/0xa4
> >    [<ffff82d040454bc4>] F __start_xen+0x1ee7/0x2578
> >    [<ffff82d040203334>] F __high_start+0x94/0xa0
> >
> > Pagetable walk from 0000000062ccfa70:
> >  L4[0x000] = 000000207ef1c063 ffffffffffffffff
> >  L3[0x001] = 000000005d6c0063 ffffffffffffffff
> >  L2[0x116] = 8000000062c001e3 ffffffffffffffff (PSE)
> >
> > ****************************************
> > Panic on CPU 0:
> > FATAL PAGE FAULT
> > [error_code=0011]
> > Faulting linear address: 0000000062ccfa70
> > ****************************************
> >
> > Swap the preference to default to CMOS first, and EFI later, in an attempt to
> > use EFI_GET_TIME as a last resort option only.  Note that Linux for example
> > doesn't allow calling the get_time method, and instead provides a dummy handler
> > that unconditionally returns EFI_UNSUPPORTED on x86-64.
> >
> > Such change in the preferences requires some re-arranging of the function
> > logic, so that panic messages with workaround suggestions are suitably printed.
> >
> > Signed-off-by: Roger Pau Monné <roger.pau@citrix.com>
> > ---
> >  xen/arch/x86/time.c | 26 +++++++++++++++++++-------
> >  1 file changed, 19 insertions(+), 7 deletions(-)
> >
> > diff --git a/xen/arch/x86/time.c b/xen/arch/x86/time.c
> > index 272ca2468ea6..0eee954809a9 100644
> > --- a/xen/arch/x86/time.c
> > +++ b/xen/arch/x86/time.c
> > @@ -1305,24 +1305,36 @@ static unsigned long get_cmos_time(void)
> >      static bool __read_mostly cmos_rtc_probe;
> >      boolean_param("cmos-rtc-probe", cmos_rtc_probe);
> >  
> > +    if ( likely(!(acpi_gbl_FADT.boot_flags & ACPI_FADT_NO_CMOS_RTC)) )
> > +        cmos_rtc_probe = false;
> > +
> > +    if ( (!(acpi_gbl_FADT.boot_flags & ACPI_FADT_NO_CMOS_RTC) ||
> > +          cmos_rtc_probe) && read_cmos_time(&rtc, cmos_rtc_probe) )
> > +        return mktime(rtc.year, rtc.mon, rtc.day, rtc.hour, rtc.min, rtc.sec);
> > +
> >      if ( efi_enabled(EFI_RS) )
> >      {
> >          unsigned long res = efi_get_time();
> >  
> >          if ( res )
> >              return res;
> > +
> > +        panic("Broken EFI_GET_TIME %s\n",
> > +              !cmos_rtc_probe ? "try booting with \"cmos-rtc-probe\" option"
> > +                              : "and no CMOS RTC found");
> >      }
> >  
> > -    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 )
> > +    /*
> > +     * No viable clock source found.  Attempt to provide some guidance to the
> > +     * user about possible workarounds to boot Xen on the system.
> > +     */
> > +    ASSERT(system_state < SYS_STATE_smp_boot);
> > +
> > +    if ( !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);
> > +    panic("No CMOS RTC found - system must be booted from EFI\n");
> >  }
> >  
> >  static unsigned int __ro_after_init cmos_alias_mask;
> 
> Hmm, I know I said "fix the crash first, cleanup later" on the prior
> patch, but I'm tempted to retract that.  This is very hard to follow.

Yeah, I was attempting to refrain myself from fully re-writing the logic.

> Going back to first principles, at runtime we need USE_{XEN,RTC,EFI} to
> select between the various methods available.
> 
> At boot, we need to figure out what to do.  I think we want an
> init_wallclock_time() split out of get_cmos_time() (which is badly named
> anyway, given EFI), and called from init_xen_time() only.  In
> particular, the init stuff should not be re-evaluated in
> time_{suspend,resume}().

I also disliked the current arrangement, and adding a probe function
crossed my mind, I simply wanted to avoid this fix growing to a 10
patch series, but I think it's inevitable.

> Various details we have to work with:
> 
>  * ACPI_FADT_NO_CMOS_RTC, although we know this is falsely set on some
> platforms, hence the whole probing logic.
> 
>  * Booted on an EFI system, although we know EFI_GET_TIME is broken on
> millions of systems, and we only find this out with a #PF in the middle
> of EFI-RS.  Furthermore, more-major-OSes-than-us strictly veto the use
> of this service, and it's not only Linux; it's Windows too.
> 
> Personally, I think "cmos-rtc-probe" is inappropriate/insufficient.  It
> ought to be wc-time=guess|xen|rtc|efi.  We should be able to influence
> the behaviour more than a boolean "probe or not".  The Xen case might be
> better as "hypervisor", although I can't see any evidence of Viridian
> having a virtualised wallclock interface.
> 
> I think the init logic wants to be:
>  * If ACPI says we have an RTC, use it.
>  * If ACPI says we have no RTC, probe to see if it's really there.
>  * If we genuinely seem to not have an RTC, probe EFI.  This would be
> quite invasive in the #PF handler, but not impossible.

My plan would be to use EFI as a last resort if available, and hence
not probe it.  It would however be nice to give the user a better
error message than a #PF in the hypervisor with some random stack
trace.

Is #PF the only fault that we can expect from EFI_GET_TIME?  That's
what I've seen on some of the systems, but I wouldn't for example
discard #GP or #UD as also possible outcomes?

> 
> 
> That said, I'm still holding out hope that we can simply delete Xen's
> need for wallclock time.  Xen only uses wallclock time for the
> non-default console_timestamps=date, but even then it's uncorrelate with
> dom0 changing the platform time, leading to actively-misleading log
> files.  There's a reason why "time since boot" is the norm.

But there's a wallclock provided in the share_info page, do you
suggest that dom0 should pass the wallclock value to Xen, so Xen can
initialize the shared_info wallclock time?

That would leave the fields uninitialized for dom0 which would be an
ABI change.

Thanks, Roger.
Jan Beulich Sept. 2, 2024, 8:56 a.m. UTC | #4
On 02.09.2024 10:45, Roger Pau Monné wrote:
> Is #PF the only fault that we can expect from EFI_GET_TIME?  That's
> what I've seen on some of the systems, but I wouldn't for example
> discard #GP or #UD as also possible outcomes?

I've definitely seen #GP in stack traces. What I can't say for sure is
whether those were _only_ because of our originally wrong stack
alignment, when firmware code wants to use XMM registers.

Jan
diff mbox series

Patch

diff --git a/xen/arch/x86/time.c b/xen/arch/x86/time.c
index 272ca2468ea6..0eee954809a9 100644
--- a/xen/arch/x86/time.c
+++ b/xen/arch/x86/time.c
@@ -1305,24 +1305,36 @@  static unsigned long get_cmos_time(void)
     static bool __read_mostly cmos_rtc_probe;
     boolean_param("cmos-rtc-probe", cmos_rtc_probe);
 
+    if ( likely(!(acpi_gbl_FADT.boot_flags & ACPI_FADT_NO_CMOS_RTC)) )
+        cmos_rtc_probe = false;
+
+    if ( (!(acpi_gbl_FADT.boot_flags & ACPI_FADT_NO_CMOS_RTC) ||
+          cmos_rtc_probe) && read_cmos_time(&rtc, cmos_rtc_probe) )
+        return mktime(rtc.year, rtc.mon, rtc.day, rtc.hour, rtc.min, rtc.sec);
+
     if ( efi_enabled(EFI_RS) )
     {
         unsigned long res = efi_get_time();
 
         if ( res )
             return res;
+
+        panic("Broken EFI_GET_TIME %s\n",
+              !cmos_rtc_probe ? "try booting with \"cmos-rtc-probe\" option"
+                              : "and no CMOS RTC found");
     }
 
-    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 )
+    /*
+     * No viable clock source found.  Attempt to provide some guidance to the
+     * user about possible workarounds to boot Xen on the system.
+     */
+    ASSERT(system_state < SYS_STATE_smp_boot);
+
+    if ( !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);
+    panic("No CMOS RTC found - system must be booted from EFI\n");
 }
 
 static unsigned int __ro_after_init cmos_alias_mask;