KVM: TDX: Add support for find pending IRQ in a protected local APIC
authorSean Christopherson <seanjc@google.com>
Sat, 22 Feb 2025 01:47:42 +0000 (09:47 +0800)
committerPaolo Bonzini <pbonzini@redhat.com>
Fri, 14 Mar 2025 18:20:55 +0000 (14:20 -0400)
Add flag and hook to KVM's local APIC management to support determining
whether or not a TDX guest has a pending IRQ.  For TDX vCPUs, the virtual
APIC page is owned by the TDX module and cannot be accessed by KVM.  As a
result, registers that are virtualized by the CPU, e.g. PPR, cannot be
read or written by KVM.  To deliver interrupts for TDX guests, KVM must
send an IRQ to the CPU on the posted interrupt notification vector.  And
to determine if TDX vCPU has a pending interrupt, KVM must check if there
is an outstanding notification.

Return "no interrupt" in kvm_apic_has_interrupt() if the guest APIC is
protected to short-circuit the various other flows that try to pull an
IRQ out of the vAPIC, the only valid operation is querying _if_ an IRQ is
pending, KVM can't do anything based on _which_ IRQ is pending.

Intentionally omit sanity checks from other flows, e.g. PPR update, so as
not to degrade non-TDX guests with unnecessary checks.  A well-behaved KVM
and userspace will never reach those flows for TDX guests, but reaching
them is not fatal if something does go awry.

For the TD exits not due to HLT TDCALL, skip checking RVI pending in
tdx_protected_apic_has_interrupt().  Except for the guest being stupid
(e.g., non-HLT TDCALL in an interrupt shadow), it's not even possible to
have an interrupt in RVI that is fully unmasked.  There is no any CPU flows
that modify RVI in the middle of instruction execution.  I.e. if RVI is
non-zero, then either the interrupt has been pending since before the TD
exit, or the instruction caused the TD exit is in an STI/SS shadow.  KVM
doesn't care about STI/SS shadows outside of the HALTED case.  And if the
interrupt was pending before TD exit, then it _must_ be blocked, otherwise
the interrupt would have been serviced at the instruction boundary.

For the HLT TDCALL case, it will be handled in a future patch when HLT
TDCALL is supported.

Signed-off-by: Sean Christopherson <seanjc@google.com>
Signed-off-by: Isaku Yamahata <isaku.yamahata@intel.com>
Signed-off-by: Binbin Wu <binbin.wu@linux.intel.com>
Message-ID: <20250222014757.897978-2-binbin.wu@linux.intel.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
arch/x86/include/asm/kvm-x86-ops.h
arch/x86/include/asm/kvm_host.h
arch/x86/kvm/irq.c
arch/x86/kvm/lapic.c
arch/x86/kvm/lapic.h
arch/x86/kvm/vmx/main.c
arch/x86/kvm/vmx/tdx.c
arch/x86/kvm/vmx/x86_ops.h

index aae4193f80e3c04b3b3d44f8072d85ecdf73039d..79406bf07a1ce9c43d4eaff188f805bae41ca728 100644 (file)
@@ -116,6 +116,7 @@ KVM_X86_OP_OPTIONAL(pi_start_assignment)
 KVM_X86_OP_OPTIONAL(apicv_pre_state_restore)
 KVM_X86_OP_OPTIONAL(apicv_post_state_restore)
 KVM_X86_OP_OPTIONAL_RET0(dy_apicv_has_pending_interrupt)
+KVM_X86_OP_OPTIONAL(protected_apic_has_interrupt)
 KVM_X86_OP_OPTIONAL(set_hv_timer)
 KVM_X86_OP_OPTIONAL(cancel_hv_timer)
 KVM_X86_OP(setup_mce)
index 3a6373fc58a169c8e64ff1cae012bfddc7aafc34..7fdbe84374f46d018c73a73dd9df7ada93b41954 100644 (file)
@@ -1842,6 +1842,7 @@ struct kvm_x86_ops {
        void (*apicv_pre_state_restore)(struct kvm_vcpu *vcpu);
        void (*apicv_post_state_restore)(struct kvm_vcpu *vcpu);
        bool (*dy_apicv_has_pending_interrupt)(struct kvm_vcpu *vcpu);
+       bool (*protected_apic_has_interrupt)(struct kvm_vcpu *vcpu);
 
        int (*set_hv_timer)(struct kvm_vcpu *vcpu, u64 guest_deadline_tsc,
                            bool *expired);
index 63f66c51975a57e80867154f011a4abed1250757..97d68d8379293b05715d67b6b1cf329127fa7ec3 100644 (file)
@@ -100,6 +100,9 @@ int kvm_cpu_has_interrupt(struct kvm_vcpu *v)
        if (kvm_cpu_has_extint(v))
                return 1;
 
+       if (lapic_in_kernel(v) && v->arch.apic->guest_apic_protected)
+               return kvm_x86_call(protected_apic_has_interrupt)(v);
+
        return kvm_apic_has_interrupt(v) != -1; /* LAPIC */
 }
 EXPORT_SYMBOL_GPL(kvm_cpu_has_interrupt);
index a1cbca31ec3096040cbf75367517d16102c0e981..bbdede07d0630003b79837bfa0bb30e030828095 100644 (file)
@@ -2967,6 +2967,9 @@ int kvm_apic_has_interrupt(struct kvm_vcpu *vcpu)
        if (!kvm_apic_present(vcpu))
                return -1;
 
+       if (apic->guest_apic_protected)
+               return -1;
+
        __apic_update_ppr(apic, &ppr);
        return apic_has_interrupt_for_ppr(apic, ppr);
 }
index 1a8553ebdb42f3d18e47c98b42c9953e5ba5097e..e33c969439f72a8be39927b0740b5fc9a21cba8b 100644 (file)
@@ -65,6 +65,8 @@ struct kvm_lapic {
        bool sw_enabled;
        bool irr_pending;
        bool lvt0_in_nmi_mode;
+       /* Select registers in the vAPIC cannot be read/written. */
+       bool guest_apic_protected;
        /* Number of bits set in ISR. */
        s16 isr_count;
        /* The highest vector set in ISR; if -1 - invalid, must scan ISR. */
index a567865baf9b6c4b61521e2ed5bef26675ae4895..0bf1b71f23333bc4db22d2c5d594d13d6ac058f9 100644 (file)
@@ -62,6 +62,7 @@ static __init int vt_hardware_setup(void)
                vt_x86_ops.set_external_spte = tdx_sept_set_private_spte;
                vt_x86_ops.free_external_spt = tdx_sept_free_private_spt;
                vt_x86_ops.remove_external_spte = tdx_sept_remove_private_spte;
+               vt_x86_ops.protected_apic_has_interrupt = tdx_protected_apic_has_interrupt;
        }
 
        return 0;
index 35f28e1654175adc144428c28ce7dc19c7325977..70d1783fc2d8bd8766ea17f76ed253a89a5ec3ca 100644 (file)
@@ -649,6 +649,7 @@ int tdx_vcpu_create(struct kvm_vcpu *vcpu)
                return -EINVAL;
 
        fpstate_set_confidential(&vcpu->arch.guest_fpu);
+       vcpu->arch.apic->guest_apic_protected = true;
 
        vcpu->arch.efer = EFER_SCE | EFER_LME | EFER_LMA | EFER_NX;
 
@@ -695,6 +696,11 @@ void tdx_vcpu_load(struct kvm_vcpu *vcpu, int cpu)
        local_irq_enable();
 }
 
+bool tdx_protected_apic_has_interrupt(struct kvm_vcpu *vcpu)
+{
+       return pi_has_pending_interrupt(vcpu);
+}
+
 /*
  * Compared to vmx_prepare_switch_to_guest(), there is not much to do
  * as SEAMCALL/SEAMRET calls take care of most of save and restore.
index 2960d95c2c78654b8b4c9d8c0db9ffb69eb851e1..8a2324db350356df35ff6e81f79e2d9f0e5e8b2e 100644 (file)
@@ -135,6 +135,7 @@ int tdx_vcpu_pre_run(struct kvm_vcpu *vcpu);
 fastpath_t tdx_vcpu_run(struct kvm_vcpu *vcpu, bool force_immediate_exit);
 void tdx_prepare_switch_to_guest(struct kvm_vcpu *vcpu);
 void tdx_vcpu_put(struct kvm_vcpu *vcpu);
+bool tdx_protected_apic_has_interrupt(struct kvm_vcpu *vcpu);
 int tdx_handle_exit(struct kvm_vcpu *vcpu,
                enum exit_fastpath_completion fastpath);
 void tdx_get_exit_info(struct kvm_vcpu *vcpu, u32 *reason,
@@ -172,6 +173,7 @@ static inline fastpath_t tdx_vcpu_run(struct kvm_vcpu *vcpu, bool force_immediat
 }
 static inline void tdx_prepare_switch_to_guest(struct kvm_vcpu *vcpu) {}
 static inline void tdx_vcpu_put(struct kvm_vcpu *vcpu) {}
+static inline bool tdx_protected_apic_has_interrupt(struct kvm_vcpu *vcpu) { return false; }
 static inline int tdx_handle_exit(struct kvm_vcpu *vcpu,
                enum exit_fastpath_completion fastpath) { return 0; }
 static inline void tdx_get_exit_info(struct kvm_vcpu *vcpu, u32 *reason, u64 *info1,