qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [PATCH v2] mc146818rtc: fix timer interrupt reinjection


From: Paolo Bonzini
Subject: Re: [PATCH v2] mc146818rtc: fix timer interrupt reinjection
Date: Mon, 18 Nov 2019 23:08:24 +0100
User-agent: Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Thunderbird/60.8.0

On 18/11/19 22:44, Marcelo Tosatti wrote:
> Second patch blocks NTPd from synchronizing to a source at all
> (can't even confirm if it fails to synchronize after a while).
> 
> Problem seems to be that calling from timer interrupt path:
> 
>    /*
>      * if the periodic timer's update is due to period re-configuration,
>      * we should count the clock since last interrupt.
>      */
>     if (old_period) {
>         int64_t last_periodic_clock, next_periodic_clock;
> 
>         next_periodic_clock = muldiv64(s->next_periodic_time,
>                                 RTC_CLOCK_RATE, NANOSECONDS_PER_SECOND);
>         last_periodic_clock = next_periodic_clock - old_period;
>         lost_clock = cur_clock - last_periodic_clock;
>         assert(lost_clock >= 0);
>     }
> 
> Adds the difference between when the timer interrupt actually executed 
> (cur_clock) and when it should have executed (last_periodic_clock) 
> as reinject time (which will end up injecting more timer interrupts 
> than necessary, so the clock runs faster than it should).

Yes, I made a similar reasoning.  Thanks for the patch, I don't like 
that it adds complication over complication but I guess it would be okay 
for 4.2, if Alex confirms it works for him.  Mine is a much bigger
change .

> Perhaps this is the reason for the 5%+ performance delta?
> 
> The following, on top of Paolo's two patches, fixes it for me
> (and NTPd is able to maintain clock synchronized while playing video on 
> Windows 7).

FYI, here is my attempt at cleaning up everything:

------------------- 8< ----------------
>From c0a53b1c9a331ac4cf5846b477ba0fb795933a34 Mon Sep 17 00:00:00 2001
From: Paolo Bonzini <address@hidden>
Date: Sun, 17 Nov 2019 10:07:38 +0100
Subject: [PATCH 1/5] Revert "mc146818rtc: fix timer interrupt reinjection"

This reverts commit b429de730174b388ea5760e3debb0d542ea3c261, except
that the reversal of the outer "if (period)" is left in.

Signed-off-by: Paolo Bonzini <address@hidden>
---
 hw/rtc/mc146818rtc.c | 67 ++++++++++++++++++++++++++--------------------------
 1 file changed, 33 insertions(+), 34 deletions(-)

diff --git a/hw/rtc/mc146818rtc.c b/hw/rtc/mc146818rtc.c
index ee6bf82..9869dc5 100644
--- a/hw/rtc/mc146818rtc.c
+++ b/hw/rtc/mc146818rtc.c
@@ -174,7 +174,6 @@ periodic_timer_update(RTCState *s, int64_t current_time, 
uint32_t old_period)
     int64_t cur_clock, next_irq_clock, lost_clock = 0;
 
     period = rtc_periodic_clock_ticks(s);
-
     if (!period) {
         s->irq_coalesced = 0;
         timer_del(s->periodic_timer);
@@ -197,42 +196,42 @@ periodic_timer_update(RTCState *s, int64_t current_time, 
uint32_t old_period)
         last_periodic_clock = next_periodic_clock - old_period;
         lost_clock = cur_clock - last_periodic_clock;
         assert(lost_clock >= 0);
+    }
 
+    /*
+     * s->irq_coalesced can change for two reasons:
+     *
+     * a) if one or more periodic timer interrupts have been lost,
+     *    lost_clock will be more that a period.
+     *
+     * b) when the period may be reconfigured, we expect the OS to
+     *    treat delayed tick as the new period.  So, when switching
+     *    from a shorter to a longer period, scale down the missing,
+     *    because the OS will treat past delayed ticks as longer
+     *    (leftovers are put back into lost_clock).  When switching
+     *    to a shorter period, scale up the missing ticks since the
+     *    OS handler will treat past delayed ticks as shorter.
+     */
+    if (s->lost_tick_policy == LOST_TICK_POLICY_SLEW) {
+        uint32_t old_irq_coalesced = s->irq_coalesced;
+
+        s->period = period;
+        lost_clock += old_irq_coalesced * old_period;
+        s->irq_coalesced = lost_clock / s->period;
+        lost_clock %= s->period;
+        if (old_irq_coalesced != s->irq_coalesced ||
+            old_period != s->period) {
+            DPRINTF_C("cmos: coalesced irqs scaled from %d to %d, "
+                      "period scaled from %d to %d\n", old_irq_coalesced,
+                      s->irq_coalesced, old_period, s->period);
+            rtc_coalesced_timer_update(s);
+        }
+    } else {
         /*
-         * s->irq_coalesced can change for two reasons:
-         *
-         * a) if one or more periodic timer interrupts have been lost,
-         *    lost_clock will be more that a period.
-         *
-         * b) when the period may be reconfigured, we expect the OS to
-         *    treat delayed tick as the new period.  So, when switching
-         *    from a shorter to a longer period, scale down the missing,
-         *    because the OS will treat past delayed ticks as longer
-         *    (leftovers are put back into lost_clock).  When switching
-         *    to a shorter period, scale up the missing ticks since the
-         *    OS handler will treat past delayed ticks as shorter.
+         * no way to compensate the interrupt if LOST_TICK_POLICY_SLEW
+         * is not used, we should make the time progress anyway.
          */
-        if (s->lost_tick_policy == LOST_TICK_POLICY_SLEW) {
-            uint32_t old_irq_coalesced = s->irq_coalesced;
-
-            s->period = period;
-            lost_clock += old_irq_coalesced * old_period;
-            s->irq_coalesced = lost_clock / s->period;
-            lost_clock %= s->period;
-            if (old_irq_coalesced != s->irq_coalesced ||
-                old_period != s->period) {
-                DPRINTF_C("cmos: coalesced irqs scaled from %d to %d, "
-                          "period scaled from %d to %d\n", old_irq_coalesced,
-                          s->irq_coalesced, old_period, s->period);
-                rtc_coalesced_timer_update(s);
-            }
-        } else {
-            /*
-             * no way to compensate the interrupt if LOST_TICK_POLICY_SLEW
-             * is not used, we should make the time progress anyway.
-             */
-            lost_clock = MIN(lost_clock, period);
-        }
+        lost_clock = MIN(lost_clock, period);
     }
 
     assert(lost_clock >= 0 && lost_clock <= period);
-- 
1.8.3.1


>From 6a989dedd43b1885bd3f2178d686bf7a4fe06a08 Mon Sep 17 00:00:00 2001
From: Paolo Bonzini <address@hidden>
Date: Sun, 17 Nov 2019 10:28:14 +0100
Subject: [PATCH 2/5] mc146818rtc: keep s->period up to date

Right now s->period is only used if s->lost_tick_policy ==
LOST_TICK_POLICY_SLEW.  Not having to recompute it all the time
will come in handy in upcoming refactoring, so just store it in
RTCState.

Signed-off-by: Paolo Bonzini <address@hidden>
---
 hw/rtc/mc146818rtc.c | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/hw/rtc/mc146818rtc.c b/hw/rtc/mc146818rtc.c
index 9869dc5..da7837c 100644
--- a/hw/rtc/mc146818rtc.c
+++ b/hw/rtc/mc146818rtc.c
@@ -174,6 +174,8 @@ periodic_timer_update(RTCState *s, int64_t current_time, 
uint32_t old_period)
     int64_t cur_clock, next_irq_clock, lost_clock = 0;
 
     period = rtc_periodic_clock_ticks(s);
+    s->period = period;
+
     if (!period) {
         s->irq_coalesced = 0;
         timer_del(s->periodic_timer);
@@ -215,7 +217,6 @@ periodic_timer_update(RTCState *s, int64_t current_time, 
uint32_t old_period)
     if (s->lost_tick_policy == LOST_TICK_POLICY_SLEW) {
         uint32_t old_irq_coalesced = s->irq_coalesced;
 
-        s->period = period;
         lost_clock += old_irq_coalesced * old_period;
         s->irq_coalesced = lost_clock / s->period;
         lost_clock %= s->period;
@@ -231,12 +232,12 @@ periodic_timer_update(RTCState *s, int64_t current_time, 
uint32_t old_period)
          * no way to compensate the interrupt if LOST_TICK_POLICY_SLEW
          * is not used, we should make the time progress anyway.
          */
-        lost_clock = MIN(lost_clock, period);
+        lost_clock = MIN(lost_clock, s->period);
     }
 
-    assert(lost_clock >= 0 && lost_clock <= period);
+    assert(lost_clock >= 0 && lost_clock <= s->period);
 
-    next_irq_clock = cur_clock + period - lost_clock;
+    next_irq_clock = cur_clock + s->period - lost_clock;
     s->next_periodic_time = periodic_clock_to_ns(next_irq_clock) + 1;
     timer_mod(s->periodic_timer, s->next_periodic_time);
 }
@@ -794,6 +795,7 @@ static int rtc_post_load(void *opaque, int version_id)
         s->offset = 0;
         check_update_timer(s);
     }
+    s->period = rtc_periodic_clock_ticks(s);
 
     /* The periodic timer is deterministic in record/replay mode,
      * so there is no need to update it after loading the vmstate.
-- 
1.8.3.1


>From 258e46c4f234385c60857ef3542c335bf6560608 Mon Sep 17 00:00:00 2001
From: Paolo Bonzini <address@hidden>
Date: Mon, 18 Nov 2019 11:50:31 +0100
Subject: [PATCH 3/5] mc146818rtc: clean up update of coalesced timer for
 periodic_timer_update

Remove knowledge of old_period from the code that sets up the
next periodic timer deadline.  Instead, account the missed IRQs into
lost_clock before that part runs.

This prepares for splitting periodic_timer_update in two parts,
one for period reconfiguration and one that sets up the next
periodic timer deadline.

Signed-off-by: Paolo Bonzini <address@hidden>
---
 hw/rtc/mc146818rtc.c | 18 ++++++++----------
 1 file changed, 8 insertions(+), 10 deletions(-)

diff --git a/hw/rtc/mc146818rtc.c b/hw/rtc/mc146818rtc.c
index da7837c..47d966c 100644
--- a/hw/rtc/mc146818rtc.c
+++ b/hw/rtc/mc146818rtc.c
@@ -198,6 +198,12 @@ periodic_timer_update(RTCState *s, int64_t current_time, 
uint32_t old_period)
         last_periodic_clock = next_periodic_clock - old_period;
         lost_clock = cur_clock - last_periodic_clock;
         assert(lost_clock >= 0);
+
+        if (s->lost_tick_policy == LOST_TICK_POLICY_SLEW) {
+            lost_clock += s->irq_coalesced * old_period;
+            s->irq_coalesced = 0;
+            timer_del(s->coalesced_timer);
+        }
     }
 
     /*
@@ -215,18 +221,9 @@ periodic_timer_update(RTCState *s, int64_t current_time, 
uint32_t old_period)
      *    OS handler will treat past delayed ticks as shorter.
      */
     if (s->lost_tick_policy == LOST_TICK_POLICY_SLEW) {
-        uint32_t old_irq_coalesced = s->irq_coalesced;
-
-        lost_clock += old_irq_coalesced * old_period;
         s->irq_coalesced = lost_clock / s->period;
         lost_clock %= s->period;
-        if (old_irq_coalesced != s->irq_coalesced ||
-            old_period != s->period) {
-            DPRINTF_C("cmos: coalesced irqs scaled from %d to %d, "
-                      "period scaled from %d to %d\n", old_irq_coalesced,
-                      s->irq_coalesced, old_period, s->period);
-            rtc_coalesced_timer_update(s);
-        }
+        rtc_coalesced_timer_update(s);
     } else {
         /*
          * no way to compensate the interrupt if LOST_TICK_POLICY_SLEW
@@ -883,6 +880,7 @@ static void rtc_reset(void *opaque)
     if (s->lost_tick_policy == LOST_TICK_POLICY_SLEW) {
         s->irq_coalesced = 0;
         s->irq_reinject_on_ack_count = 0;
+        rtc_coalesced_timer_update(s);
     }
 }
 
-- 
1.8.3.1


>From c722009caa5d4959499a89d63d74082164385f45 Mon Sep 17 00:00:00 2001
From: Paolo Bonzini <address@hidden>
Date: Mon, 18 Nov 2019 12:04:43 +0100
Subject: [PATCH 4/5] mc146818rtc: reorganize arguments of
 periodic_timer_update

This is mostly preparation for the next patch.  Because the next patch
will pass the number of lost 32 kHz ticks to periodic_timer_update,
it makes sense that the current time is also passed in 32 kHz units.
This makes calling periodic_timer_update from cmos_ioport_write a
bit unwieldy, so extract periodic_timer_reconfigure.

Signed-off-by: Paolo Bonzini <address@hidden>
---
 hw/rtc/mc146818rtc.c | 38 ++++++++++++++++++++++++++------------
 1 file changed, 26 insertions(+), 12 deletions(-)

diff --git a/hw/rtc/mc146818rtc.c b/hw/rtc/mc146818rtc.c
index 47d966c..8a9e004 100644
--- a/hw/rtc/mc146818rtc.c
+++ b/hw/rtc/mc146818rtc.c
@@ -163,15 +163,23 @@ static uint32_t rtc_periodic_clock_ticks(RTCState *s)
     return periodic_period_to_clock(period_code);
 }
 
+static uint32_t rtc_periodic_clock_get_ticks(void)
+{
+    int64_t current_time = qemu_clock_get_ns(rtc_clock);
+
+    /* compute 32 khz clock */
+    return muldiv64(current_time, RTC_CLOCK_RATE, NANOSECONDS_PER_SECOND);
+}
+
 /*
  * handle periodic timer. @old_period indicates the periodic timer update
  * is just due to period adjustment.
  */
 static void
-periodic_timer_update(RTCState *s, int64_t current_time, uint32_t old_period)
+periodic_timer_update(RTCState *s, int64_t cur_clock, uint32_t old_period)
 {
     uint32_t period;
-    int64_t cur_clock, next_irq_clock, lost_clock = 0;
+    int64_t next_irq_clock, lost_clock = 0;
 
     period = rtc_periodic_clock_ticks(s);
     s->period = period;
@@ -182,10 +190,6 @@ periodic_timer_update(RTCState *s, int64_t current_time, 
uint32_t old_period)
         return;
     }
 
-    /* compute 32 khz clock */
-    cur_clock =
-        muldiv64(current_time, RTC_CLOCK_RATE, NANOSECONDS_PER_SECOND);
-
     /*
      * if the periodic timer's update is due to period re-configuration,
      * we should count the clock since last interrupt.
@@ -239,11 +243,23 @@ periodic_timer_update(RTCState *s, int64_t current_time, 
uint32_t old_period)
     timer_mod(s->periodic_timer, s->next_periodic_time);
 }
 
+static void
+periodic_timer_reconfigure(RTCState *s, uint32_t old_period)
+{
+    int64_t cur_clock = rtc_periodic_clock_get_ticks();
+
+    periodic_timer_update(s, cur_clock, old_period);
+}
+
 static void rtc_periodic_timer(void *opaque)
 {
     RTCState *s = opaque;
+    int64_t last_periodic_clock;
+
+    last_periodic_clock =
+        muldiv64(s->next_periodic_time, RTC_CLOCK_RATE, 
NANOSECONDS_PER_SECOND);
 
-    periodic_timer_update(s, s->next_periodic_time, 0);
+    periodic_timer_update(s, last_periodic_clock, 0);
     s->cmos_data[RTC_REG_C] |= REG_C_PF;
     if (s->cmos_data[RTC_REG_B] & REG_B_PIE) {
         s->cmos_data[RTC_REG_C] |= REG_C_IRQF;
@@ -508,8 +524,7 @@ static void cmos_ioport_write(void *opaque, hwaddr addr,
                 (s->cmos_data[RTC_REG_A] & REG_A_UIP);
 
             if (update_periodic_timer) {
-                periodic_timer_update(s, qemu_clock_get_ns(rtc_clock),
-                                      old_period);
+                periodic_timer_reconfigure(s, old_period);
             }
 
             check_update_timer(s);
@@ -547,8 +562,7 @@ static void cmos_ioport_write(void *opaque, hwaddr addr,
             s->cmos_data[RTC_REG_B] = data;
 
             if (update_periodic_timer) {
-                periodic_timer_update(s, qemu_clock_get_ns(rtc_clock),
-                                      old_period);
+                periodic_timer_reconfigure(s, old_period);
             }
 
             check_update_timer(s);
@@ -802,7 +816,7 @@ static int rtc_post_load(void *opaque, int version_id)
         uint64_t now = qemu_clock_get_ns(rtc_clock);
         if (now < s->next_periodic_time ||
             now > (s->next_periodic_time + get_max_clock_jump())) {
-            periodic_timer_update(s, qemu_clock_get_ns(rtc_clock), 0);
+            periodic_timer_reconfigure(s, s->period);
         }
     }
 
-- 
1.8.3.1


>From 8994fed352d8147d2dea99710728cc15fb9f2cc2 Mon Sep 17 00:00:00 2001
From: Paolo Bonzini <address@hidden>
Date: Mon, 18 Nov 2019 11:59:59 +0100
Subject: [PATCH 5/5] mc146818rtc: fix timer interrupt reinjection again

Commit 369b41359af46bded5799c9ef8be2b641d92e043 broke timer interrupt
reinjection when there is no period change by the guest.  In that
case, old_period is 0, which ends up zeroing irq_coalesced (counter of
reinjected interrupts).

The consequence is Windows 7 is unable to synchronize time via NTP.
Easily reproducible by playing a fullscreen video with cirrus and VNC.

That part of periodic_timer_update that introduces the bug is
only needed due to reconfiguration of the period, so move it to
periodic_timer_reconfigure.  In that path, old_period == 0 does mean
that the periodic timer was off.

Reported-by: Marcelo Tosatti <address@hidden>
Signed-off-by: Paolo Bonzini <address@hidden>
---
 hw/rtc/mc146818rtc.c | 72 +++++++++++++++++++++++++---------------------------
 1 file changed, 34 insertions(+), 38 deletions(-)

diff --git a/hw/rtc/mc146818rtc.c b/hw/rtc/mc146818rtc.c
index 8a9e004..823f706 100644
--- a/hw/rtc/mc146818rtc.c
+++ b/hw/rtc/mc146818rtc.c
@@ -171,44 +171,10 @@ static uint32_t rtc_periodic_clock_get_ticks(void)
     return muldiv64(current_time, RTC_CLOCK_RATE, NANOSECONDS_PER_SECOND);
 }
 
-/*
- * handle periodic timer. @old_period indicates the periodic timer update
- * is just due to period adjustment.
- */
 static void
-periodic_timer_update(RTCState *s, int64_t cur_clock, uint32_t old_period)
+periodic_timer_update(RTCState *s, int64_t cur_clock, int64_t lost_clock)
 {
-    uint32_t period;
-    int64_t next_irq_clock, lost_clock = 0;
-
-    period = rtc_periodic_clock_ticks(s);
-    s->period = period;
-
-    if (!period) {
-        s->irq_coalesced = 0;
-        timer_del(s->periodic_timer);
-        return;
-    }
-
-    /*
-     * if the periodic timer's update is due to period re-configuration,
-     * we should count the clock since last interrupt.
-     */
-    if (old_period) {
-        int64_t last_periodic_clock, next_periodic_clock;
-
-        next_periodic_clock = muldiv64(s->next_periodic_time,
-                                RTC_CLOCK_RATE, NANOSECONDS_PER_SECOND);
-        last_periodic_clock = next_periodic_clock - old_period;
-        lost_clock = cur_clock - last_periodic_clock;
-        assert(lost_clock >= 0);
-
-        if (s->lost_tick_policy == LOST_TICK_POLICY_SLEW) {
-            lost_clock += s->irq_coalesced * old_period;
-            s->irq_coalesced = 0;
-            timer_del(s->coalesced_timer);
-        }
-    }
+    int64_t next_irq_clock;
 
     /*
      * s->irq_coalesced can change for two reasons:
@@ -243,23 +209,53 @@ periodic_timer_update(RTCState *s, int64_t cur_clock, 
uint32_t old_period)
     timer_mod(s->periodic_timer, s->next_periodic_time);
 }
 
+/* adjust periodic timer on period adjustment */
 static void
 periodic_timer_reconfigure(RTCState *s, uint32_t old_period)
 {
     int64_t cur_clock = rtc_periodic_clock_get_ticks();
+    int64_t lost_clock = 0;
 
-    periodic_timer_update(s, cur_clock, old_period);
+    s->period = rtc_periodic_clock_ticks(s);
+    if (!s->period) {
+        s->irq_coalesced = 0;
+        timer_del(s->periodic_timer);
+        return;
+    }
+
+    /*
+     * if the periodic timer was active before, account the time since
+     * the last interrupt.
+     */
+    if (old_period) {
+        int64_t last_periodic_clock, next_periodic_clock;
+
+        next_periodic_clock = muldiv64(s->next_periodic_time,
+                                RTC_CLOCK_RATE, NANOSECONDS_PER_SECOND);
+        last_periodic_clock = next_periodic_clock - old_period;
+        lost_clock = cur_clock - last_periodic_clock;
+        assert(lost_clock >= 0);
+
+        if (s->lost_tick_policy == LOST_TICK_POLICY_SLEW) {
+            lost_clock += s->irq_coalesced * old_period;
+            s->irq_coalesced = 0;
+            timer_del(s->coalesced_timer);
+        }
+    }
+
+    periodic_timer_update(s, cur_clock, lost_clock);
 }
 
 static void rtc_periodic_timer(void *opaque)
 {
     RTCState *s = opaque;
+    int64_t cur_clock = rtc_periodic_clock_get_ticks();
     int64_t last_periodic_clock;
 
     last_periodic_clock =
         muldiv64(s->next_periodic_time, RTC_CLOCK_RATE, 
NANOSECONDS_PER_SECOND);
 
-    periodic_timer_update(s, last_periodic_clock, 0);
+    periodic_timer_update(s, cur_clock, cur_clock - last_periodic_clock);
     s->cmos_data[RTC_REG_C] |= REG_C_PF;
     if (s->cmos_data[RTC_REG_B] & REG_B_PIE) {
         s->cmos_data[RTC_REG_C] |= REG_C_IRQF;
-- 
1.8.3.1




reply via email to

[Prev in Thread] Current Thread [Next in Thread]