counter_passed = ((hvm_get_guest_time(v) - vlapic->timer_last_update)
/ (APIC_BUS_CYCLE_NS * vlapic->hw.timer_divisor));
- if ( tmict != 0 )
+ /* If timer_last_update is 0, then TMCCT should return 0 as well. */
+ if ( tmict && vlapic->timer_last_update )
{
if ( vlapic_lvtt_period(vlapic) )
counter_passed %= tmict;
* It expect the new value of LVTT to be set *after* being called, with this
* new values passed as parameter (only APIC_TIMER_MODE_MASK bits matter).
*/
-static void vlapic_update_timer(struct vlapic *vlapic, uint32_t lvtt)
+static void vlapic_update_timer(struct vlapic *vlapic, uint32_t lvtt,
+ bool tmict_updated)
{
- uint64_t period;
- bool is_periodic;
+ uint64_t period, delta = 0;
+ bool is_oneshot, is_periodic;
is_periodic = (lvtt & APIC_TIMER_MODE_MASK) == APIC_TIMER_MODE_PERIODIC;
+ is_oneshot = (lvtt & APIC_TIMER_MODE_MASK) == APIC_TIMER_MODE_ONESHOT;
period = (uint64_t)vlapic_get_reg(vlapic, APIC_TMICT)
* APIC_BUS_CYCLE_NS * vlapic->hw.timer_divisor;
- if ( period )
+ /* Calculate the next time the timer should trigger an interrupt. */
+ if ( tmict_updated )
+ delta = period;
+ else if ( period && vlapic->timer_last_update )
{
- TRACE_2_LONG_3D(TRC_HVM_EMUL_LAPIC_START_TIMER, TRC_PAR_LONG(period),
+ uint64_t time_passed = hvm_get_guest_time(current)
+ - vlapic->timer_last_update;
+
+ /* This depends of the previous mode, if a new mode is being set */
+ if ( vlapic_lvtt_period(vlapic) )
+ time_passed %= period;
+ if ( time_passed < period )
+ delta = period - time_passed;
+ }
+
+ if ( delta && (is_oneshot || is_periodic) )
+ {
+ TRACE_2_LONG_3D(TRC_HVM_EMUL_LAPIC_START_TIMER, TRC_PAR_LONG(delta),
TRC_PAR_LONG(is_periodic ? period : 0),
vlapic->pt.irq);
- create_periodic_time(current, &vlapic->pt, period,
+ create_periodic_time(current, &vlapic->pt, delta,
is_periodic ? period : 0, vlapic->pt.irq,
is_periodic ? vlapic_pt_cb : NULL,
&vlapic->timer_last_update);
vlapic->timer_last_update = vlapic->pt.last_plt_gtime;
+ if ( !tmict_updated )
+ vlapic->timer_last_update -= period - delta;
HVM_DBG_LOG(DBG_LEVEL_VLAPIC,
"bus cycle is %uns, "
{
TRACE_0D(TRC_HVM_EMUL_LAPIC_STOP_TIMER);
destroy_periodic_time(&vlapic->pt);
+ /*
+ * From now, TMCCT should return 0 until TMICT is set again.
+ * This is because the timer mode was one-shot when the counter reach 0
+ * or just because the timer is disable.
+ */
+ vlapic->timer_last_update = 0;
}
}
break;
case APIC_LVTT: /* LVT Timer Reg */
- if ( (vlapic_get_reg(vlapic, offset) & APIC_TIMER_MODE_MASK) !=
- (val & APIC_TIMER_MODE_MASK) )
+ if ( vlapic_lvtt_tdt(vlapic) !=
+ ((val & APIC_TIMER_MODE_MASK) == APIC_TIMER_MODE_TSC_DEADLINE))
{
- TRACE_0D(TRC_HVM_EMUL_LAPIC_STOP_TIMER);
- destroy_periodic_time(&vlapic->pt);
vlapic_set_reg(vlapic, APIC_TMICT, 0);
- vlapic_set_reg(vlapic, APIC_TMCCT, 0);
vlapic->hw.tdt_msr = 0;
}
vlapic->pt.irq = val & APIC_VECTOR_MASK;
+
+ vlapic_update_timer(vlapic, val, false);
+
/* fallthrough */
case APIC_LVTTHMR: /* LVT Thermal Monitor */
case APIC_LVTPC: /* LVT Performance Counter */
vlapic_set_reg(vlapic, APIC_TMICT, val);
- vlapic_update_timer(vlapic, vlapic_get_reg(vlapic, APIC_LVTT));
+ vlapic_update_timer(vlapic, vlapic_get_reg(vlapic, APIC_LVTT), true);
break;
case APIC_TDCR: