diff mbox

[RFC:,2/2] patch for QEMU HPET periodic timer emulation to alleviate time drift (code part 2)

Message ID 580687218.226286.1296740680936.JavaMail.root@zmail07.collab.prod.int.phx2.redhat.com (mailing list archive)
State New, archived
Headers show

Commit Message

Ulrich Obergfell Feb. 3, 2011, 1:44 p.m. UTC
None
diff mbox

Patch

diff -up ./hw/hpet.c.orig2 ./hw/hpet.c
--- ./hw/hpet.c.orig2	2011-01-21 23:34:47.000000000 +0100
+++ ./hw/hpet.c	2011-02-01 19:20:24.619247214 +0100
@@ -41,6 +41,13 @@ 
 
 #define HPET_MSI_SUPPORT        0
 
+#ifdef TARGET_I386
+#define MAX_IRQS_TO_INJECT (uint32_t)100
+#define MAX_IRQ_RATE       (uint32_t)10
+
+extern int hpet_driftfix;
+#endif
+
 struct HPETState;
 typedef struct HPETTimer {  /* timers */
     uint8_t tn;             /*timer number*/
@@ -55,6 +62,13 @@  typedef struct HPETTimer {  /* timers */
     uint8_t wrap_flag;      /* timer pop will indicate wrap for one-shot 32-bit
                              * mode. Next pop will be actual timer expiration.
                              */
+#ifdef TARGET_I386
+    /* for lost interrupt compensation */
+    uint64_t saved_period;
+    uint32_t irqs_to_inject;
+    uint32_t irq_rate;
+    uint32_t divisor;
+#endif
 } HPETTimer;
 
 typedef struct HPETState {
@@ -248,7 +262,11 @@  static int hpet_post_load(void *opaque, 
 
 static const VMStateDescription vmstate_hpet_timer = {
     .name = "hpet_timer",
+#ifdef TARGET_I386
+    .version_id = 3,
+#else
     .version_id = 1,
+#endif
     .minimum_version_id = 1,
     .minimum_version_id_old = 1,
     .fields      = (VMStateField []) {
@@ -258,6 +276,13 @@  static const VMStateDescription vmstate_
         VMSTATE_UINT64(fsb, HPETTimer),
         VMSTATE_UINT64(period, HPETTimer),
         VMSTATE_UINT8(wrap_flag, HPETTimer),
+#ifdef TARGET_I386
+        /* for lost interrupt compensation */
+        VMSTATE_UINT64_V(saved_period, HPETTimer, 3),
+        VMSTATE_UINT32_V(irqs_to_inject, HPETTimer, 3),
+        VMSTATE_UINT32_V(irq_rate, HPETTimer, 3),
+        VMSTATE_UINT32_V(divisor, HPETTimer, 3),
+#endif
         VMSTATE_TIMER(qemu_timer, HPETTimer),
         VMSTATE_END_OF_LIST()
     }
@@ -265,7 +290,11 @@  static const VMStateDescription vmstate_
 
 static const VMStateDescription vmstate_hpet = {
     .name = "hpet",
+#ifdef TARGET_I386
+    .version_id = 3,
+#else
     .version_id = 2,
+#endif
     .minimum_version_id = 1,
     .minimum_version_id_old = 1,
     .pre_save = hpet_pre_save,
@@ -289,7 +318,9 @@  static void hpet_timer(void *opaque)
 {
     HPETTimer *t = opaque;
     uint64_t diff;
-
+#ifdef TARGET_I386
+    uint32_t irq_count = 0;
+#endif
     uint64_t period = t->period;
     uint64_t cur_tick = hpet_get_ticks(t->state);
 
@@ -297,13 +328,88 @@  static void hpet_timer(void *opaque)
         if (t->config & HPET_TN_32BIT) {
             while (hpet_time_after(cur_tick, t->cmp)) {
                 t->cmp = (uint32_t)(t->cmp + t->period);
+#ifdef TARGET_I386
+                /* count one irq per period */
+                ++irq_count;
+#endif
             }
         } else {
             while (hpet_time_after64(cur_tick, t->cmp)) {
                 t->cmp += period;
+#ifdef TARGET_I386
+                /* count one irq per period */
+                ++irq_count;
+#endif
             }
         }
         diff = hpet_calculate_diff(t, cur_tick);
+#ifdef TARGET_I386
+        if (hpet_driftfix) {
+            /*
+             * If the period value changed since the previous callback,
+             * scale the number of irqs to inject to the new period value
+             * and save the new period value.
+             */
+            if (t->saved_period != t->period) {
+                t->irqs_to_inject = (t->irqs_to_inject * t->saved_period)
+                                                       / t->period;
+                t->saved_period = t->period;
+            }
+            /*
+             * Add the irq count of the current callback to the number
+             * of irqs to inject. Make sure the result does not exceed
+             * the limit.
+             */
+            t->irqs_to_inject += irq_count;
+            if (t->irqs_to_inject > MAX_IRQS_TO_INJECT)
+                t->irqs_to_inject = MAX_IRQS_TO_INJECT;
+            /*
+             * One irq will be injected during the current callback. If
+             * the number of irqs to inject is greater than 1, there is
+             * is a backlog of 'irqs_to_inject - 1' interrupts that were
+             * lost and need to be compensated.
+             */
+            if (t->irqs_to_inject > 1) {
+                /*
+                 * If the irq count of the current callback is greater
+                 * than 1, 'irq_count - 1' interrupts were lost since
+                 * the previous callback. Increase the rate at which
+                 * additional irqs will be injected to compensate the
+                 * lost interrupts. Make sure the rate does not exceed
+                 * the limit.
+                 */
+                if (irq_count > 1) {
+                    t->irq_rate++;
+                    if (t->irq_rate > MAX_IRQ_RATE)
+                        t->irq_rate = MAX_IRQ_RATE;
+                }
+                /*
+                 * The irq count of the current callback is expected to
+                 * be zero while additional irqs are injected between two
+                 * regular timer interrupts. If the irq count is not zero
+                 * this marks the start of the next timer period. Hence,
+                 * re-initialize the divisor.
+                 */
+                if (irq_count)
+                    t->divisor = t->irq_rate;
+                /*
+                 * 'diff' contains the number of HPET ticks between the
+                 * current callback and the start of the next timer period.
+                 * Divide 'diff' to get additional callbacks. The goal of
+                 * decrementing the divisor is to get additional callbacks
+                 * at similar intervals. [Ideally, the intervals between
+                 * additional callbacks should be very similar. However,
+                 * this cannot be guaranteed because callbacks can occur
+                 * delayed.] Make sure the divisor is not zero at the next
+                 * callback.
+                 */
+                diff /= t->divisor;
+                t->divisor--;
+                if (t->divisor == 0)
+                    t->divisor = t->irq_rate;
+            }
+        }
+#endif
         qemu_mod_timer(t->qemu_timer,
                        qemu_get_clock(vm_clock) + (int64_t)ticks_to_ns(diff));
     } else if (t->config & HPET_TN_32BIT && !timer_is_periodic(t)) {
@@ -315,6 +421,18 @@  static void hpet_timer(void *opaque)
         }
     }
     update_irq(t, 1);
+#ifdef TARGET_I386
+    if (hpet_driftfix) {
+        /*
+         * Re-initialize the rate and the divisor if the backlog
+         * of lost interrupts has been compensated.
+         */
+        if (t->irqs_to_inject && --t->irqs_to_inject == 0) {
+            t->irq_rate = 1;
+            t->divisor = 1;
+        }
+    }
+#endif
 }
 
 static void hpet_set_timer(HPETTimer *t)
@@ -653,6 +771,13 @@  static void hpet_reset(DeviceState *d)
         timer->config |=  0x00000004ULL << 32;
         timer->period = 0ULL;
         timer->wrap_flag = 0;
+#ifdef TARGET_I386
+        /* for lost interrupt compensation */
+        timer->saved_period = 0;
+        timer->irqs_to_inject = 0;
+        timer->irq_rate = 1;
+        timer->divisor = 1;
+#endif
     }
 
     s->hpet_counter = 0ULL;
@@ -713,6 +838,13 @@  static int hpet_init(SysBusDevice *dev)
         timer->qemu_timer = qemu_new_timer(vm_clock, hpet_timer, timer);
         timer->tn = i;
         timer->state = s;
+#ifdef TARGET_I386
+        /* for lost interrupt compensation */
+        timer->saved_period = 0;
+        timer->irqs_to_inject = 0;
+        timer->irq_rate = 1;
+        timer->divisor = 1;
+#endif
     }
 
     /* 64-bit main counter; LegacyReplacementRoute. */