* [PATCH v4 1/9] KVM: TDX: Enable Notify VM exit
2026-08-19 9:48 [PATCH v4 0/9] KVM: TDX: Enable VM-DoS Prevention Features for TDX Xiaoyao Li
@ 2026-08-19 9:48 ` Xiaoyao Li
2026-08-19 9:48 ` [PATCH v4 2/9] KVM: TDX: Check if there is valid exit infos based on vp_enter_ret Xiaoyao Li
` (2 subsequent siblings)
3 siblings, 0 replies; 6+ messages in thread
From: Xiaoyao Li @ 2026-08-19 9:48 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini
Cc: Kiryl Shutsemau, Rick Edgecombe, Xiaoyao Li, kvm, linux-kernel,
linux-coco, nik.borisov
Enable Notify VM exit functionality for TDX guests.
Notify VM exit is an existing feature supported by KVM. Userspace can
enable Notify VM exit through KVM_CAP_X86_NOTIFY_VMEXIT when it's
reported as supported. However, KVM reports the support of this CAP just
based on the hardware capability but doesn't differentiate between VMX
and TDX. This leads to the issue that userspace can enable this cap for
TDX guests without getting an error, but the feature is not actually
enabled because KVM doesn't call the TDX module API to program the
relevant TD VMCS fields.
Enable Notify VM exit for TDX guests by:
- Invoking TDX module API calls to set NOTIFY_VM_EXITING and Notify
Window in TD VMCS. It's done in tdx_vcpu_init() where other TD VMCS
bits are set. Since TDX vCPU cannot be reset, it only needs to be
configured once when initializing the TDX vCPU.
- Adding corresponding exit handler for TDX Notify VM Exit.
Notify VM exit can happen when executing the IRET instruction. If the
IRET unblocks the NMI blocking state, bit 12 of the exit qualification
is set. In this case, the VMM needs to restore the "blocked by NMI" state
when it decides to re-enter the guest. For TDX, KVM cannot manage the
GUEST_INTERRUPTIBILITY_INFO and it's TDX module's responsibility to
handle it. Extract the common part without NMI blocking handling into a
helper in common.h so that it can be shared between VMX and TDX.
Note, KVM uses "pre-production" terminology for the feature formally called
Notify VM-Exit. All public versions of the SDM refer to the feature as
Instruction Timeout. This will be remedied in the near future, for now,
use KVM's terminology for consistency.
Note, #2, there is no enumeration bit for Notify VM exit by TDX module
because all TDX modules support it, and allow to set the corresponding
TD VMCS fields as long as the hardware supports the feature.
Fixes: 161d34609f9b ("KVM: TDX: Make TDX VM type supported")
Cc: stable@vger.kernel.org
Signed-off-by: Xiaoyao Li <xiaoyao.li@intel.com>
Reviewed-by: Rick Edgecombe <rick.p.edgecombe@intel.com>
---
Changes in v4:
- rename __vmx_handle_notify() to __vt_handle_notify(), to better
reflect it is a shared helper for both VMX and TDX.
Changes in v3:
- Collect R-b tag from Rick.
Changes in v2:
- Mention the feature name mismatch between KVM and SDM in changelog and
leave the renaming to future, since this patch is targeted for stable
- Extract the common handling into a helper, and put the helper in
common.h instead of refactorin the existing handle_notify() in vmx.h
- Add a note to clarify the feature is always supported by TDX module,
to make Sashiko happy.
---
arch/x86/kvm/vmx/common.h | 19 +++++++++++++++++++
arch/x86/kvm/vmx/tdx.c | 10 ++++++++++
arch/x86/kvm/vmx/vmx.c | 13 +------------
3 files changed, 30 insertions(+), 12 deletions(-)
diff --git a/arch/x86/kvm/vmx/common.h b/arch/x86/kvm/vmx/common.h
index 08005676702c..7fce1bbabc78 100644
--- a/arch/x86/kvm/vmx/common.h
+++ b/arch/x86/kvm/vmx/common.h
@@ -4,6 +4,7 @@
#include <linux/kvm_host.h>
#include <asm/posted_intr.h>
+#include <asm/vmx.h>
#include "mmu.h"
@@ -183,6 +184,24 @@ static inline void __vmx_deliver_posted_interrupt(struct kvm_vcpu *vcpu,
kvm_vcpu_trigger_posted_interrupt(vcpu, POSTED_INTR_VECTOR);
}
+static inline int __vt_handle_notify(struct kvm_vcpu *vcpu,
+ unsigned long exit_qual)
+{
+ bool context_invalid = exit_qual & NOTIFY_VM_CONTEXT_INVALID;
+
+ ++vcpu->stat.notify_window_exits;
+
+ if (vcpu->kvm->arch.notify_vmexit_flags & KVM_X86_NOTIFY_VMEXIT_USER ||
+ context_invalid) {
+ vcpu->run->exit_reason = KVM_EXIT_NOTIFY;
+ vcpu->run->notify.flags = context_invalid ?
+ KVM_NOTIFY_CONTEXT_INVALID : 0;
+ return 0;
+ }
+
+ return 1;
+}
+
noinstr void vmx_handle_nmi(struct kvm_vcpu *vcpu);
#endif /* __KVM_X86_VMX_COMMON_H */
diff --git a/arch/x86/kvm/vmx/tdx.c b/arch/x86/kvm/vmx/tdx.c
index b272c20586a7..d557840687d2 100644
--- a/arch/x86/kvm/vmx/tdx.c
+++ b/arch/x86/kvm/vmx/tdx.c
@@ -2126,6 +2126,9 @@ int tdx_handle_exit(struct kvm_vcpu *vcpu, fastpath_t fastpath)
* - If it's not an MSMI, no need to do anything here.
*/
return 1;
+ case EXIT_REASON_NOTIFY:
+ /* NMI blocking state is handled by TDX module */
+ return __vt_handle_notify(vcpu, vmx_get_exit_qual(vcpu));
default:
break;
}
@@ -3154,6 +3157,13 @@ static int tdx_vcpu_init(struct kvm_vcpu *vcpu, struct kvm_tdx_cmd *cmd)
td_vmcs_write64(tdx, POSTED_INTR_DESC_ADDR, __pa(&tdx->vt.pi_desc));
td_vmcs_setbit32(tdx, PIN_BASED_VM_EXEC_CONTROL, PIN_BASED_POSTED_INTR);
+ if (kvm_notify_vmexit_enabled(vcpu->kvm)) {
+ td_vmcs_setbit32(tdx, SECONDARY_VM_EXEC_CONTROL,
+ SECONDARY_EXEC_NOTIFY_VM_EXITING);
+ td_vmcs_write32(tdx, NOTIFY_WINDOW,
+ vcpu->kvm->arch.notify_window);
+ }
+
tdx->state = VCPU_TD_STATE_INITIALIZED;
return 0;
diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
index e3bfe6aca1a0..35ac9ddffaf4 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -6279,9 +6279,6 @@ static int handle_bus_lock_vmexit(struct kvm_vcpu *vcpu)
static int handle_notify(struct kvm_vcpu *vcpu)
{
unsigned long exit_qual = vmx_get_exit_qual(vcpu);
- bool context_invalid = exit_qual & NOTIFY_VM_CONTEXT_INVALID;
-
- ++vcpu->stat.notify_window_exits;
/*
* Notify VM exit happened while executing iret from NMI,
@@ -6291,15 +6288,7 @@ static int handle_notify(struct kvm_vcpu *vcpu)
vmcs_set_bits(GUEST_INTERRUPTIBILITY_INFO,
GUEST_INTR_STATE_NMI);
- if (vcpu->kvm->arch.notify_vmexit_flags & KVM_X86_NOTIFY_VMEXIT_USER ||
- context_invalid) {
- vcpu->run->exit_reason = KVM_EXIT_NOTIFY;
- vcpu->run->notify.flags = context_invalid ?
- KVM_NOTIFY_CONTEXT_INVALID : 0;
- return 0;
- }
-
- return 1;
+ return __vt_handle_notify(vcpu, exit_qual);
}
static int vmx_get_msr_imm_reg(struct kvm_vcpu *vcpu)
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH v4 2/9] KVM: TDX: Check if there is valid exit infos based on vp_enter_ret
2026-08-19 9:48 [PATCH v4 0/9] KVM: TDX: Enable VM-DoS Prevention Features for TDX Xiaoyao Li
2026-08-19 9:48 ` [PATCH v4 1/9] KVM: TDX: Enable Notify VM exit Xiaoyao Li
@ 2026-08-19 9:48 ` Xiaoyao Li
2026-08-19 16:39 ` Edgecombe, Rick P
2026-08-19 9:48 ` [PATCH v4 3/9] KVM: TDX: Set bits 31:16 to 0 for the synthesized Exit Reason Xiaoyao Li
[not found] ` <20260819094903.3060020-6-xiaoyao.li@intel.com>
3 siblings, 1 reply; 6+ messages in thread
From: Xiaoyao Li @ 2026-08-19 9:48 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini
Cc: Kiryl Shutsemau, Rick Edgecombe, Xiaoyao Li, kvm, linux-kernel,
linux-coco, nik.borisov
Check if there is valid exit info based on vp_enter_ret instead of relying
on the clobbered Exit Reason, in tdx_get_exit_info().
Current KVM uses "Exit Reason is not equal to the synthesized invalid
Exit Reason, -1u," as the condition to identify there is a real TD Exit
and valid exit infos. However, there is one issue with this approach:
KVM updates the Exit Reason to the synthesized invalid Exit Reason for
real EPT MISCONFIG as well. This is a false positive for real EPT
MISCONFIG, which has valid exit infos.
Though the issue can be addressed by changing the handling for real EPT
MISCONFIG to not update the Exit Reason to the synthesized one, relying
on the clobbered Exit Reason itself is brittle. Instead, check
vp_enter_ret directly to identify if it is a valid Exit Reason.
Fixes: da407fe45908 ("KVM: TDX: Handle EPT violation/misconfig exit")
Cc: stable@vger.kernel.org
Suggested-by: Sean Christopherson <seanjc@google.com>
Signed-off-by: Xiaoyao Li <xiaoyao.li@intel.com>
---
Changes in v4:
- new patch.
---
arch/x86/kvm/vmx/tdx.c | 22 ++++++++++++++--------
1 file changed, 14 insertions(+), 8 deletions(-)
diff --git a/arch/x86/kvm/vmx/tdx.c b/arch/x86/kvm/vmx/tdx.c
index d557840687d2..1dead84e6077 100644
--- a/arch/x86/kvm/vmx/tdx.c
+++ b/arch/x86/kvm/vmx/tdx.c
@@ -921,21 +921,27 @@ static __always_inline u32 tdcall_to_vmx_exit_reason(struct kvm_vcpu *vcpu)
return EXIT_REASON_TDCALL;
}
-static __always_inline u32 tdx_to_vmx_exit_reason(struct kvm_vcpu *vcpu)
+static __always_inline bool tdx_is_exit_reason_valid(u64 vp_enter_ret)
{
- struct vcpu_tdx *tdx = to_tdx(vcpu);
- u32 exit_reason;
-
- switch (tdx->vp_enter_ret & TDX_SEAMCALL_STATUS_MASK) {
+ switch (vp_enter_ret & TDX_SEAMCALL_STATUS_MASK) {
case TDX_SUCCESS:
case TDX_NON_RECOVERABLE_VCPU:
case TDX_NON_RECOVERABLE_TD:
case TDX_NON_RECOVERABLE_TD_NON_ACCESSIBLE:
case TDX_NON_RECOVERABLE_TD_WRONG_APIC_MODE:
- break;
+ return true;
default:
- return -1u;
+ return false;
}
+}
+
+static __always_inline u32 tdx_to_vmx_exit_reason(struct kvm_vcpu *vcpu)
+{
+ struct vcpu_tdx *tdx = to_tdx(vcpu);
+ u32 exit_reason;
+
+ if (!tdx_is_exit_reason_valid(tdx->vp_enter_ret))
+ return -1u;
exit_reason = tdx->vp_enter_ret;
@@ -2144,7 +2150,7 @@ void tdx_get_exit_info(struct kvm_vcpu *vcpu, u32 *reason,
struct vcpu_tdx *tdx = to_tdx(vcpu);
*reason = tdx->vt.exit_reason.full;
- if (*reason != -1u) {
+ if (tdx_is_exit_reason_valid(tdx->vp_enter_ret)) {
*info1 = vmx_get_exit_qual(vcpu);
*info2 = tdx->ext_exit_qualification;
*intr_info = vmx_get_intr_info(vcpu);
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH v4 3/9] KVM: TDX: Set bits 31:16 to 0 for the synthesized Exit Reason
2026-08-19 9:48 [PATCH v4 0/9] KVM: TDX: Enable VM-DoS Prevention Features for TDX Xiaoyao Li
2026-08-19 9:48 ` [PATCH v4 1/9] KVM: TDX: Enable Notify VM exit Xiaoyao Li
2026-08-19 9:48 ` [PATCH v4 2/9] KVM: TDX: Check if there is valid exit infos based on vp_enter_ret Xiaoyao Li
@ 2026-08-19 9:48 ` Xiaoyao Li
[not found] ` <20260819094903.3060020-6-xiaoyao.li@intel.com>
3 siblings, 0 replies; 6+ messages in thread
From: Xiaoyao Li @ 2026-08-19 9:48 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini
Cc: Kiryl Shutsemau, Rick Edgecombe, Xiaoyao Li, kvm, linux-kernel,
linux-coco, nik.borisov
Set bits 31:16 to 0 instead of all-1s for KVM's synthesized Exit Reason.
KVM is going to support Bus Lock VM exit for TDX, after which bit 26 of
the Exit Reason becomes meaningful and indicates that a bus lock happened.
The existing synthesized Exit Reason, -1u, will cause a false positive in
that case. Change the synthesized Exit Reason from -1u to U16_MAX, so that
bits 31:16 are set to 0. This also avoids the potential issues when other
bits in 31:16 become valid in the future.
As a bonus, the check for synthesized Exit Reason in tdx_failed_vmentry()
becomes unnecessary. Just drop it.
Cc: stable@vger.kernel.org
Signed-off-by: Xiaoyao Li <xiaoyao.li@intel.com>
Reviewed-by: Rick Edgecombe <rick.p.edgecombe@intel.com>
---
Note, the checking of tdx_failed_vmentry() looks to miss the case where
a real EPT_MISCONFIG happens with failed_vmentry being set. First, in
practice, EPT_MISCONFIG cannot happen with failed_vmentry being set.
Second, even if it can, this is a pre-existing issue and the next
patch can address it.
Changes in v4:
- Collect R-b from Rick.
Changes in v3:
- split from the patch 2 in v2.
- define a MARCO for the synthesized invalid Exit Reason.
---
arch/x86/kvm/vmx/tdx.c | 15 +++++++++++----
1 file changed, 11 insertions(+), 4 deletions(-)
diff --git a/arch/x86/kvm/vmx/tdx.c b/arch/x86/kvm/vmx/tdx.c
index 1dead84e6077..4e275cb6927a 100644
--- a/arch/x86/kvm/vmx/tdx.c
+++ b/arch/x86/kvm/vmx/tdx.c
@@ -935,13 +935,21 @@ static __always_inline bool tdx_is_exit_reason_valid(u64 vp_enter_ret)
}
}
+/* Synthesized invalid Exit Reason */
+#define TDX_INVALID_EXIT_REASON U16_MAX
+
static __always_inline u32 tdx_to_vmx_exit_reason(struct kvm_vcpu *vcpu)
{
struct vcpu_tdx *tdx = to_tdx(vcpu);
u32 exit_reason;
+ /*
+ * Return the synthesized invalid Exit Reason, as the TDX module
+ * never attempted to run the vCPU, i.e. the Exit Reason is undefined,
+ * but this is NOT a failed VM-Enter.
+ */
if (!tdx_is_exit_reason_valid(tdx->vp_enter_ret))
- return -1u;
+ return TDX_INVALID_EXIT_REASON;
exit_reason = tdx->vp_enter_ret;
@@ -956,7 +964,7 @@ static __always_inline u32 tdx_to_vmx_exit_reason(struct kvm_vcpu *vcpu)
* Defer KVM_BUG_ON() until tdx_handle_exit() because this is in
* non-instrumentable code with interrupts disabled.
*/
- return -1u;
+ return TDX_INVALID_EXIT_REASON;
default:
break;
}
@@ -987,8 +995,7 @@ static noinstr void tdx_vcpu_enter_exit(struct kvm_vcpu *vcpu)
static bool tdx_failed_vmentry(struct kvm_vcpu *vcpu)
{
- return vmx_get_exit_reason(vcpu).failed_vmentry &&
- vmx_get_exit_reason(vcpu).full != -1u;
+ return vmx_get_exit_reason(vcpu).failed_vmentry;
}
static fastpath_t tdx_exit_handlers_fastpath(struct kvm_vcpu *vcpu)
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread