@@ -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. */