[PATCH v2 06/36] KVM: VMX: Implement the hooks for VMX guest virtual deadline timer
From: isaku . yamahata
Date: Thu Mar 05 2026 - 12:48:29 EST
From: Yang Zhong <yang.zhong@xxxxxxxxxxxxxxx>
Implement the hooks for the VMX backend for APIC timer virtualization to
access the related VMCS fields.
Signed-off-by: Yang Zhong <yang.zhong@xxxxxxxxxxxxxxx>
Co-developed-by: Isaku Yamahata <isaku.yamahata@xxxxxxxxx>
Signed-off-by: Isaku Yamahata <isaku.yamahata@xxxxxxxxx>
---
Changes:
v1 -> v2:
Move kvm_cpu_apicv_active() before kvm_lapic_lvtt_timer_mode() as
it checks in-kernel apic check.
---
arch/x86/kvm/lapic.h | 5 ++
arch/x86/kvm/vmx/capabilities.h | 6 +++
arch/x86/kvm/vmx/main.c | 5 ++
arch/x86/kvm/vmx/vmx.c | 83 ++++++++++++++++++++++++++++++++-
arch/x86/kvm/vmx/x86_ops.h | 5 ++
5 files changed, 103 insertions(+), 1 deletion(-)
diff --git a/arch/x86/kvm/lapic.h b/arch/x86/kvm/lapic.h
index 5e96299c31f7..2f510503f5b3 100644
--- a/arch/x86/kvm/lapic.h
+++ b/arch/x86/kvm/lapic.h
@@ -237,6 +237,11 @@ static inline int kvm_lapic_latched_init(struct kvm_vcpu *vcpu)
return lapic_in_kernel(vcpu) && test_bit(KVM_APIC_INIT, &vcpu->arch.apic->pending_events);
}
+static inline int kvm_lapic_lvtt_timer_mode(struct kvm_vcpu *vcpu)
+{
+ return vcpu->arch.apic->lapic_timer.timer_mode;
+}
+
bool kvm_apic_pending_eoi(struct kvm_vcpu *vcpu, int vector);
bool kvm_lapic_suppress_eoi_broadcast(struct kvm_lapic *apic);
diff --git a/arch/x86/kvm/vmx/capabilities.h b/arch/x86/kvm/vmx/capabilities.h
index 4e371c93ae16..c5cb098f579b 100644
--- a/arch/x86/kvm/vmx/capabilities.h
+++ b/arch/x86/kvm/vmx/capabilities.h
@@ -90,6 +90,12 @@ static inline bool cpu_has_vmx_preemption_timer(void)
PIN_BASED_VMX_PREEMPTION_TIMER;
}
+static inline bool cpu_has_vmx_apic_timer_virt(void)
+{
+ return vmcs_config.cpu_based_3rd_exec_ctrl &
+ TERTIARY_EXEC_GUEST_APIC_TIMER;
+}
+
static inline bool cpu_has_vmx_posted_intr(void)
{
return vmcs_config.pin_based_exec_ctrl & PIN_BASED_POSTED_INTR;
diff --git a/arch/x86/kvm/vmx/main.c b/arch/x86/kvm/vmx/main.c
index dbebddf648be..ed20c859def3 100644
--- a/arch/x86/kvm/vmx/main.c
+++ b/arch/x86/kvm/vmx/main.c
@@ -994,6 +994,11 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
#ifdef CONFIG_X86_64
.set_hv_timer = vt_op(set_hv_timer),
.cancel_hv_timer = vt_op(cancel_hv_timer),
+ .can_use_apic_virt_timer = vmx_can_use_apic_virt_timer,
+ .set_apic_virt_timer = vmx_set_apic_virt_timer,
+ .cancel_apic_virt_timer = vmx_cancel_apic_virt_timer,
+ .set_guest_tsc_deadline_virt = vmx_set_guest_tsc_deadline_virt,
+ .get_guest_tsc_deadline_virt = vmx_get_guest_tsc_deadline_virt,
#endif
.setup_mce = vt_op(setup_mce),
diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
index 4ccb2e42322d..b70641bfecab 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -2802,7 +2802,8 @@ static int setup_vmcs_config(struct vmcs_config *vmcs_conf,
& ~TERTIARY_EXEC_GUEST_APIC_TIMER,
MSR_IA32_VMX_PROCBASED_CTLS3);
- if (!(_cpu_based_2nd_exec_control & SECONDARY_EXEC_VIRTUAL_INTR_DELIVERY))
+ if (!IS_ENABLED(CONFIG_X86_64) ||
+ !(_cpu_based_2nd_exec_control & SECONDARY_EXEC_VIRTUAL_INTR_DELIVERY))
_cpu_based_3rd_exec_control &= ~TERTIARY_EXEC_GUEST_APIC_TIMER;
if (adjust_vmx_controls(KVM_REQUIRED_VMX_VM_EXIT_CONTROLS,
@@ -8364,6 +8365,86 @@ void vmx_cancel_hv_timer(struct kvm_vcpu *vcpu)
{
to_vmx(vcpu)->hv_deadline_tsc = -1;
}
+
+bool vmx_can_use_apic_virt_timer(struct kvm_vcpu *vcpu)
+{
+ if (vcpu->kvm->arch.vm_type != KVM_X86_DEFAULT_VM)
+ return false;
+
+ return cpu_has_vmx_apic_timer_virt() &&
+ /* SECONDARY_EXEC_VIRTUAL_INTR_DELIVERY and in-kernel apic */
+ kvm_vcpu_apicv_active(vcpu) &&
+ /* VMX guest virtual timer supports only TSC deadline mode. */
+ kvm_lapic_lvtt_timer_mode(vcpu) == APIC_LVT_TIMER_TSCDEADLINE &&
+ /* KVM doesn't use RDTSC existing. Safeguard. */
+ !(exec_controls_get(to_vmx(vcpu)) & CPU_BASED_RDTSC_EXITING);
+}
+
+void vmx_set_apic_virt_timer(struct kvm_vcpu *vcpu, u16 vector)
+{
+ vmcs_write16(GUEST_APIC_TIMER_VECTOR, vector);
+ vmx_disable_intercept_for_msr(vcpu, MSR_IA32_TSC_DEADLINE, MSR_TYPE_RW);
+ tertiary_exec_controls_setbit(to_vmx(vcpu), TERTIARY_EXEC_GUEST_APIC_TIMER);
+}
+
+void vmx_cancel_apic_virt_timer(struct kvm_vcpu *vcpu)
+{
+ vmx_enable_intercept_for_msr(vcpu, MSR_IA32_TSC_DEADLINE, MSR_TYPE_RW);
+ tertiary_exec_controls_clearbit(to_vmx(vcpu), TERTIARY_EXEC_GUEST_APIC_TIMER);
+}
+
+static u64 vmx_calc_deadline_l1_to_host(struct kvm_vcpu *vcpu, u64 l1_tsc)
+{
+ u64 host_tsc_now = rdtsc();
+ u64 l1_tsc_now = kvm_read_l1_tsc(vcpu, host_tsc_now);
+ u64 host_tsc;
+
+ /* 0 means that timer is disarmed. */
+ if (!l1_tsc)
+ return 0;
+
+ host_tsc = l1_tsc - vcpu->arch.l1_tsc_offset;
+ if (vcpu->arch.l1_tsc_scaling_ratio != kvm_caps.default_tsc_scaling_ratio)
+ if (u64_shl_div_u64(l1_tsc,
+ kvm_caps.tsc_scaling_ratio_frac_bits,
+ vcpu->arch.l1_tsc_scaling_ratio,
+ &host_tsc))
+ host_tsc = ~0ull;
+
+ /*
+ * Clamp the result on overflow.
+ * TSC deadline isn't supposed to overflow in practice.
+ * ~0ull is considered that the timer is armed, but won't fire in
+ * practical time frame.
+ */
+ if (l1_tsc > l1_tsc_now && host_tsc <= host_tsc_now)
+ host_tsc = ~0ull;
+ /*
+ * Clamp the result on underflow.
+ * The past value means fire the timer immediately.
+ * Pick the obvious past value.
+ */
+ if (l1_tsc <= l1_tsc_now && host_tsc > host_tsc_now)
+ host_tsc = 1ull;
+
+ if (!host_tsc)
+ host_tsc = 1ull;
+
+ return host_tsc;
+}
+
+void vmx_set_guest_tsc_deadline_virt(struct kvm_vcpu *vcpu,
+ u64 guest_deadline_virt)
+{
+ vmcs_write64(GUEST_DEADLINE_VIR, guest_deadline_virt);
+ vmcs_write64(GUEST_DEADLINE_PHY,
+ vmx_calc_deadline_l1_to_host(vcpu, guest_deadline_virt));
+}
+
+u64 vmx_get_guest_tsc_deadline_virt(struct kvm_vcpu *vcpu)
+{
+ return vmcs_read64(GUEST_DEADLINE_VIR);
+}
#endif
void vmx_update_cpu_dirty_logging(struct kvm_vcpu *vcpu)
diff --git a/arch/x86/kvm/vmx/x86_ops.h b/arch/x86/kvm/vmx/x86_ops.h
index d09abeac2b56..364050e0427c 100644
--- a/arch/x86/kvm/vmx/x86_ops.h
+++ b/arch/x86/kvm/vmx/x86_ops.h
@@ -117,6 +117,11 @@ void vmx_update_cpu_dirty_logging(struct kvm_vcpu *vcpu);
int vmx_set_hv_timer(struct kvm_vcpu *vcpu, u64 guest_deadline_tsc,
bool *expired);
void vmx_cancel_hv_timer(struct kvm_vcpu *vcpu);
+bool vmx_can_use_apic_virt_timer(struct kvm_vcpu *vcpu);
+void vmx_set_apic_virt_timer(struct kvm_vcpu *vcpu, u16 vector);
+void vmx_cancel_apic_virt_timer(struct kvm_vcpu *vcpu);
+void vmx_set_guest_tsc_deadline_virt(struct kvm_vcpu *vcpu, u64 guest_deadline_tsc);
+u64 vmx_get_guest_tsc_deadline_virt(struct kvm_vcpu *vcpu);
#endif
void vmx_setup_mce(struct kvm_vcpu *vcpu);
--
2.45.2