]> xenbits.xensource.com Git - xen.git/commitdiff
x86/hvm/rtc: preserved guest RTC offset during suspend/resume/migrate
authorPaul Durrant <pdurrant@amazon.com>
Fri, 27 Dec 2019 08:50:31 +0000 (09:50 +0100)
committerJan Beulich <jbeulich@suse.com>
Fri, 27 Dec 2019 08:50:31 +0000 (09:50 +0100)
The emulated RTC is synchronized with the PV wallclock; any write to the
RTC will update struct domain's 'time_offset_seconds' field and call
update_domain_wallclock().

However, the value of 'time_offset_seconds' is not preserved in any save
record and indeed, when the RTC save record is loaded, the CMOS values
will be updated based on an offset value which may or may not have been
set by the toolstack [1]. This may result in making bogus values available
to the guest and messing up any calculations done in the call to
alarm_timer_update() at the end of rtc_load().

This patch extends the RTC save record to contain an offset value, which
will be zero filled on load of an older record. The 'time_offset_secoonds'
field in struct domain is also modified into a 'time_offset' struct,
containing a 'seconds' field and a boolean 'set' field.

The code in rtc_load() then uses the new value in the save record to
update the value of struct domain's 'time_offset.seconds' unless
'time_offset.set' is true, which will only be the case if the toolstack has
already performed a XEN_DOMCTL_settimeoffset.

[1] There is currently no way for a toolstack to read the value of
    'time_offset_seconds' from struct domain. In the past, any hope of
    preservation of the value across a guest life-cycle operation was based
    on relying on qemu-dm to write a value into xenstore whenever the RTC
    was updated, in response to an IOREQ with type IOREQ_TYPE_TIMEOFFSET
    being sent by Xen; see:

    https://xenbits.xen.org/gitweb/?p=qemu-xen-traditional.git;a=blob;f=i386-dm/helper2.c#l457

    but this behaviour was never forward-ported into upstream QEMU, which
    completely ignores that IOREQ type.
    In either case, nothing in xl or libxl ever samples the value of
    RTC offset from xenstore so any offset adjustment to a non-zero value
    performed by the guest (which in the case of Windows is highly likely
    as it normally writes RTC in local time, whereas Xen maintains time in
    UTC) is completely lost with the de-facto toolstack, and always has
    been. Instead, PV drivers are relied upon to paper over this gaping
    hole.

Signed-off-by: Paul Durrant <pdurrant@amazon.com>
Reviewed-by: Jan Beulich <jbeulich@suse.com>
Acked-by: Julien Grall <julien@xen.org>
xen/arch/arm/platform_hypercall.c
xen/arch/arm/time.c
xen/arch/arm/vtimer.c
xen/arch/x86/hvm/rtc.c
xen/arch/x86/time.c
xen/common/time.c
xen/include/public/arch-x86/hvm/save.h
xen/include/xen/sched.h

index 5aab856ce73a33add0803b6923904dca004f33ca..8efac7ee602a7886c3dc94d55cf03e85f853488b 100644 (file)
@@ -53,7 +53,7 @@ long do_platform_op(XEN_GUEST_HANDLE_PARAM(xen_platform_op_t) u_xenpf_op)
         if ( likely(!op->u.settime64.mbz) )
             do_settime(op->u.settime64.secs,
                        op->u.settime64.nsecs,
-                       op->u.settime64.system_time + SECONDS(d->time_offset_seconds));
+                       op->u.settime64.system_time + SECONDS(d->time_offset.seconds));
         else
             ret = -EINVAL;
         break;
index 739bcf186c954701c99ae965e17c1b70951b97ef..b0021c2c69faea902ee8e8dd7bd4054d8dd1ae40 100644 (file)
@@ -353,7 +353,8 @@ void update_vcpu_system_time(struct vcpu *v)
 
 void domain_set_time_offset(struct domain *d, int64_t time_offset_seconds)
 {
-    d->time_offset_seconds = time_offset_seconds;
+    d->time_offset.seconds = time_offset_seconds;
+    d->time_offset.set = true;
     /* XXX update guest visible wallclock time */
 }
 
index e6aebdac9ee6ae330ae2cee029c8c10d53875bee..240a850b6e641719f70b2ba72dadfdbbf3e32ff4 100644 (file)
@@ -64,8 +64,8 @@ int domain_vtimer_init(struct domain *d, struct xen_arch_domainconfig *config)
 {
     d->arch.phys_timer_base.offset = NOW();
     d->arch.virt_timer_base.offset = READ_SYSREG64(CNTPCT_EL0);
-    d->time_offset_seconds = ticks_to_ns(d->arch.virt_timer_base.offset - boot_count);
-    do_div(d->time_offset_seconds, 1000000000);
+    d->time_offset.seconds = ticks_to_ns(d->arch.virt_timer_base.offset - boot_count);
+    do_div(d->time_offset.seconds, 1000000000);
 
     config->clock_frequency = timer_dt_clock_frequency;
 
index 42339682e847233e3904a2cb0f9c82b22b4d8615..bb41efe84a996f202078a587d9143310d5fb9ff0 100644 (file)
@@ -594,7 +594,7 @@ static void rtc_set_time(RTCState *s)
 
     /* We use the guest's setting of the RTC to define the local-time 
      * offset for this domain. */
-    d->time_offset_seconds += (after - before);
+    d->time_offset.seconds += (after - before);
     update_domain_wallclock_time(d);
     /* Also tell qemu-dm about it so it will be remembered for next boot. */
     send_timeoffset_req(after - before);
@@ -747,8 +747,10 @@ static int rtc_save(struct vcpu *v, hvm_domain_context_t *h)
         return 0;
 
     spin_lock(&s->lock);
+    s->hw.rtc_offset = d->time_offset.seconds;
     rc = hvm_save_entry(RTC, 0, h, &s->hw);
     spin_unlock(&s->lock);
+
     return rc;
 }
 
@@ -763,7 +765,7 @@ static int rtc_load(struct domain *d, hvm_domain_context_t *h)
     spin_lock(&s->lock);
 
     /* Restore the registers */
-    if ( hvm_load_entry(RTC, h, &s->hw) != 0 )
+    if ( hvm_load_entry_zeroextend(RTC, h, &s->hw) != 0 )
     {
         spin_unlock(&s->lock);
         return -EINVAL;
@@ -771,6 +773,12 @@ static int rtc_load(struct domain *d, hvm_domain_context_t *h)
 
     /* Reset the wall-clock time.  In normal running, this runs with host 
      * time, so let's keep doing that. */
+    if ( !d->time_offset.set )
+    {
+        d->time_offset.seconds = s->hw.rtc_offset;
+        update_domain_wallclock_time(d);
+    }
+
     s->current_tm = gmtime(get_localtime(d));
     rtc_copy_date(s);
 
index ea696a95e82fb06e36b87a45497ddeab15219c08..e79cb4d019837d7f244bb15197447a5d70cd0582 100644 (file)
@@ -1288,7 +1288,8 @@ static void update_domain_rtc(void)
 
 void domain_set_time_offset(struct domain *d, int64_t time_offset_seconds)
 {
-    d->time_offset_seconds = time_offset_seconds;
+    d->time_offset.seconds = time_offset_seconds;
+    d->time_offset.set = true;
     if ( is_hvm_domain(d) )
         rtc_update_clock(d);
     update_domain_wallclock_time(d);
index a7caea99e09da29e75df3087252ea319979b2125..82336e2d5a1f3081d41e5483c14a8a6997e5a458 100644 (file)
@@ -105,7 +105,7 @@ void update_domain_wallclock_time(struct domain *d)
     *wc_version = version_update_begin(*wc_version);
     smp_wmb();
 
-    sec = wc_sec + d->time_offset_seconds;
+    sec = wc_sec + d->time_offset.seconds;
     shared_info(d, wc_sec)    = sec;
     shared_info(d, wc_nsec)   = wc_nsec;
 #ifdef CONFIG_X86
@@ -148,13 +148,13 @@ void do_settime(u64 secs, unsigned int nsecs, u64 system_time_base)
 unsigned long get_localtime(struct domain *d)
 {
     return wc_sec + (wc_nsec + NOW()) / 1000000000ULL
-        + d->time_offset_seconds;
+        + d->time_offset.seconds;
 }
 
 /* Return microsecs after 00:00:00 localtime, 1 January, 1970. */
 uint64_t get_localtime_us(struct domain *d)
 {
-    return (SECONDS(wc_sec + d->time_offset_seconds) + wc_nsec + NOW())
+    return (SECONDS(wc_sec + d->time_offset.seconds) + wc_nsec + NOW())
            / 1000UL;
 }
 
index bb8fa7c12fff8bf2d45a2e8c4caff21b408f925e..b2ad3fcd740d6c539d8a89c63df3bf5e5a6e7ee7 100644 (file)
@@ -500,6 +500,8 @@ struct hvm_hw_rtc {
     /* Index register for 2-part operations */
     uint8_t cmos_index;
     uint8_t pad0;
+    /* RTC offset from host time */
+    int64_t rtc_offset;
 };
 
 DECLARE_HVM_SAVE_TYPE(RTC, 11, struct hvm_hw_rtc);
index 9f7bc69293ba9bcf038a78f2acb77cf0303db8ae..94add379902d7bb1ea68b95f03bd675a4820a729 100644 (file)
@@ -406,7 +406,10 @@ struct domain
     /* Domain is paused by controller software? */
     int              controller_pause_count;
 
-    int64_t          time_offset_seconds;
+    struct {
+        int64_t seconds;
+        bool set;
+    } time_offset;
 
 #ifdef CONFIG_HAS_PCI
     struct list_head pdev_list;