Skip to contentSkip to conversation

KVM TDX basic feature support

PATCH In review [email protected] 21 participants 695 messages Feb 26, 2024 – Jul 18, 2024 Page 5 of 6

Subsystems (8)
Documentation x86 KVM Kselftest +4 more subsystems
lore
Share & tools
lore
#533 lore raw

PATCH v19 90/130 KVM: x86: Assume timer IRQ was injected if APIC state is proteced

In reply to [email protected]

From: Sean Christopherson <[email protected]>

If APIC state is protected, i.e. the vCPU is a TDX guest, assume a timer IRQ was injected when deciding whether or not to busy wait in the "timer advanced" path. The "real" vIRR is not readable/writable, so trying to query for a pending timer IRQ will return garbage.

Note, TDX can scour the PIR if it wants to be more precise and skip the "wait" call entirely.

---
 arch/x86/kvm/lapic.c | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)
arch/x86/kvm/lapic.c +10 -1
index e8034f2f2dd1..8025c7f614e0 100644
@@ -1774,8 +1774,17 @@ static void apic_update_lvtt(struct kvm_lapic *apic)
 static bool lapic_timer_int_injected(struct kvm_vcpu *vcpu)
 {
 	struct kvm_lapic *apic = vcpu->arch.apic;
-	u32 reg = kvm_lapic_get_reg(apic, APIC_LVTT);
+	u32 reg;
 
+	/*
+	 * Assume a timer IRQ was "injected" if the APIC is protected.  KVM's
+	 * copy of the vIRR is bogus, it's the responsibility of the caller to
+	 * precisely check whether or not a timer IRQ is pending.
+	 */
+	if (apic->guest_apic_protected)
+		return true;
+
+	reg  = kvm_lapic_get_reg(apic, APIC_LVTT);
 	if (kvm_apic_hw_enabled(apic)) {
 		int vec = reg & APIC_VECTOR_MASK;
 		void *bitmap = apic->regs + APIC_ISR;
Signature · 1 line
2.25.1
#534 lore raw

PATCH v19 91/130 KVM: TDX: remove use of struct vcpu_vmx from posted_interrupt.c

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

As TDX will use posted_interrupt.c, the use of struct vcpu_vmx is a blocker. Because the members of struct pi_desc pi_desc and struct list_head pi_wakeup_list are only used in posted_interrupt.c, introduce common structure, struct vcpu_pi, make vcpu_vmx and vcpu_tdx has same layout in the top of structure.

To minimize the diff size, avoid code conversion like, vmx->pi_desc => vmx->common->pi_desc. Instead add compile time check if the layout is expected.

---
 arch/x86/kvm/vmx/posted_intr.c | 41 ++++++++++++++++++++++++++--------
 arch/x86/kvm/vmx/posted_intr.h | 11 +++++++++
 arch/x86/kvm/vmx/tdx.c         |  1 +
 arch/x86/kvm/vmx/tdx.h         |  8 +++++++
 arch/x86/kvm/vmx/vmx.h         | 14 +++++++-----
 5 files changed, 60 insertions(+), 15 deletions(-)
arch/x86/kvm/vmx/posted_intr.c +32 -9
index af662312fd07..b66add9da0f3 100644
@@ -11,6 +11,7 @@
 #include "posted_intr.h"
 #include "trace.h"
 #include "vmx.h"
+#include "tdx.h"
 
 /*
  * Maintain a per-CPU list of vCPUs that need to be awakened by wakeup_handler()
@@ -31,9 +32,29 @@ static DEFINE_PER_CPU(struct list_head, wakeup_vcpus_on_cpu);
  */
 static DEFINE_PER_CPU(raw_spinlock_t, wakeup_vcpus_on_cpu_lock);
 
+/*
+ * The layout of the head of struct vcpu_vmx and struct vcpu_tdx must match with
+ * struct vcpu_pi.
+ */
+static_assert(offsetof(struct vcpu_pi, pi_desc) ==
+	      offsetof(struct vcpu_vmx, pi_desc));
+static_assert(offsetof(struct vcpu_pi, pi_wakeup_list) ==
+	      offsetof(struct vcpu_vmx, pi_wakeup_list));
+#ifdef CONFIG_INTEL_TDX_HOST
+static_assert(offsetof(struct vcpu_pi, pi_desc) ==
+	      offsetof(struct vcpu_tdx, pi_desc));
+static_assert(offsetof(struct vcpu_pi, pi_wakeup_list) ==
+	      offsetof(struct vcpu_tdx, pi_wakeup_list));
+#endif
+
+static inline struct vcpu_pi *vcpu_to_pi(struct kvm_vcpu *vcpu)
+{
+	return (struct vcpu_pi *)vcpu;
+}
+
 static inline struct pi_desc *vcpu_to_pi_desc(struct kvm_vcpu *vcpu)
 {
-	return &(to_vmx(vcpu)->pi_desc);
+	return &vcpu_to_pi(vcpu)->pi_desc;
 }
 
 static int pi_try_set_control(struct pi_desc *pi_desc, u64 *pold, u64 new)
@@ -52,8 +73,8 @@ static int pi_try_set_control(struct pi_desc *pi_desc, u64 *pold, u64 new)
 
 void vmx_vcpu_pi_load(struct kvm_vcpu *vcpu, int cpu)
 {
-	struct pi_desc *pi_desc = vcpu_to_pi_desc(vcpu);
-	struct vcpu_vmx *vmx = to_vmx(vcpu);
+	struct vcpu_pi *vcpu_pi = vcpu_to_pi(vcpu);
+	struct pi_desc *pi_desc = &vcpu_pi->pi_desc;
 	struct pi_desc old, new;
 	unsigned long flags;
 	unsigned int dest;
@@ -90,7 +111,7 @@ void vmx_vcpu_pi_load(struct kvm_vcpu *vcpu, int cpu)
 	 */
 	if (pi_desc->nv == POSTED_INTR_WAKEUP_VECTOR) {
 		raw_spin_lock(&per_cpu(wakeup_vcpus_on_cpu_lock, vcpu->cpu));
-		list_del(&vmx->pi_wakeup_list);
+		list_del(&vcpu_pi->pi_wakeup_list);
 		raw_spin_unlock(&per_cpu(wakeup_vcpus_on_cpu_lock, vcpu->cpu));
 	}
 
@@ -145,15 +166,15 @@ static bool vmx_can_use_vtd_pi(struct kvm *kvm)
  */
 static void pi_enable_wakeup_handler(struct kvm_vcpu *vcpu)
 {
-	struct pi_desc *pi_desc = vcpu_to_pi_desc(vcpu);
-	struct vcpu_vmx *vmx = to_vmx(vcpu);
+	struct vcpu_pi *vcpu_pi = vcpu_to_pi(vcpu);
+	struct pi_desc *pi_desc = &vcpu_pi->pi_desc;
 	struct pi_desc old, new;
 	unsigned long flags;
 
 	local_irq_save(flags);
 
 	raw_spin_lock(&per_cpu(wakeup_vcpus_on_cpu_lock, vcpu->cpu));
-	list_add_tail(&vmx->pi_wakeup_list,
+	list_add_tail(&vcpu_pi->pi_wakeup_list,
 		      &per_cpu(wakeup_vcpus_on_cpu, vcpu->cpu));
 	raw_spin_unlock(&per_cpu(wakeup_vcpus_on_cpu_lock, vcpu->cpu));
 
@@ -190,7 +211,8 @@ static bool vmx_needs_pi_wakeup(struct kvm_vcpu *vcpu)
 	 * notification vector is switched to the one that calls
 	 * back to the pi_wakeup_handler() function.
 	 */
-	return vmx_can_use_ipiv(vcpu) || vmx_can_use_vtd_pi(vcpu->kvm);
+	return (vmx_can_use_ipiv(vcpu) && !is_td_vcpu(vcpu)) ||
+		vmx_can_use_vtd_pi(vcpu->kvm);
 }
 
 void vmx_vcpu_pi_put(struct kvm_vcpu *vcpu)
@@ -200,7 +222,8 @@ void vmx_vcpu_pi_put(struct kvm_vcpu *vcpu)
 	if (!vmx_needs_pi_wakeup(vcpu))
 		return;
 
-	if (kvm_vcpu_is_blocking(vcpu) && !vmx_interrupt_blocked(vcpu))
+	if (kvm_vcpu_is_blocking(vcpu) &&
+	    (is_td_vcpu(vcpu) || !vmx_interrupt_blocked(vcpu)))
 		pi_enable_wakeup_handler(vcpu);
 
 	/*
arch/x86/kvm/vmx/posted_intr.h +11 -0
index 26992076552e..2fe8222308b2 100644
@@ -94,6 +94,17 @@ static inline bool pi_test_sn(struct pi_desc *pi_desc)
 			(unsigned long *)&pi_desc->control);
 }
 
+struct vcpu_pi {
+	struct kvm_vcpu	vcpu;
+
+	/* Posted interrupt descriptor */
+	struct pi_desc pi_desc;
+
+	/* Used if this vCPU is waiting for PI notification wakeup. */
+	struct list_head pi_wakeup_list;
+	/* Until here common layout betwwn vcpu_vmx and vcpu_tdx. */
+};
+
 void vmx_vcpu_pi_load(struct kvm_vcpu *vcpu, int cpu);
 void vmx_vcpu_pi_put(struct kvm_vcpu *vcpu);
 void pi_wakeup_handler(void);
arch/x86/kvm/vmx/tdx.c +1 -0
index a5b52aa6d153..1da58c36217c 100644
@@ -584,6 +584,7 @@ int tdx_vcpu_create(struct kvm_vcpu *vcpu)
 
 	fpstate_set_confidential(&vcpu->arch.guest_fpu);
 	vcpu->arch.apic->guest_apic_protected = true;
+	INIT_LIST_HEAD(&tdx->pi_wakeup_list);
 
 	vcpu->arch.efer = EFER_SCE | EFER_LME | EFER_LMA | EFER_NX;
 
arch/x86/kvm/vmx/tdx.h +8 -0
index 7f8c78f06508..eaffa7384725 100644
@@ -4,6 +4,7 @@
 
 #ifdef CONFIG_INTEL_TDX_HOST
 
+#include "posted_intr.h"
 #include "pmu_intel.h"
 #include "tdx_ops.h"
 
@@ -69,6 +70,13 @@ union tdx_exit_reason {
 struct vcpu_tdx {
 	struct kvm_vcpu	vcpu;
 
+	/* Posted interrupt descriptor */
+	struct pi_desc pi_desc;
+
+	/* Used if this vCPU is waiting for PI notification wakeup. */
+	struct list_head pi_wakeup_list;
+	/* Until here same layout to struct vcpu_pi. */
+
 	unsigned long tdvpr_pa;
 	unsigned long *tdvpx_pa;
 	bool td_vcpu_created;
arch/x86/kvm/vmx/vmx.h +8 -6
index 79ff54f08fee..634a9a250b95 100644
@@ -235,6 +235,14 @@ struct nested_vmx {
 
 struct vcpu_vmx {
 	struct kvm_vcpu       vcpu;
+
+	/* Posted interrupt descriptor */
+	struct pi_desc pi_desc;
+
+	/* Used if this vCPU is waiting for PI notification wakeup. */
+	struct list_head pi_wakeup_list;
+	/* Until here same layout to struct vcpu_pi. */
+
 	u8                    fail;
 	u8		      x2apic_msr_bitmap_mode;
 
@@ -304,12 +312,6 @@ struct vcpu_vmx {
 
 	union vmx_exit_reason exit_reason;
 
-	/* Posted interrupt descriptor */
-	struct pi_desc pi_desc;
-
-	/* Used if this vCPU is waiting for PI notification wakeup. */
-	struct list_head pi_wakeup_list;
-
 	/* Support for a guest hypervisor (nested VMX) */
 	struct nested_vmx nested;
 
Signature · 1 line
2.25.1
#535 lore raw

Re: [PATCH v19 091/130] KVM: TDX: remove use of struct vcpu_vmx from posted_interrupt.c

Quoted 4 lines · On 2/26/2024 4:26 PM, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

As TDX will use posted_interrupt.c, the use of struct vcpu_vmx is a blocker. Because the members of

Extra "of"

Quoted 127 lines

struct pi_desc pi_desc and struct
list_head pi_wakeup_list are only used in posted_interrupt.c, introduce common structure, struct vcpu_pi, make vcpu_vmx and vcpu_tdx has same layout in the top of structure.

To minimize the diff size, avoid code conversion like, vmx->pi_desc => vmx->common->pi_desc. Instead add compile time check if the layout is expected.

---
  arch/x86/kvm/vmx/posted_intr.c | 41 ++++++++++++++++++++++++++--------
  arch/x86/kvm/vmx/posted_intr.h | 11 +++++++++
  arch/x86/kvm/vmx/tdx.c         |  1 +
  arch/x86/kvm/vmx/tdx.h         |  8 +++++++
  arch/x86/kvm/vmx/vmx.h         | 14 +++++++-----
  5 files changed, 60 insertions(+), 15 deletions(-)
arch/x86/kvm/vmx/posted_intr.c +32 -9
index af662312fd07..b66add9da0f3 100644
@@ -11,6 +11,7 @@
  #include "posted_intr.h"
  #include "trace.h"
  #include "vmx.h"
+#include "tdx.h"
 
  /*
   * Maintain a per-CPU list of vCPUs that need to be awakened by wakeup_handler()
@@ -31,9 +32,29 @@ static DEFINE_PER_CPU(struct list_head, wakeup_vcpus_on_cpu);
   */
  static DEFINE_PER_CPU(raw_spinlock_t, wakeup_vcpus_on_cpu_lock);
 
+/*
+ * The layout of the head of struct vcpu_vmx and struct vcpu_tdx must match with
+ * struct vcpu_pi.
+ */
+static_assert(offsetof(struct vcpu_pi, pi_desc) ==
+	      offsetof(struct vcpu_vmx, pi_desc));
+static_assert(offsetof(struct vcpu_pi, pi_wakeup_list) ==
+	      offsetof(struct vcpu_vmx, pi_wakeup_list));
+#ifdef CONFIG_INTEL_TDX_HOST
+static_assert(offsetof(struct vcpu_pi, pi_desc) ==
+	      offsetof(struct vcpu_tdx, pi_desc));
+static_assert(offsetof(struct vcpu_pi, pi_wakeup_list) ==
+	      offsetof(struct vcpu_tdx, pi_wakeup_list));
+#endif
+
+static inline struct vcpu_pi *vcpu_to_pi(struct kvm_vcpu *vcpu)
+{
+	return (struct vcpu_pi *)vcpu;
+}
+
  static inline struct pi_desc *vcpu_to_pi_desc(struct kvm_vcpu *vcpu)
  {
-	return &(to_vmx(vcpu)->pi_desc);
+	return &vcpu_to_pi(vcpu)->pi_desc;
  }
 
  static int pi_try_set_control(struct pi_desc *pi_desc, u64 *pold, u64 new)
@@ -52,8 +73,8 @@ static int pi_try_set_control(struct pi_desc *pi_desc, u64 *pold, u64 new)
 
  void vmx_vcpu_pi_load(struct kvm_vcpu *vcpu, int cpu)
  {
-	struct pi_desc *pi_desc = vcpu_to_pi_desc(vcpu);
-	struct vcpu_vmx *vmx = to_vmx(vcpu);
+	struct vcpu_pi *vcpu_pi = vcpu_to_pi(vcpu);
+	struct pi_desc *pi_desc = &vcpu_pi->pi_desc;
  	struct pi_desc old, new;
  	unsigned long flags;
  	unsigned int dest;
@@ -90,7 +111,7 @@ void vmx_vcpu_pi_load(struct kvm_vcpu *vcpu, int cpu)
  	 */
  	if (pi_desc->nv == POSTED_INTR_WAKEUP_VECTOR) {
  		raw_spin_lock(&per_cpu(wakeup_vcpus_on_cpu_lock, vcpu->cpu));
-		list_del(&vmx->pi_wakeup_list);
+		list_del(&vcpu_pi->pi_wakeup_list);
  		raw_spin_unlock(&per_cpu(wakeup_vcpus_on_cpu_lock, vcpu->cpu));
  	}
 
@@ -145,15 +166,15 @@ static bool vmx_can_use_vtd_pi(struct kvm *kvm)
   */
  static void pi_enable_wakeup_handler(struct kvm_vcpu *vcpu)
  {
-	struct pi_desc *pi_desc = vcpu_to_pi_desc(vcpu);
-	struct vcpu_vmx *vmx = to_vmx(vcpu);
+	struct vcpu_pi *vcpu_pi = vcpu_to_pi(vcpu);
+	struct pi_desc *pi_desc = &vcpu_pi->pi_desc;
  	struct pi_desc old, new;
  	unsigned long flags;
 
  	local_irq_save(flags);
 
  	raw_spin_lock(&per_cpu(wakeup_vcpus_on_cpu_lock, vcpu->cpu));
-	list_add_tail(&vmx->pi_wakeup_list,
+	list_add_tail(&vcpu_pi->pi_wakeup_list,
  		      &per_cpu(wakeup_vcpus_on_cpu, vcpu->cpu));
  	raw_spin_unlock(&per_cpu(wakeup_vcpus_on_cpu_lock, vcpu->cpu));
 
@@ -190,7 +211,8 @@ static bool vmx_needs_pi_wakeup(struct kvm_vcpu *vcpu)
  	 * notification vector is switched to the one that calls
  	 * back to the pi_wakeup_handler() function.
  	 */
-	return vmx_can_use_ipiv(vcpu) || vmx_can_use_vtd_pi(vcpu->kvm);
+	return (vmx_can_use_ipiv(vcpu) && !is_td_vcpu(vcpu)) ||
+		vmx_can_use_vtd_pi(vcpu->kvm);
  }
 
  void vmx_vcpu_pi_put(struct kvm_vcpu *vcpu)
@@ -200,7 +222,8 @@ void vmx_vcpu_pi_put(struct kvm_vcpu *vcpu)
  	if (!vmx_needs_pi_wakeup(vcpu))
  		return;
 
-	if (kvm_vcpu_is_blocking(vcpu) && !vmx_interrupt_blocked(vcpu))
+	if (kvm_vcpu_is_blocking(vcpu) &&
+	    (is_td_vcpu(vcpu) || !vmx_interrupt_blocked(vcpu)))
  		pi_enable_wakeup_handler(vcpu);
 
  	/*
arch/x86/kvm/vmx/posted_intr.h +0 -0
index 26992076552e..2fe8222308b2 100644
@@ -94,6 +94,17 @@ static inline bool pi_test_sn(struct pi_desc *pi_desc)
  			(unsigned long *)&pi_desc->control);
  }

arch/x86/kvm/vmx/posted_intr.h static inline bool pi_test_sn(struct pi_desc *pi_desc) · line 105

Quoted 10 lines
arch/x86/kvm/vmx/posted_intr.h +9 -0
 
+struct vcpu_pi {
+	struct kvm_vcpu	vcpu;
+
+	/* Posted interrupt descriptor */
+	struct pi_desc pi_desc;
+
+	/* Used if this vCPU is waiting for PI notification wakeup. */
+	struct list_head pi_wakeup_list;
+	/* Until here common layout betwwn vcpu_vmx and vcpu_tdx. */

s/betwwn/between

Also, in pi_wakeup_handler(), it is still using struct vcpu_vmx, but it could be vcpu_tdx.
Functionally it is OK, however, since you have added vcpu_pi, should it use vcpu_pi instead of vcpu_vmx in pi_wakeup_handler()?

Quoted 75 lines
arch/x86/kvm/vmx/posted_intr.h +2 -0
+};
+
  void vmx_vcpu_pi_load(struct kvm_vcpu *vcpu, int cpu);
  void vmx_vcpu_pi_put(struct kvm_vcpu *vcpu);
  void pi_wakeup_handler(void);
arch/x86/kvm/vmx/tdx.c +1 -0
index a5b52aa6d153..1da58c36217c 100644
@@ -584,6 +584,7 @@ int tdx_vcpu_create(struct kvm_vcpu *vcpu)
 
  	fpstate_set_confidential(&vcpu->arch.guest_fpu);
  	vcpu->arch.apic->guest_apic_protected = true;
+	INIT_LIST_HEAD(&tdx->pi_wakeup_list);
 
  	vcpu->arch.efer = EFER_SCE | EFER_LME | EFER_LMA | EFER_NX;
 
arch/x86/kvm/vmx/tdx.h +8 -0
index 7f8c78f06508..eaffa7384725 100644
@@ -4,6 +4,7 @@
 
  #ifdef CONFIG_INTEL_TDX_HOST
 
+#include "posted_intr.h"
  #include "pmu_intel.h"
  #include "tdx_ops.h"
 
@@ -69,6 +70,13 @@ union tdx_exit_reason {
  struct vcpu_tdx {
  	struct kvm_vcpu	vcpu;
 
+	/* Posted interrupt descriptor */
+	struct pi_desc pi_desc;
+
+	/* Used if this vCPU is waiting for PI notification wakeup. */
+	struct list_head pi_wakeup_list;
+	/* Until here same layout to struct vcpu_pi. */
+
  	unsigned long tdvpr_pa;
  	unsigned long *tdvpx_pa;
  	bool td_vcpu_created;
arch/x86/kvm/vmx/vmx.h +8 -6
index 79ff54f08fee..634a9a250b95 100644
@@ -235,6 +235,14 @@ struct nested_vmx {
 
  struct vcpu_vmx {
  	struct kvm_vcpu       vcpu;
+
+	/* Posted interrupt descriptor */
+	struct pi_desc pi_desc;
+
+	/* Used if this vCPU is waiting for PI notification wakeup. */
+	struct list_head pi_wakeup_list;
+	/* Until here same layout to struct vcpu_pi. */
+
  	u8                    fail;
  	u8		      x2apic_msr_bitmap_mode;
 
@@ -304,12 +312,6 @@ struct vcpu_vmx {
 
  	union vmx_exit_reason exit_reason;
 
-	/* Posted interrupt descriptor */
-	struct pi_desc pi_desc;
-
-	/* Used if this vCPU is waiting for PI notification wakeup. */
-	struct list_head pi_wakeup_list;
-
  	/* Support for a guest hypervisor (nested VMX) */
  	struct nested_vmx nested;
 
#536 lore raw

Re: [PATCH v19 091/130] KVM: TDX: remove use of struct vcpu_vmx from posted_interrupt.c

Quoted 138 lines · Binbin Wu
Quoted 4 lines · On 2/26/2024 4:26 PM, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

As TDX will use posted_interrupt.c, the use of struct vcpu_vmx is a blocker. Because the members of

Extra "of"

Quoted 115 lines

struct pi_desc pi_desc and struct
list_head pi_wakeup_list are only used in posted_interrupt.c, introduce common structure, struct vcpu_pi, make vcpu_vmx and vcpu_tdx has same layout in the top of structure.

To minimize the diff size, avoid code conversion like, vmx->pi_desc => vmx->common->pi_desc. Instead add compile time check if the layout is expected.

---
  arch/x86/kvm/vmx/posted_intr.c | 41 ++++++++++++++++++++++++++--------
  arch/x86/kvm/vmx/posted_intr.h | 11 +++++++++
  arch/x86/kvm/vmx/tdx.c         |  1 +
  arch/x86/kvm/vmx/tdx.h         |  8 +++++++
  arch/x86/kvm/vmx/vmx.h         | 14 +++++++-----
  5 files changed, 60 insertions(+), 15 deletions(-)
arch/x86/kvm/vmx/posted_intr.c +32 -9
index af662312fd07..b66add9da0f3 100644
@@ -11,6 +11,7 @@
  #include "posted_intr.h"
  #include "trace.h"
  #include "vmx.h"
+#include "tdx.h"
  /*
   * Maintain a per-CPU list of vCPUs that need to be awakened by wakeup_handler()
@@ -31,9 +32,29 @@ static DEFINE_PER_CPU(struct list_head, wakeup_vcpus_on_cpu);
   */
  static DEFINE_PER_CPU(raw_spinlock_t, wakeup_vcpus_on_cpu_lock);
+/*
+ * The layout of the head of struct vcpu_vmx and struct vcpu_tdx must match with
+ * struct vcpu_pi.
+ */
+static_assert(offsetof(struct vcpu_pi, pi_desc) ==
+	      offsetof(struct vcpu_vmx, pi_desc));
+static_assert(offsetof(struct vcpu_pi, pi_wakeup_list) ==
+	      offsetof(struct vcpu_vmx, pi_wakeup_list));
+#ifdef CONFIG_INTEL_TDX_HOST
+static_assert(offsetof(struct vcpu_pi, pi_desc) ==
+	      offsetof(struct vcpu_tdx, pi_desc));
+static_assert(offsetof(struct vcpu_pi, pi_wakeup_list) ==
+	      offsetof(struct vcpu_tdx, pi_wakeup_list));
+#endif
+
+static inline struct vcpu_pi *vcpu_to_pi(struct kvm_vcpu *vcpu)
+{
+	return (struct vcpu_pi *)vcpu;
+}
+
  static inline struct pi_desc *vcpu_to_pi_desc(struct kvm_vcpu *vcpu)
  {
-	return &(to_vmx(vcpu)->pi_desc);
+	return &vcpu_to_pi(vcpu)->pi_desc;
  }
  static int pi_try_set_control(struct pi_desc *pi_desc, u64 *pold, u64 new)
@@ -52,8 +73,8 @@ static int pi_try_set_control(struct pi_desc *pi_desc, u64 *pold, u64 new)
  void vmx_vcpu_pi_load(struct kvm_vcpu *vcpu, int cpu)
  {
-	struct pi_desc *pi_desc = vcpu_to_pi_desc(vcpu);
-	struct vcpu_vmx *vmx = to_vmx(vcpu);
+	struct vcpu_pi *vcpu_pi = vcpu_to_pi(vcpu);
+	struct pi_desc *pi_desc = &vcpu_pi->pi_desc;
  	struct pi_desc old, new;
  	unsigned long flags;
  	unsigned int dest;
@@ -90,7 +111,7 @@ void vmx_vcpu_pi_load(struct kvm_vcpu *vcpu, int cpu)
  	 */
  	if (pi_desc->nv == POSTED_INTR_WAKEUP_VECTOR) {
  		raw_spin_lock(&per_cpu(wakeup_vcpus_on_cpu_lock, vcpu->cpu));
-		list_del(&vmx->pi_wakeup_list);
+		list_del(&vcpu_pi->pi_wakeup_list);
  		raw_spin_unlock(&per_cpu(wakeup_vcpus_on_cpu_lock, vcpu->cpu));
  	}
@@ -145,15 +166,15 @@ static bool vmx_can_use_vtd_pi(struct kvm *kvm)
   */
  static void pi_enable_wakeup_handler(struct kvm_vcpu *vcpu)
  {
-	struct pi_desc *pi_desc = vcpu_to_pi_desc(vcpu);
-	struct vcpu_vmx *vmx = to_vmx(vcpu);
+	struct vcpu_pi *vcpu_pi = vcpu_to_pi(vcpu);
+	struct pi_desc *pi_desc = &vcpu_pi->pi_desc;
  	struct pi_desc old, new;
  	unsigned long flags;
  	local_irq_save(flags);
  	raw_spin_lock(&per_cpu(wakeup_vcpus_on_cpu_lock, vcpu->cpu));
-	list_add_tail(&vmx->pi_wakeup_list,
+	list_add_tail(&vcpu_pi->pi_wakeup_list,
  		      &per_cpu(wakeup_vcpus_on_cpu, vcpu->cpu));
  	raw_spin_unlock(&per_cpu(wakeup_vcpus_on_cpu_lock, vcpu->cpu));
@@ -190,7 +211,8 @@ static bool vmx_needs_pi_wakeup(struct kvm_vcpu *vcpu)
  	 * notification vector is switched to the one that calls
  	 * back to the pi_wakeup_handler() function.
  	 */
-	return vmx_can_use_ipiv(vcpu) || vmx_can_use_vtd_pi(vcpu->kvm);
+	return (vmx_can_use_ipiv(vcpu) && !is_td_vcpu(vcpu)) ||
+		vmx_can_use_vtd_pi(vcpu->kvm);
  }
  void vmx_vcpu_pi_put(struct kvm_vcpu *vcpu)
@@ -200,7 +222,8 @@ void vmx_vcpu_pi_put(struct kvm_vcpu *vcpu)
  	if (!vmx_needs_pi_wakeup(vcpu))
  		return;
-	if (kvm_vcpu_is_blocking(vcpu) && !vmx_interrupt_blocked(vcpu))
+	if (kvm_vcpu_is_blocking(vcpu) &&
+	    (is_td_vcpu(vcpu) || !vmx_interrupt_blocked(vcpu)))
  		pi_enable_wakeup_handler(vcpu);
  	/*
arch/x86/kvm/vmx/posted_intr.h +0 -0
index 26992076552e..2fe8222308b2 100644
@@ -94,6 +94,17 @@ static inline bool pi_test_sn(struct pi_desc *pi_desc)
  			(unsigned long *)&pi_desc->control);

arch/x86/kvm/vmx/posted_intr.h static inline bool pi_test_sn(struct pi_desc *pi_desc) · line 104

Quoted 10 lines
arch/x86/kvm/vmx/posted_intr.h +9 -0
  }
+struct vcpu_pi {
+	struct kvm_vcpu	vcpu;
+
+	/* Posted interrupt descriptor */
+	struct pi_desc pi_desc;
+
+	/* Used if this vCPU is waiting for PI notification wakeup. */
+	struct list_head pi_wakeup_list;
+	/* Until here common layout betwwn vcpu_vmx and vcpu_tdx. */

s/betwwn/between

Quoted 5 lines

Also, in pi_wakeup_handler(), it is still using struct vcpu_vmx, but it could
be vcpu_tdx.
Functionally it is OK, however, since you have added vcpu_pi, should it use vcpu_pi instead of vcpu_vmx in pi_wakeup_handler()?

Makes sense.

arch/x86/kvm/vmx/posted_intr.c +4 -5
index b66add9da0f3..5b71aef931dc 100644
@@ -243,13 +243,13 @@ void pi_wakeup_handler(void)
        int cpu = smp_processor_id();
        struct list_head *wakeup_list = &per_cpu(wakeup_vcpus_on_cpu, cpu);
        raw_spinlock_t *spinlock = &per_cpu(wakeup_vcpus_on_cpu_lock, cpu);
-       struct vcpu_vmx *vmx;
+       struct vcpu_pi *pi;
 
        raw_spin_lock(spinlock);
-       list_for_each_entry(vmx, wakeup_list, pi_wakeup_list) {
+       list_for_each_entry(pi, wakeup_list, pi_wakeup_list) {
 
-               if (pi_test_on(&vmx->pi_desc))
-                       kvm_vcpu_wake_up(&vmx->vcpu);
+               if (pi_test_on(&pi->pi_desc))
+                       kvm_vcpu_wake_up(&pi->vcpu);
        }
 
-- 

Isaku Yamahata <[email protected]>

#537 lore raw

Re: [PATCH v19 091/130] KVM: TDX: remove use of struct vcpu_vmx from posted_interrupt.c

In reply to [email protected]

arch/x86/kvm/vmx/posted_intr.c static bool vmx_needs_pi_wakeup(struct kvm_vcpu *vcpu) · line 215

Quoted 7 lines · On Mon, Feb 26, 2024 at 12:26:33AM -0800, [email protected] wrote:
arch/x86/kvm/vmx/posted_intr.c +2 -1
@@ -190,7 +211,8 @@ static bool vmx_needs_pi_wakeup(struct kvm_vcpu *vcpu)
 	 * notification vector is switched to the one that calls
 	 * back to the pi_wakeup_handler() function.
 	 */
-	return vmx_can_use_ipiv(vcpu) || vmx_can_use_vtd_pi(vcpu->kvm);
+	return (vmx_can_use_ipiv(vcpu) && !is_td_vcpu(vcpu)) ||
+		vmx_can_use_vtd_pi(vcpu->kvm);

It is better to separate this functional change from the code refactoring.

arch/x86/kvm/vmx/posted_intr.c void vmx_vcpu_pi_put(struct kvm_vcpu *vcpu) · line 226

Quoted 10 lines
arch/x86/kvm/vmx/posted_intr.c +2 -1
 }
 
 void vmx_vcpu_pi_put(struct kvm_vcpu *vcpu)
@@ -200,7 +222,8 @@ void vmx_vcpu_pi_put(struct kvm_vcpu *vcpu)
 	if (!vmx_needs_pi_wakeup(vcpu))
 		return;
 
-	if (kvm_vcpu_is_blocking(vcpu) && !vmx_interrupt_blocked(vcpu))
+	if (kvm_vcpu_is_blocking(vcpu) &&
+	    (is_td_vcpu(vcpu) || !vmx_interrupt_blocked(vcpu)))

Ditto.

This looks incorrect to me. here we assume interrupt is always enabled for TD. But on TDVMCALL(HLT), the guest tells KVM if hlt is called with interrupt disabled. KVM can just check that interrupt status passed from the guest.

Quoted 2 lines
arch/x86/kvm/vmx/posted_intr.c +0 -0
 		pi_enable_wakeup_handler(vcpu);
 
#538 lore raw

Re: [PATCH v19 091/130] KVM: TDX: remove use of struct vcpu_vmx from posted_interrupt.c

Quoted 10 lines · Chao Gao

arch/x86/kvm/vmx/posted_intr.c static bool vmx_needs_pi_wakeup(struct kvm_vcpu *vcpu) · line 215

Quoted 7 lines · On Mon, Feb 26, 2024 at 12:26:33AM -0800, [email protected] wrote:
arch/x86/kvm/vmx/posted_intr.c +2 -1
@@ -190,7 +211,8 @@ static bool vmx_needs_pi_wakeup(struct kvm_vcpu *vcpu)
 	 * notification vector is switched to the one that calls
 	 * back to the pi_wakeup_handler() function.
 	 */
-	return vmx_can_use_ipiv(vcpu) || vmx_can_use_vtd_pi(vcpu->kvm);
+	return (vmx_can_use_ipiv(vcpu) && !is_td_vcpu(vcpu)) ||
+		vmx_can_use_vtd_pi(vcpu->kvm);

It is better to separate this functional change from the code refactoring.

Agreed. Let's split this patch.

Quoted 13 lines

arch/x86/kvm/vmx/posted_intr.c void vmx_vcpu_pi_put(struct kvm_vcpu *vcpu) · line 226

Quoted 10 lines
arch/x86/kvm/vmx/posted_intr.c +2 -1
 }
 
 void vmx_vcpu_pi_put(struct kvm_vcpu *vcpu)
@@ -200,7 +222,8 @@ void vmx_vcpu_pi_put(struct kvm_vcpu *vcpu)
 	if (!vmx_needs_pi_wakeup(vcpu))
 		return;
 
-	if (kvm_vcpu_is_blocking(vcpu) && !vmx_interrupt_blocked(vcpu))
+	if (kvm_vcpu_is_blocking(vcpu) &&
+	    (is_td_vcpu(vcpu) || !vmx_interrupt_blocked(vcpu)))

Ditto.

Quoted 3 lines

This looks incorrect to me. here we assume interrupt is always enabled for TD. But on TDVMCALL(HLT), the guest tells KVM if hlt is called with interrupt disabled. KVM can just check that interrupt status passed from the guest.

That's true. We can complicate this function and HLT emulation. But I don't think it's worthwhile because HLT with interrupt masked is rare. Only for CPU online.

Signature · 1 line
Isaku Yamahata <[email protected]>
#539 lore raw

Re: [PATCH v19 091/130] KVM: TDX: remove use of struct vcpu_vmx from posted_interrupt.c

Quoted 15 lines · Isaku Yamahata
Quoted 12 lines · Chao Gao

arch/x86/kvm/vmx/posted_intr.c void vmx_vcpu_pi_put(struct kvm_vcpu *vcpu) · line 226

Quoted 10 lines

}

void vmx_vcpu_pi_put(struct kvm_vcpu *vcpu)

arch/x86/kvm/vmx/posted_intr.c +2 -1
@@ -200,7 +222,8 @@ void vmx_vcpu_pi_put(struct kvm_vcpu *vcpu)
 	if (!vmx_needs_pi_wakeup(vcpu))
 		return;
 
-	if (kvm_vcpu_is_blocking(vcpu) && !vmx_interrupt_blocked(vcpu))
+	if (kvm_vcpu_is_blocking(vcpu) &&
+	    (is_td_vcpu(vcpu) || !vmx_interrupt_blocked(vcpu)))

Ditto.

Quoted 6 lines
Quoted 3 lines

This looks incorrect to me. here we assume interrupt is always enabled for TD. But on TDVMCALL(HLT), the guest tells KVM if hlt is called with interrupt disabled. KVM can just check that interrupt status passed from the guest.

That's true. We can complicate this function and HLT emulation. But I don't think it's worthwhile because HLT with interrupt masked is rare. Only for CPU online.

Then, it's better to add some comments?

#540 lore raw

PATCH v19 92/130 KVM: TDX: Implement interrupt injection

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

TDX supports interrupt inject into vcpu with posted interrupt. Wire up the corresponding kvm x86 operations to posted interrupt. Move kvm_vcpu_trigger_posted_interrupt() from vmx.c to common.h to share the code.

VMX can inject interrupt by setting interrupt information field, VM_ENTRY_INTR_INFO_FIELD, of VMCS. TDX supports interrupt injection only by posted interrupt. Ignore the execution path to access VM_ENTRY_INTR_INFO_FIELD.

As cpu state is protected and apicv is enabled for the TDX guest, VMM can inject interrupt by updating posted interrupt descriptor. Treat interrupt can be injected always.

---
 arch/x86/kvm/vmx/common.h      | 71 ++++++++++++++++++++++++++
 arch/x86/kvm/vmx/main.c        | 93 ++++++++++++++++++++++++++++++----
 arch/x86/kvm/vmx/posted_intr.c |  2 +-
 arch/x86/kvm/vmx/posted_intr.h |  2 +
 arch/x86/kvm/vmx/tdx.c         | 25 +++++++++
 arch/x86/kvm/vmx/vmx.c         | 67 +-----------------------
 arch/x86/kvm/vmx/x86_ops.h     |  7 ++-
 7 files changed, 190 insertions(+), 77 deletions(-)
arch/x86/kvm/vmx/common.h +71 -0
index 235908f3e044..6f21d0d48809 100644
@@ -4,6 +4,7 @@
 
 #include <linux/kvm_host.h>
 
+#include "posted_intr.h"
 #include "mmu.h"
 
 static inline int __vmx_handle_ept_violation(struct kvm_vcpu *vcpu, gpa_t gpa,
@@ -30,4 +31,74 @@ static inline int __vmx_handle_ept_violation(struct kvm_vcpu *vcpu, gpa_t gpa,
 	return kvm_mmu_page_fault(vcpu, gpa, error_code, NULL, 0);
 }
 
+static inline void kvm_vcpu_trigger_posted_interrupt(struct kvm_vcpu *vcpu,
+						     int pi_vec)
+{
+#ifdef CONFIG_SMP
+	if (vcpu->mode == IN_GUEST_MODE) {
+		/*
+		 * The vector of the virtual has already been set in the PIR.
+		 * Send a notification event to deliver the virtual interrupt
+		 * unless the vCPU is the currently running vCPU, i.e. the
+		 * event is being sent from a fastpath VM-Exit handler, in
+		 * which case the PIR will be synced to the vIRR before
+		 * re-entering the guest.
+		 *
+		 * When the target is not the running vCPU, the following
+		 * possibilities emerge:
+		 *
+		 * Case 1: vCPU stays in non-root mode. Sending a notification
+		 * event posts the interrupt to the vCPU.
+		 *
+		 * Case 2: vCPU exits to root mode and is still runnable. The
+		 * PIR will be synced to the vIRR before re-entering the guest.
+		 * Sending a notification event is ok as the host IRQ handler
+		 * will ignore the spurious event.
+		 *
+		 * Case 3: vCPU exits to root mode and is blocked. vcpu_block()
+		 * has already synced PIR to vIRR and never blocks the vCPU if
+		 * the vIRR is not empty. Therefore, a blocked vCPU here does
+		 * not wait for any requested interrupts in PIR, and sending a
+		 * notification event also results in a benign, spurious event.
+		 */
+
+		if (vcpu != kvm_get_running_vcpu())
+			__apic_send_IPI_mask(get_cpu_mask(vcpu->cpu), pi_vec);
+		return;
+	}
+#endif
+	/*
+	 * The vCPU isn't in the guest; wake the vCPU in case it is blocking,
+	 * otherwise do nothing as KVM will grab the highest priority pending
+	 * IRQ via ->sync_pir_to_irr() in vcpu_enter_guest().
+	 */
+	kvm_vcpu_wake_up(vcpu);
+}
+
+/*
+ * Send interrupt to vcpu via posted interrupt way.
+ * 1. If target vcpu is running(non-root mode), send posted interrupt
+ * notification to vcpu and hardware will sync PIR to vIRR atomically.
+ * 2. If target vcpu isn't running(root mode), kick it to pick up the
+ * interrupt from PIR in next vmentry.
+ */
+static inline void __vmx_deliver_posted_interrupt(struct kvm_vcpu *vcpu,
+						  struct pi_desc *pi_desc, int vector)
+{
+	if (pi_test_and_set_pir(vector, pi_desc))
+		return;
+
+	/* If a previous notification has sent the IPI, nothing to do.  */
+	if (pi_test_and_set_on(pi_desc))
+		return;
+
+	/*
+	 * The implied barrier in pi_test_and_set_on() pairs with the smp_mb_*()
+	 * after setting vcpu->mode in vcpu_enter_guest(), thus the vCPU is
+	 * guaranteed to see PID.ON=1 and sync the PIR to IRR if triggering a
+	 * posted interrupt "fails" because vcpu->mode != IN_GUEST_MODE.
+	 */
+	kvm_vcpu_trigger_posted_interrupt(vcpu, POSTED_INTR_VECTOR);
+}
+
 #endif /* __KVM_X86_VMX_COMMON_H */
arch/x86/kvm/vmx/main.c +84 -9

127 lines not on this page · raw message

arch/x86/kvm/vmx/posted_intr.c +1 -1
index b66add9da0f3..c86768b83f0b 100644
@@ -52,7 +52,7 @@ static inline struct vcpu_pi *vcpu_to_pi(struct kvm_vcpu *vcpu)
 	return (struct vcpu_pi *)vcpu;
 }
 
-static inline struct pi_desc *vcpu_to_pi_desc(struct kvm_vcpu *vcpu)
+struct pi_desc *vcpu_to_pi_desc(struct kvm_vcpu *vcpu)
 {
 	return &vcpu_to_pi(vcpu)->pi_desc;
 }
arch/x86/kvm/vmx/posted_intr.h +2 -0
index 2fe8222308b2..0f9983b6910b 100644
@@ -105,6 +105,8 @@ struct vcpu_pi {
 	/* Until here common layout betwwn vcpu_vmx and vcpu_tdx. */
 };
 
+struct pi_desc *vcpu_to_pi_desc(struct kvm_vcpu *vcpu);
+
 void vmx_vcpu_pi_load(struct kvm_vcpu *vcpu, int cpu);
 void vmx_vcpu_pi_put(struct kvm_vcpu *vcpu);
 void pi_wakeup_handler(void);
arch/x86/kvm/vmx/tdx.c +25 -0
index 1da58c36217c..1dfa9b503e0d 100644
@@ -7,6 +7,7 @@
 
 #include "capabilities.h"
 #include "x86_ops.h"
+#include "common.h"
 #include "mmu.h"
 #include "tdx_arch.h"
 #include "tdx.h"
@@ -603,6 +604,9 @@ int tdx_vcpu_create(struct kvm_vcpu *vcpu)
 	tdx->host_state_need_save = true;
 	tdx->host_state_need_restore = false;
 
+	tdx->pi_desc.nv = POSTED_INTR_VECTOR;
+	tdx->pi_desc.sn = 1;
+
 	return 0;
 }
 
@@ -610,6 +614,7 @@ void tdx_vcpu_load(struct kvm_vcpu *vcpu, int cpu)
 {
 	struct vcpu_tdx *tdx = to_tdx(vcpu);
 
+	vmx_vcpu_pi_load(vcpu, cpu);
 	if (vcpu->cpu == cpu)
 		return;
 
@@ -848,6 +853,12 @@ fastpath_t tdx_vcpu_run(struct kvm_vcpu *vcpu)
 
 	trace_kvm_entry(vcpu);
 
+	if (pi_test_on(&tdx->pi_desc)) {
+		apic->send_IPI_self(POSTED_INTR_VECTOR);
+
+		kvm_wait_lapic_expire(vcpu);
+	}
+
 	tdx_vcpu_enter_exit(tdx);
 
 	tdx_user_return_update_cache(vcpu);
@@ -1213,6 +1224,16 @@ static int tdx_sept_remove_private_spte(struct kvm *kvm, gfn_t gfn,
 	return tdx_sept_drop_private_spte(kvm, gfn, level, pfn);
 }
 
+void tdx_deliver_interrupt(struct kvm_lapic *apic, int delivery_mode,
+			   int trig_mode, int vector)
+{
+	struct kvm_vcpu *vcpu = apic->vcpu;
+	struct vcpu_tdx *tdx = to_tdx(vcpu);
+
+	/* TDX supports only posted interrupt.  No lapic emulation. */
+	__vmx_deliver_posted_interrupt(vcpu, &tdx->pi_desc, vector);
+}
+
 static int tdx_get_capabilities(struct kvm_tdx_cmd *cmd)
 {
 	struct kvm_tdx_capabilities __user *user_caps;
@@ -1972,6 +1993,10 @@ int tdx_vcpu_ioctl(struct kvm_vcpu *vcpu, void __user *argp)
 	if (ret)
 		return ret;
 
+	td_vmcs_write16(tdx, POSTED_INTR_NV, POSTED_INTR_VECTOR);
+	td_vmcs_write64(tdx, POSTED_INTR_DESC_ADDR, __pa(&tdx->pi_desc));
+	td_vmcs_setbit32(tdx, PIN_BASED_VM_EXEC_CONTROL, PIN_BASED_POSTED_INTR);
+
 	tdx->initialized = true;
 	return 0;
 }
arch/x86/kvm/vmx/vmx.c +1 -66

85 lines not on this page · raw message

arch/x86/kvm/vmx/x86_ops.h +6 -1
index f3f90186926c..12a212e71827 100644
@@ -58,7 +58,6 @@ int vmx_check_intercept(struct kvm_vcpu *vcpu,
 bool vmx_apic_init_signal_blocked(struct kvm_vcpu *vcpu);
 void vmx_migrate_timers(struct kvm_vcpu *vcpu);
 void vmx_set_virtual_apic_mode(struct kvm_vcpu *vcpu);
-void vmx_apicv_pre_state_restore(struct kvm_vcpu *vcpu);
 bool vmx_check_apicv_inhibit_reasons(enum kvm_apicv_inhibit reason);
 void vmx_hwapic_irr_update(struct kvm_vcpu *vcpu, int max_irr);
 void vmx_hwapic_isr_update(int max_isr);
@@ -158,6 +157,9 @@ void tdx_vcpu_load(struct kvm_vcpu *vcpu, int cpu);
 bool tdx_protected_apic_has_interrupt(struct kvm_vcpu *vcpu);
 u8 tdx_get_mt_mask(struct kvm_vcpu *vcpu, gfn_t gfn, bool is_mmio);
 
+void tdx_deliver_interrupt(struct kvm_lapic *apic, int delivery_mode,
+			   int trig_mode, int vector);
+
 int tdx_vcpu_ioctl(struct kvm_vcpu *vcpu, void __user *argp);
 
 void tdx_flush_tlb(struct kvm_vcpu *vcpu);
@@ -198,6 +200,9 @@ static inline void tdx_vcpu_load(struct kvm_vcpu *vcpu, int cpu) {}
 static inline bool tdx_protected_apic_has_interrupt(struct kvm_vcpu *vcpu) { return false; }
 static inline u8 tdx_get_mt_mask(struct kvm_vcpu *vcpu, gfn_t gfn, bool is_mmio) { return 0; }
 
+static inline void tdx_deliver_interrupt(struct kvm_lapic *apic, int delivery_mode,
+					 int trig_mode, int vector) {}
+
 static inline int tdx_vcpu_ioctl(struct kvm_vcpu *vcpu, void __user *argp) { return -EOPNOTSUPP; }
 
 static inline void tdx_flush_tlb(struct kvm_vcpu *vcpu) {}
Signature · 1 line
2.25.1
#541 lore raw

Re: [PATCH v19 092/130] KVM: TDX: Implement interrupt injection

arch/x86/kvm/vmx/tdx.c fastpath_t tdx_vcpu_run(struct kvm_vcpu *vcpu) · line 859

Quoted 8 lines
arch/x86/kvm/vmx/tdx.c +4 -0
@@ -848,6 +853,12 @@ fastpath_t tdx_vcpu_run(struct kvm_vcpu *vcpu)
 
 	trace_kvm_entry(vcpu);
 
+	if (pi_test_on(&tdx->pi_desc)) {
+		apic->send_IPI_self(POSTED_INTR_VECTOR);
+
+		kvm_wait_lapic_expire(vcpu);

it seems the APIC timer change was inadvertently included.

#542 lore raw

Re: [PATCH v19 092/130] KVM: TDX: Implement interrupt injection

Quoted 10 lines · Chao Gao

arch/x86/kvm/vmx/tdx.c fastpath_t tdx_vcpu_run(struct kvm_vcpu *vcpu) · line 859

Quoted 8 lines
arch/x86/kvm/vmx/tdx.c +4 -0
@@ -848,6 +853,12 @@ fastpath_t tdx_vcpu_run(struct kvm_vcpu *vcpu)
 
 	trace_kvm_entry(vcpu);
 
+	if (pi_test_on(&tdx->pi_desc)) {
+		apic->send_IPI_self(POSTED_INTR_VECTOR);
+
+		kvm_wait_lapic_expire(vcpu);

it seems the APIC timer change was inadvertently included.

Oops. Thanks for catching it.

Signature · 1 line
Isaku Yamahata <[email protected]>
#543 lore raw

Re: [PATCH v19 092/130] KVM: TDX: Implement interrupt injection

In reply to [email protected]
Quoted 28 lines · On 2/26/2024 4:26 PM, [email protected] wrote:

From: Isaku Yamahata<[email protected]>

TDX supports interrupt inject into vcpu with posted interrupt. Wire up the corresponding kvm x86 operations to posted interrupt. Move kvm_vcpu_trigger_posted_interrupt() from vmx.c to common.h to share the code.

VMX can inject interrupt by setting interrupt information field, VM_ENTRY_INTR_INFO_FIELD, of VMCS. TDX supports interrupt injection only by posted interrupt. Ignore the execution path to access VM_ENTRY_INTR_INFO_FIELD.

As cpu state is protected and apicv is enabled for the TDX guest, VMM can inject interrupt by updating posted interrupt descriptor. Treat interrupt can be injected always.

---
  arch/x86/kvm/vmx/common.h      | 71 ++++++++++++++++++++++++++
  arch/x86/kvm/vmx/main.c        | 93 ++++++++++++++++++++++++++++++----
  arch/x86/kvm/vmx/posted_intr.c |  2 +-
  arch/x86/kvm/vmx/posted_intr.h |  2 +
  arch/x86/kvm/vmx/tdx.c         | 25 +++++++++
  arch/x86/kvm/vmx/vmx.c         | 67 +-----------------------
  arch/x86/kvm/vmx/x86_ops.h     |  7 ++-
  7 files changed, 190 insertions(+), 77 deletions(-)

[...]

arch/x86/kvm/vmx/main.c static void vt_sched_in(struct kvm_vcpu *vcpu, int cpu) · line 331

Quoted 5 lines
arch/x86/kvm/vmx/main.c +4 -0
+static void vt_set_interrupt_shadow(struct kvm_vcpu *vcpu, int mask)
+{
+	if (is_td_vcpu(vcpu))
+		return;

Please add a blank line.

Quoted 3 lines
arch/x86/kvm/vmx/main.c +3 -0
+	vmx_set_interrupt_shadow(vcpu, mask);
+}
+

[...]

arch/x86/kvm/vmx/tdx.c fastpath_t tdx_vcpu_run(struct kvm_vcpu *vcpu) · line 859

Quoted 9 lines
arch/x86/kvm/vmx/tdx.c +4 -0
@@ -848,6 +853,12 @@ fastpath_t tdx_vcpu_run(struct kvm_vcpu *vcpu)
 
  	trace_kvm_entry(vcpu);
 
+	if (pi_test_on(&tdx->pi_desc)) {
+		apic->send_IPI_self(POSTED_INTR_VECTOR);
+
+		kvm_wait_lapic_expire(vcpu);

As Chao pointed out, APIC timer change shouldn't be included in this patch.

Maybe better to put the splitted patch closer to patch "KVM: x86: Assume timer IRQ was injected if APIC state is proteced" becasue they are related.

Quoted 9 lines
arch/x86/kvm/vmx/tdx.c +2 -0
+	}
+
  	tdx_vcpu_enter_exit(tdx);
 
  	tdx_user_return_update_cache(vcpu);
@@ -1213,6 +1224,16 @@ static int tdx_sept_remove_private_spte(struct kvm *kvm, gfn_t gfn,
  	return tdx_sept_drop_private_spte(kvm, gfn, level, pfn);
  }
 

[...]

#544 lore raw

PATCH v19 93/130 KVM: TDX: Implements vcpu request_immediate_exit

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

Now we are able to inject interrupts into TDX vcpu, it's ready to block TDX vcpu. Wire up kvm x86 methods for blocking/unblocking vcpu for TDX. To unblock on pending events, request immediate exit methods is also needed.

---
 arch/x86/kvm/vmx/main.c | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)
arch/x86/kvm/vmx/main.c +11 -1
index f2c9d6358f9e..ee6c04959d4c 100644
@@ -372,6 +372,16 @@ static void vt_enable_irq_window(struct kvm_vcpu *vcpu)
 	vmx_enable_irq_window(vcpu);
 }
 
+static void vt_request_immediate_exit(struct kvm_vcpu *vcpu)
+{
+	if (is_td_vcpu(vcpu)) {
+		__kvm_request_immediate_exit(vcpu);
+		return;
+	}
+
+	vmx_request_immediate_exit(vcpu);
+}
+
 static u8 vt_get_mt_mask(struct kvm_vcpu *vcpu, gfn_t gfn, bool is_mmio)
 {
 	if (is_td_vcpu(vcpu))
@@ -549,7 +559,7 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
 	.check_intercept = vmx_check_intercept,
 	.handle_exit_irqoff = vmx_handle_exit_irqoff,
 
-	.request_immediate_exit = vmx_request_immediate_exit,
+	.request_immediate_exit = vt_request_immediate_exit,
 
 	.sched_in = vt_sched_in,
 
Signature · 1 line
2.25.1
#545 lore raw

Re: [PATCH v19 093/130] KVM: TDX: Implements vcpu request_immediate_exit

Quoted 5 lines · On Mon, Feb 26, 2024 at 12:26:35AM -0800, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

Now we are able to inject interrupts into TDX vcpu, it's ready to block TDX vcpu. Wire up kvm x86 methods for blocking/unblocking vcpu for TDX. To unblock on pending events, request immediate exit methods is also needed.

TDX doesn't support this immediate exit. It is considered as a potential attack to TDs. TDX module deploys 0/1-step mitigations to prevent this. Even KVM issues a self-IPI before TD-entry, TD-exit will happen after the guest runs a random number of instructions.

KVM shouldn't request immediate exits in the first place. Just emit a warning if KVM tries to do this.

Quoted 41 lines

---
arch/x86/kvm/vmx/main.c | 12 +++++++++++-

1 file changed, 11 insertions(+), 1 deletion(-)
arch/x86/kvm/vmx/main.c +11 -1
index f2c9d6358f9e..ee6c04959d4c 100644
@@ -372,6 +372,16 @@ static void vt_enable_irq_window(struct kvm_vcpu *vcpu)
 	vmx_enable_irq_window(vcpu);
 }
 
+static void vt_request_immediate_exit(struct kvm_vcpu *vcpu)
+{
+	if (is_td_vcpu(vcpu)) {
+		__kvm_request_immediate_exit(vcpu);
+		return;
+	}
+
+	vmx_request_immediate_exit(vcpu);
+}
+
 static u8 vt_get_mt_mask(struct kvm_vcpu *vcpu, gfn_t gfn, bool is_mmio)
 {
 	if (is_td_vcpu(vcpu))
@@ -549,7 +559,7 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
 	.check_intercept = vmx_check_intercept,
 	.handle_exit_irqoff = vmx_handle_exit_irqoff,
 
-	.request_immediate_exit = vmx_request_immediate_exit,
+	.request_immediate_exit = vt_request_immediate_exit,
 
 	.sched_in = vt_sched_in,
 
Signature · 1 line
2.25.1
#546 lore raw

Re: [PATCH v19 093/130] KVM: TDX: Implements vcpu request_immediate_exit

Quoted 7 lines · Chao Gao
Quoted 5 lines · On Mon, Feb 26, 2024 at 12:26:35AM -0800, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

Now we are able to inject interrupts into TDX vcpu, it's ready to block TDX vcpu. Wire up kvm x86 methods for blocking/unblocking vcpu for TDX. To unblock on pending events, request immediate exit methods is also needed.

Quoted 7 lines

TDX doesn't support this immediate exit. It is considered as a potential attack to TDs. TDX module deploys 0/1-step mitigations to prevent this. Even KVM issues a self-IPI before TD-entry, TD-exit will happen after the guest runs a random number of instructions.

KVM shouldn't request immediate exits in the first place. Just emit a warning if KVM tries to do this.

0ec3d6d1f169
("KVM: x86: Fully defer to vendor code to decide how to force immediate exit") removed the hook. This patch will be dropped and tdx_vcpu_run() will ignore force_immediate_exit.

Signature · 1 line
Isaku Yamahata <[email protected]>
#547 lore raw

PATCH v19 94/130 KVM: TDX: Implement methods to inject NMI

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

TDX vcpu control structure defines one bit for pending NMI for VMM to inject NMI by setting the bit without knowing TDX vcpu NMI states. Because the vcpu state is protected, VMM can't know about NMI states of TDX vcpu. The TDX module handles actual injection and NMI states transition.

Add methods for NMI and treat NMI can be injected always.

---
 arch/x86/kvm/vmx/main.c    | 64 +++++++++++++++++++++++++++++++++++---
 arch/x86/kvm/vmx/tdx.c     |  6 ++++
 arch/x86/kvm/vmx/x86_ops.h |  2 ++
 3 files changed, 67 insertions(+), 5 deletions(-)
arch/x86/kvm/vmx/main.c +59 -5
index ee6c04959d4c..6d6d443a2bbd 100644
@@ -306,6 +306,60 @@ static void vt_flush_tlb_guest(struct kvm_vcpu *vcpu)
 	vmx_flush_tlb_guest(vcpu);
 }
 
+static void vt_inject_nmi(struct kvm_vcpu *vcpu)
+{
+	if (is_td_vcpu(vcpu)) {
+		tdx_inject_nmi(vcpu);
+		return;
+	}
+
+	vmx_inject_nmi(vcpu);
+}
+
+static int vt_nmi_allowed(struct kvm_vcpu *vcpu, bool for_injection)
+{
+	/*
+	 * The TDX module manages NMI windows and NMI reinjection, and hides NMI
+	 * blocking, all KVM can do is throw an NMI over the wall.
+	 */
+	if (is_td_vcpu(vcpu))
+		return true;
+
+	return vmx_nmi_allowed(vcpu, for_injection);
+}
+
+static bool vt_get_nmi_mask(struct kvm_vcpu *vcpu)
+{
+	/*
+	 * Assume NMIs are always unmasked.  KVM could query PEND_NMI and treat
+	 * NMIs as masked if a previous NMI is still pending, but SEAMCALLs are
+	 * expensive and the end result is unchanged as the only relevant usage
+	 * of get_nmi_mask() is to limit the number of pending NMIs, i.e. it
+	 * only changes whether KVM or the TDX module drops an NMI.
+	 */
+	if (is_td_vcpu(vcpu))
+		return false;
+
+	return vmx_get_nmi_mask(vcpu);
+}
+
+static void vt_set_nmi_mask(struct kvm_vcpu *vcpu, bool masked)
+{
+	if (is_td_vcpu(vcpu))
+		return;
+
+	vmx_set_nmi_mask(vcpu, masked);
+}
+
+static void vt_enable_nmi_window(struct kvm_vcpu *vcpu)
+{
+	/* Refer the comment in vt_get_nmi_mask(). */
+	if (is_td_vcpu(vcpu))
+		return;
+
+	vmx_enable_nmi_window(vcpu);
+}
+
 static void vt_load_mmu_pgd(struct kvm_vcpu *vcpu, hpa_t root_hpa,
 			int pgd_level)
 {
@@ -515,14 +569,14 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
 	.get_interrupt_shadow = vt_get_interrupt_shadow,
 	.patch_hypercall = vmx_patch_hypercall,
 	.inject_irq = vt_inject_irq,
-	.inject_nmi = vmx_inject_nmi,
+	.inject_nmi = vt_inject_nmi,
 	.inject_exception = vmx_inject_exception,
 	.cancel_injection = vt_cancel_injection,
 	.interrupt_allowed = vt_interrupt_allowed,
-	.nmi_allowed = vmx_nmi_allowed,
-	.get_nmi_mask = vmx_get_nmi_mask,
-	.set_nmi_mask = vmx_set_nmi_mask,
-	.enable_nmi_window = vmx_enable_nmi_window,
+	.nmi_allowed = vt_nmi_allowed,
+	.get_nmi_mask = vt_get_nmi_mask,
+	.set_nmi_mask = vt_set_nmi_mask,
+	.enable_nmi_window = vt_enable_nmi_window,
 	.enable_irq_window = vt_enable_irq_window,
 	.update_cr8_intercept = vmx_update_cr8_intercept,
 	.set_virtual_apic_mode = vmx_set_virtual_apic_mode,
arch/x86/kvm/vmx/tdx.c +6 -0
index 1dfa9b503e0d..be21dca47992 100644
@@ -874,6 +874,12 @@ fastpath_t tdx_vcpu_run(struct kvm_vcpu *vcpu)
 	return EXIT_FASTPATH_NONE;
 }
 
+void tdx_inject_nmi(struct kvm_vcpu *vcpu)
+{
+	++vcpu->stat.nmi_injections;
+	td_management_write8(to_tdx(vcpu), TD_VCPU_PEND_NMI, 1);
+}
+
 void tdx_load_mmu_pgd(struct kvm_vcpu *vcpu, hpa_t root_hpa, int pgd_level)
 {
 	WARN_ON_ONCE(root_hpa & ~PAGE_MASK);
arch/x86/kvm/vmx/x86_ops.h +2 -0
index 12a212e71827..539f3f9686fe 100644
@@ -159,6 +159,7 @@ u8 tdx_get_mt_mask(struct kvm_vcpu *vcpu, gfn_t gfn, bool is_mmio);
 
 void tdx_deliver_interrupt(struct kvm_lapic *apic, int delivery_mode,
 			   int trig_mode, int vector);
+void tdx_inject_nmi(struct kvm_vcpu *vcpu);
 
 int tdx_vcpu_ioctl(struct kvm_vcpu *vcpu, void __user *argp);
 
@@ -202,6 +203,7 @@ static inline u8 tdx_get_mt_mask(struct kvm_vcpu *vcpu, gfn_t gfn, bool is_mmio)
 
 static inline void tdx_deliver_interrupt(struct kvm_lapic *apic, int delivery_mode,
 					 int trig_mode, int vector) {}
+static inline void tdx_inject_nmi(struct kvm_vcpu *vcpu) {}
 
 static inline int tdx_vcpu_ioctl(struct kvm_vcpu *vcpu, void __user *argp) { return -EOPNOTSUPP; }
 
Signature · 1 line
2.25.1
#548 lore raw

Re: [PATCH v19 094/130] KVM: TDX: Implement methods to inject NMI

Quoted 6 lines
arch/x86/kvm/vmx/main.c +6 -0
+static void vt_set_nmi_mask(struct kvm_vcpu *vcpu, bool masked)
+{
+	if (is_td_vcpu(vcpu))
+		return;
+
+	vmx_set_nmi_mask(vcpu, masked);

arch/x86/kvm/vmx/main.c static void vt_flush_tlb_guest(struct kvm_vcpu *vcpu) · line 361

Quoted 10 lines
arch/x86/kvm/vmx/main.c +10 -0
+}
+
+static void vt_enable_nmi_window(struct kvm_vcpu *vcpu)
+{
+	/* Refer the comment in vt_get_nmi_mask(). */
+	if (is_td_vcpu(vcpu))
+		return;
+
+	vmx_enable_nmi_window(vcpu);
+}

The two actually request something to do done for the TD. But we make them nop as TDX module doesn't support VMM to configure nmi mask and nmi window. Do you think they are worth a WARN_ON_ONCE()? or adding WARN_ON_ONCE() requires a lot of code factoring in KVM's NMI injection logics?

#549 lore raw

Re: [PATCH v19 094/130] KVM: TDX: Implement methods to inject NMI

Quoted 13 lines · Chao Gao
Quoted 13 lines
arch/x86/kvm/vmx/main.c +13 -0
+static void vt_set_nmi_mask(struct kvm_vcpu *vcpu, bool masked)
+{
+	if (is_td_vcpu(vcpu))
+		return;
+
+	vmx_set_nmi_mask(vcpu, masked);
+}
+
+static void vt_enable_nmi_window(struct kvm_vcpu *vcpu)
+{
+	/* Refer the comment in vt_get_nmi_mask(). */
+	if (is_td_vcpu(vcpu))
+		return;
Quoted 8 lines

arch/x86/kvm/vmx/main.c static void vt_flush_tlb_guest(struct kvm_vcpu *vcpu) · line 361

Quoted 3 lines
arch/x86/kvm/vmx/main.c +3 -0
+
+	vmx_enable_nmi_window(vcpu);
+}

The two actually request something to do done for the TD. But we make them nop as TDX module doesn't support VMM to configure nmi mask and nmi window. Do you think they are worth a WARN_ON_ONCE()? or adding WARN_ON_ONCE() requires a lot of code factoring in KVM's NMI injection logics?

Because user space can reach those hooks with KVM_SET_VCPU_EVENTS, we shouldn't add WARN_ON_ONCE(). There are two choices. Ignore the request (the current choice) or return error for unsupported request.

It's troublesome to allow error for them because we have to fix up the caller up to the user space. The user space may abort on such error without fix.

Signature · 1 line
Isaku Yamahata <[email protected]>
#550 lore raw

PATCH v19 95/130 KVM: VMX: Modify NMI and INTR handlers to take intr_info as function argument

In reply to [email protected]

From: Sean Christopherson <[email protected]>

TDX uses different ABI to get information about VM exit. Pass intr_info to the NMI and INTR handlers instead of pulling it from vcpu_vmx in preparation for sharing the bulk of the handlers with TDX.

When the guest TD exits to VMM, RAX holds status and exit reason, RCX holds exit qualification etc rather than the VMCS fields because VMM doesn't have access to the VMCS. The eventual code will be

VMX:
- get exit reason, intr_info, exit_qualification, and etc from VMCS
- call NMI/INTR handlers (common code)

TDX:
- get exit reason, intr_info, exit_qualification, and etc from guest registers
- call NMI/INTR handlers (common code)

---
 arch/x86/kvm/vmx/vmx.c | 16 +++++++---------
 1 file changed, 7 insertions(+), 9 deletions(-)
arch/x86/kvm/vmx/vmx.c +7 -9
index 1349ec438837..29d891e0795e 100644
@@ -6912,24 +6912,22 @@ static void handle_nm_fault_irqoff(struct kvm_vcpu *vcpu)
 		rdmsrl(MSR_IA32_XFD_ERR, vcpu->arch.guest_fpu.xfd_err);
 }
 
-static void handle_exception_irqoff(struct vcpu_vmx *vmx)
+static void handle_exception_irqoff(struct kvm_vcpu *vcpu, u32 intr_info)
 {
-	u32 intr_info = vmx_get_intr_info(&vmx->vcpu);
-
 	/* if exit due to PF check for async PF */
 	if (is_page_fault(intr_info))
-		vmx->vcpu.arch.apf.host_apf_flags = kvm_read_and_reset_apf_flags();
+		vcpu->arch.apf.host_apf_flags = kvm_read_and_reset_apf_flags();
 	/* if exit due to NM, handle before interrupts are enabled */
 	else if (is_nm_fault(intr_info))
-		handle_nm_fault_irqoff(&vmx->vcpu);
+		handle_nm_fault_irqoff(vcpu);
 	/* Handle machine checks before interrupts are enabled */
 	else if (is_machine_check(intr_info))
 		kvm_machine_check();
 }
 
-static void handle_external_interrupt_irqoff(struct kvm_vcpu *vcpu)
+static void handle_external_interrupt_irqoff(struct kvm_vcpu *vcpu,
+					     u32 intr_info)
 {
-	u32 intr_info = vmx_get_intr_info(vcpu);
 	unsigned int vector = intr_info & INTR_INFO_VECTOR_MASK;
 	gate_desc *desc = (gate_desc *)host_idt_base + vector;
 
@@ -6952,9 +6950,9 @@ void vmx_handle_exit_irqoff(struct kvm_vcpu *vcpu)
 		return;
 
 	if (vmx->exit_reason.basic == EXIT_REASON_EXTERNAL_INTERRUPT)
-		handle_external_interrupt_irqoff(vcpu);
+		handle_external_interrupt_irqoff(vcpu, vmx_get_intr_info(vcpu));
 	else if (vmx->exit_reason.basic == EXIT_REASON_EXCEPTION_NMI)
-		handle_exception_irqoff(vmx);
+		handle_exception_irqoff(vcpu, vmx_get_intr_info(vcpu));
 }
 
 /*
Signature · 1 line
2.25.1
Reviewed-by on v19 95/130
#551 lore raw

Re: [PATCH v19 095/130] KVM: VMX: Modify NMI and INTR handlers to take intr_info as function argument

Quoted 14 lines · On Mon, Feb 26, 2024 at 12:26:37AM -0800, [email protected] wrote:

From: Sean Christopherson <[email protected]>

TDX uses different ABI to get information about VM exit. Pass intr_info to the NMI and INTR handlers instead of pulling it from vcpu_vmx in preparation for sharing the bulk of the handlers with TDX.

When the guest TD exits to VMM, RAX holds status and exit reason, RCX holds exit qualification etc rather than the VMCS fields because VMM doesn't have access to the VMCS. The eventual code will be

VMX:
- get exit reason, intr_info, exit_qualification, and etc from VMCS
- call NMI/INTR handlers (common code)

Quoted 8 lines

TDX:
- get exit reason, intr_info, exit_qualification, and etc from guest registers
- call NMI/INTR handlers (common code)

#552 lore raw

PATCH v19 96/130 KVM: VMX: Move NMI/exception handler to common helper

In reply to [email protected]

From: Sean Christopherson <[email protected]>

TDX mostly handles NMI/exception exit mostly the same to VMX case. The difference is how to retrieve exit qualification. To share the code with TDX, move NMI/exception to a common header, common.h.

---
 arch/x86/kvm/vmx/common.h | 59 +++++++++++++++++++++++++++++++++
 arch/x86/kvm/vmx/vmx.c    | 68 +++++----------------------------------
 2 files changed, 67 insertions(+), 60 deletions(-)
arch/x86/kvm/vmx/common.h +59 -0
index 6f21d0d48809..632af7a76d0a 100644
@@ -4,8 +4,67 @@
 
 #include <linux/kvm_host.h>
 
+#include <asm/traps.h>
+
 #include "posted_intr.h"
 #include "mmu.h"
+#include "vmcs.h"
+#include "x86.h"
+
+extern unsigned long vmx_host_idt_base;
+void vmx_do_interrupt_irqoff(unsigned long entry);
+void vmx_do_nmi_irqoff(void);
+
+static inline void vmx_handle_nm_fault_irqoff(struct kvm_vcpu *vcpu)
+{
+	/*
+	 * Save xfd_err to guest_fpu before interrupt is enabled, so the
+	 * MSR value is not clobbered by the host activity before the guest
+	 * has chance to consume it.
+	 *
+	 * Do not blindly read xfd_err here, since this exception might
+	 * be caused by L1 interception on a platform which doesn't
+	 * support xfd at all.
+	 *
+	 * Do it conditionally upon guest_fpu::xfd. xfd_err matters
+	 * only when xfd contains a non-zero value.
+	 *
+	 * Queuing exception is done in vmx_handle_exit. See comment there.
+	 */
+	if (vcpu->arch.guest_fpu.fpstate->xfd)
+		rdmsrl(MSR_IA32_XFD_ERR, vcpu->arch.guest_fpu.xfd_err);
+}
+
+static inline void vmx_handle_exception_irqoff(struct kvm_vcpu *vcpu,
+					       u32 intr_info)
+{
+	/* if exit due to PF check for async PF */
+	if (is_page_fault(intr_info))
+		vcpu->arch.apf.host_apf_flags = kvm_read_and_reset_apf_flags();
+	/* if exit due to NM, handle before interrupts are enabled */
+	else if (is_nm_fault(intr_info))
+		vmx_handle_nm_fault_irqoff(vcpu);
+	/* Handle machine checks before interrupts are enabled */
+	else if (is_machine_check(intr_info))
+		kvm_machine_check();
+}
+
+static inline void vmx_handle_external_interrupt_irqoff(struct kvm_vcpu *vcpu,
+							u32 intr_info)
+{
+	unsigned int vector = intr_info & INTR_INFO_VECTOR_MASK;
+	gate_desc *desc = (gate_desc *)vmx_host_idt_base + vector;
+
+	if (KVM_BUG(!is_external_intr(intr_info), vcpu->kvm,
+	    "unexpected VM-Exit interrupt info: 0x%x", intr_info))
+		return;
+
+	kvm_before_interrupt(vcpu, KVM_HANDLING_IRQ);
+	vmx_do_interrupt_irqoff(gate_offset(desc));
+	kvm_after_interrupt(vcpu);
+
+	vcpu->arch.at_instruction_boundary = true;
+}
 
 static inline int __vmx_handle_ept_violation(struct kvm_vcpu *vcpu, gpa_t gpa,
 					     unsigned long exit_qualification)
arch/x86/kvm/vmx/vmx.c +8 -60

111 lines not on this page · raw message

Signature · 1 line
2.25.1
#553 lore raw

Re: [PATCH v19 096/130] KVM: VMX: Move NMI/exception handler to common helper

Quoted 5 lines · On 2/26/2024 4:26 PM, [email protected] wrote:

From: Sean Christopherson <[email protected]>

TDX mostly handles NMI/exception exit mostly the same to VMX case. The difference is how to retrieve exit qualification. To share the code with TDX, move NMI/exception to a common header, common.h.

Suggest to add "No functional change intended." in the changelog.

Quoted 202 lines
---
  arch/x86/kvm/vmx/common.h | 59 +++++++++++++++++++++++++++++++++
  arch/x86/kvm/vmx/vmx.c    | 68 +++++----------------------------------
  2 files changed, 67 insertions(+), 60 deletions(-)
arch/x86/kvm/vmx/common.h +59 -0
index 6f21d0d48809..632af7a76d0a 100644
@@ -4,8 +4,67 @@
 
  #include <linux/kvm_host.h>
 
+#include <asm/traps.h>
+
  #include "posted_intr.h"
  #include "mmu.h"
+#include "vmcs.h"
+#include "x86.h"
+
+extern unsigned long vmx_host_idt_base;
+void vmx_do_interrupt_irqoff(unsigned long entry);
+void vmx_do_nmi_irqoff(void);
+
+static inline void vmx_handle_nm_fault_irqoff(struct kvm_vcpu *vcpu)
+{
+	/*
+	 * Save xfd_err to guest_fpu before interrupt is enabled, so the
+	 * MSR value is not clobbered by the host activity before the guest
+	 * has chance to consume it.
+	 *
+	 * Do not blindly read xfd_err here, since this exception might
+	 * be caused by L1 interception on a platform which doesn't
+	 * support xfd at all.
+	 *
+	 * Do it conditionally upon guest_fpu::xfd. xfd_err matters
+	 * only when xfd contains a non-zero value.
+	 *
+	 * Queuing exception is done in vmx_handle_exit. See comment there.
+	 */
+	if (vcpu->arch.guest_fpu.fpstate->xfd)
+		rdmsrl(MSR_IA32_XFD_ERR, vcpu->arch.guest_fpu.xfd_err);
+}
+
+static inline void vmx_handle_exception_irqoff(struct kvm_vcpu *vcpu,
+					       u32 intr_info)
+{
+	/* if exit due to PF check for async PF */
+	if (is_page_fault(intr_info))
+		vcpu->arch.apf.host_apf_flags = kvm_read_and_reset_apf_flags();
+	/* if exit due to NM, handle before interrupts are enabled */
+	else if (is_nm_fault(intr_info))
+		vmx_handle_nm_fault_irqoff(vcpu);
+	/* Handle machine checks before interrupts are enabled */
+	else if (is_machine_check(intr_info))
+		kvm_machine_check();
+}
+
+static inline void vmx_handle_external_interrupt_irqoff(struct kvm_vcpu *vcpu,
+							u32 intr_info)
+{
+	unsigned int vector = intr_info & INTR_INFO_VECTOR_MASK;
+	gate_desc *desc = (gate_desc *)vmx_host_idt_base + vector;
+
+	if (KVM_BUG(!is_external_intr(intr_info), vcpu->kvm,
+	    "unexpected VM-Exit interrupt info: 0x%x", intr_info))
+		return;
+
+	kvm_before_interrupt(vcpu, KVM_HANDLING_IRQ);
+	vmx_do_interrupt_irqoff(gate_offset(desc));
+	kvm_after_interrupt(vcpu);
+
+	vcpu->arch.at_instruction_boundary = true;
+}
 
  static inline int __vmx_handle_ept_violation(struct kvm_vcpu *vcpu, gpa_t gpa,
  					     unsigned long exit_qualification)
arch/x86/kvm/vmx/vmx.c +8 -60
index 29d891e0795e..f8a00a766c40 100644
@@ -518,7 +518,7 @@ static inline void vmx_segment_cache_clear(struct vcpu_vmx *vmx)
  	vmx->segment_cache.bitmask = 0;
  }
 
-static unsigned long host_idt_base;
+unsigned long vmx_host_idt_base;
 
  #if IS_ENABLED(CONFIG_HYPERV)
  static bool __read_mostly enlightened_vmcs = true;
@@ -4273,7 +4273,7 @@ void vmx_set_constant_host_state(struct vcpu_vmx *vmx)
  	vmcs_write16(HOST_SS_SELECTOR, __KERNEL_DS);  /* 22.2.4 */
  	vmcs_write16(HOST_TR_SELECTOR, GDT_ENTRY_TSS*8);  /* 22.2.4 */
 
-	vmcs_writel(HOST_IDTR_BASE, host_idt_base);   /* 22.2.4 */
+	vmcs_writel(HOST_IDTR_BASE, vmx_host_idt_base);   /* 22.2.4 */
 
  	vmcs_writel(HOST_RIP, (unsigned long)vmx_vmexit); /* 22.2.5 */
 
@@ -5166,7 +5166,7 @@ static int handle_exception_nmi(struct kvm_vcpu *vcpu)
  	intr_info = vmx_get_intr_info(vcpu);
 
  	/*
-	 * Machine checks are handled by handle_exception_irqoff(), or by
+	 * Machine checks are handled by vmx_handle_exception_irqoff(), or by
  	 * vmx_vcpu_run() if a #MC occurs on VM-Entry.  NMIs are handled by
  	 * vmx_vcpu_enter_exit().
  	 */
@@ -5174,7 +5174,7 @@ static int handle_exception_nmi(struct kvm_vcpu *vcpu)
  		return 1;
 
  	/*
-	 * Queue the exception here instead of in handle_nm_fault_irqoff().
+	 * Queue the exception here instead of in vmx_handle_nm_fault_irqoff().
  	 * This ensures the nested_vmx check is not skipped so vmexit can
  	 * be reflected to L1 (when it intercepts #NM) before reaching this
  	 * point.
@@ -6889,59 +6889,6 @@ void vmx_load_eoi_exitmap(struct kvm_vcpu *vcpu, u64 *eoi_exit_bitmap)
  	vmcs_write64(EOI_EXIT_BITMAP3, eoi_exit_bitmap[3]);
  }
 
-void vmx_do_interrupt_irqoff(unsigned long entry);
-void vmx_do_nmi_irqoff(void);
-
-static void handle_nm_fault_irqoff(struct kvm_vcpu *vcpu)
-{
-	/*
-	 * Save xfd_err to guest_fpu before interrupt is enabled, so the
-	 * MSR value is not clobbered by the host activity before the guest
-	 * has chance to consume it.
-	 *
-	 * Do not blindly read xfd_err here, since this exception might
-	 * be caused by L1 interception on a platform which doesn't
-	 * support xfd at all.
-	 *
-	 * Do it conditionally upon guest_fpu::xfd. xfd_err matters
-	 * only when xfd contains a non-zero value.
-	 *
-	 * Queuing exception is done in vmx_handle_exit. See comment there.
-	 */
-	if (vcpu->arch.guest_fpu.fpstate->xfd)
-		rdmsrl(MSR_IA32_XFD_ERR, vcpu->arch.guest_fpu.xfd_err);
-}
-
-static void handle_exception_irqoff(struct kvm_vcpu *vcpu, u32 intr_info)
-{
-	/* if exit due to PF check for async PF */
-	if (is_page_fault(intr_info))
-		vcpu->arch.apf.host_apf_flags = kvm_read_and_reset_apf_flags();
-	/* if exit due to NM, handle before interrupts are enabled */
-	else if (is_nm_fault(intr_info))
-		handle_nm_fault_irqoff(vcpu);
-	/* Handle machine checks before interrupts are enabled */
-	else if (is_machine_check(intr_info))
-		kvm_machine_check();
-}
-
-static void handle_external_interrupt_irqoff(struct kvm_vcpu *vcpu,
-					     u32 intr_info)
-{
-	unsigned int vector = intr_info & INTR_INFO_VECTOR_MASK;
-	gate_desc *desc = (gate_desc *)host_idt_base + vector;
-
-	if (KVM_BUG(!is_external_intr(intr_info), vcpu->kvm,
-	    "unexpected VM-Exit interrupt info: 0x%x", intr_info))
-		return;
-
-	kvm_before_interrupt(vcpu, KVM_HANDLING_IRQ);
-	vmx_do_interrupt_irqoff(gate_offset(desc));
-	kvm_after_interrupt(vcpu);
-
-	vcpu->arch.at_instruction_boundary = true;
-}
-
  void vmx_handle_exit_irqoff(struct kvm_vcpu *vcpu)
  {
  	struct vcpu_vmx *vmx = to_vmx(vcpu);
@@ -6950,9 +6897,10 @@ void vmx_handle_exit_irqoff(struct kvm_vcpu *vcpu)
  		return;
 
  	if (vmx->exit_reason.basic == EXIT_REASON_EXTERNAL_INTERRUPT)
-		handle_external_interrupt_irqoff(vcpu, vmx_get_intr_info(vcpu));
+		vmx_handle_external_interrupt_irqoff(vcpu,
+						     vmx_get_intr_info(vcpu));
  	else if (vmx->exit_reason.basic == EXIT_REASON_EXCEPTION_NMI)
-		handle_exception_irqoff(vcpu, vmx_get_intr_info(vcpu));
+		vmx_handle_exception_irqoff(vcpu, vmx_get_intr_info(vcpu));
  }
 
  /*
@@ -8284,7 +8232,7 @@ __init int vmx_hardware_setup(void)
  	int r;
 
  	store_idt(&dt);
-	host_idt_base = dt.address;
+	vmx_host_idt_base = dt.address;
 
  	vmx_setup_user_return_msrs();
 
#554 lore raw

PATCH v19 97/130 KVM: x86: Split core of hypercall emulation to helper function

In reply to [email protected]

From: Sean Christopherson <[email protected]>

By necessity, TDX will use a different register ABI for hypercalls. Break out the core functionality so that it may be reused for TDX.

---
 arch/x86/include/asm/kvm_host.h |  4 +++
 arch/x86/kvm/x86.c              | 56 ++++++++++++++++++++++-----------
 2 files changed, 42 insertions(+), 18 deletions(-)
arch/x86/include/asm/kvm_host.h +4 -0
index e0ffef1d377d..bb8be091f996 100644
@@ -2177,6 +2177,10 @@ static inline void kvm_clear_apicv_inhibit(struct kvm *kvm,
 	kvm_set_or_clear_apicv_inhibit(kvm, reason, false);
 }
 
+unsigned long __kvm_emulate_hypercall(struct kvm_vcpu *vcpu, unsigned long nr,
+				      unsigned long a0, unsigned long a1,
+				      unsigned long a2, unsigned long a3,
+				      int op_64_bit, int cpl);
 int kvm_emulate_hypercall(struct kvm_vcpu *vcpu);
 
 int kvm_mmu_page_fault(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa, u64 error_code,
arch/x86/kvm/x86.c +38 -18

89 lines not on this page · raw message

Signature · 1 line
2.25.1
#555 lore raw

Re: [PATCH v19 097/130] KVM: x86: Split core of hypercall emulation to helper function

arch/x86/kvm/x86.c int kvm_emulate_hypercall(struct kvm_vcpu *vcpu) · line 10154

Quoted 5 lines · On Mon, Feb 26, 2024 at 12:26:39AM -0800, [email protected] wrote:
arch/x86/kvm/x86.c +1 -0
@@ -10162,18 +10151,49 @@ int kvm_emulate_hypercall(struct kvm_vcpu *vcpu)
 
 		WARN_ON_ONCE(vcpu->run->hypercall.flags & KVM_EXIT_HYPERCALL_MBZ);
 		vcpu->arch.complete_userspace_io = complete_hypercall_exit;
+		/* stat is incremented on completion. */

Perhaps we could use a distinct return value to signal that the request is redirected to userspace. This way, more cases can be supported, e.g., accesses to MTRR MSRs, requests to service TDs, etc. And then ...

Quoted 26 lines
arch/x86/kvm/x86.c +1 -0
 		return 0;
 	}
 	default:
 		ret = -KVM_ENOSYS;
 		break;
 	}
+

out:

arch/x86/kvm/x86.c +18 -0
+	++vcpu->stat.hypercalls;
+	return ret;
+}
+EXPORT_SYMBOL_GPL(__kvm_emulate_hypercall);
+
+int kvm_emulate_hypercall(struct kvm_vcpu *vcpu)
+{
+	unsigned long nr, a0, a1, a2, a3, ret;
+	int op_64_bit;
+	int cpl;
+
+	if (kvm_xen_hypercall_enabled(vcpu->kvm))
+		return kvm_xen_hypercall(vcpu);
+
+	if (kvm_hv_hypercall_enabled(vcpu))
+		return kvm_hv_hypercall(vcpu);
+
+	nr = kvm_rax_read(vcpu);

arch/x86/kvm/x86.c int kvm_emulate_hypercall(struct kvm_vcpu *vcpu) · line 10190

Quoted 10 lines
arch/x86/kvm/x86.c +10 -0
+	a0 = kvm_rbx_read(vcpu);
+	a1 = kvm_rcx_read(vcpu);
+	a2 = kvm_rdx_read(vcpu);
+	a3 = kvm_rsi_read(vcpu);
+	op_64_bit = is_64_bit_hypercall(vcpu);
+	cpl = static_call(kvm_x86_get_cpl)(vcpu);
+
+	ret = __kvm_emulate_hypercall(vcpu, nr, a0, a1, a2, a3, op_64_bit, cpl);
+	if (nr == KVM_HC_MAP_GPA_RANGE && !ret)
+		/* MAP_GPA tosses the request to the user space. */

no need to check what the request is. Just checking the return value will suffice.

Quoted 14 lines
arch/x86/kvm/x86.c +2 -0
+		return 0;
+
	if (!op_64_bit)
		ret = (u32)ret;
	kvm_rax_write(vcpu, ret);
arch/x86/kvm/x86.c +0 -1
-	++vcpu->stat.hypercalls;
	return kvm_skip_emulated_instruction(vcpu);
}

EXPORT_SYMBOL_GPL(kvm_emulate_hypercall);

Signature · 1 line
2.25.1
#556 lore raw

Re: [PATCH v19 097/130] KVM: x86: Split core of hypercall emulation to helper function

Quoted 10 lines · Chao Gao

arch/x86/kvm/x86.c int kvm_emulate_hypercall(struct kvm_vcpu *vcpu) · line 10154

Quoted 5 lines · On Mon, Feb 26, 2024 at 12:26:39AM -0800, [email protected] wrote:
arch/x86/kvm/x86.c +1 -0
@@ -10162,18 +10151,49 @@ int kvm_emulate_hypercall(struct kvm_vcpu *vcpu)
 
 		WARN_ON_ONCE(vcpu->run->hypercall.flags & KVM_EXIT_HYPERCALL_MBZ);
 		vcpu->arch.complete_userspace_io = complete_hypercall_exit;
+		/* stat is incremented on completion. */

Perhaps we could use a distinct return value to signal that the request is redirected to userspace. This way, more cases can be supported, e.g., accesses to MTRR MSRs, requests to service TDs, etc. And then ...

The convention here is the one for exit_handler vcpu_enter_guest() already uses. If we introduce something like KVM_VCPU_CONTINUE=1, KVM_VCPU_EXIT_TO_USER=0, it will touch many places. So if we will (I'm not sure it's worthwhile), the cleanup should be done as independently.

Quoted 30 lines
Quoted 30 lines
arch/x86/kvm/x86.c +1 -0
 		return 0;
 	}
 	default:
 		ret = -KVM_ENOSYS;
 		break;
 	}
+

out:

arch/x86/kvm/x86.c +22 -0
+	++vcpu->stat.hypercalls;
+	return ret;
+}
+EXPORT_SYMBOL_GPL(__kvm_emulate_hypercall);
+
+int kvm_emulate_hypercall(struct kvm_vcpu *vcpu)
+{
+	unsigned long nr, a0, a1, a2, a3, ret;
+	int op_64_bit;
+	int cpl;
+
+	if (kvm_xen_hypercall_enabled(vcpu->kvm))
+		return kvm_xen_hypercall(vcpu);
+
+	if (kvm_hv_hypercall_enabled(vcpu))
+		return kvm_hv_hypercall(vcpu);
+
+	nr = kvm_rax_read(vcpu);
+	a0 = kvm_rbx_read(vcpu);
+	a1 = kvm_rcx_read(vcpu);
+	a2 = kvm_rdx_read(vcpu);
+	a3 = kvm_rsi_read(vcpu);
Quoted 8 lines

arch/x86/kvm/x86.c int kvm_emulate_hypercall(struct kvm_vcpu *vcpu) · line 10190

Quoted 6 lines
arch/x86/kvm/x86.c +6 -0
+	op_64_bit = is_64_bit_hypercall(vcpu);
+	cpl = static_call(kvm_x86_get_cpl)(vcpu);
+
+	ret = __kvm_emulate_hypercall(vcpu, nr, a0, a1, a2, a3, op_64_bit, cpl);
+	if (nr == KVM_HC_MAP_GPA_RANGE && !ret)
+		/* MAP_GPA tosses the request to the user space. */

no need to check what the request is. Just checking the return value will suffice.

This is needed to avoid updating rax etc. KVM_HC_MAP_GPA_RANGE is only an exception to go to the user space. This check is a bit weird, but I couldn't find a good way.

Quoted 16 lines
Quoted 15 lines
arch/x86/kvm/x86.c +2 -0
+		return 0;
+
	if (!op_64_bit)
		ret = (u32)ret;
	kvm_rax_write(vcpu, ret);
arch/x86/kvm/x86.c +0 -1
-	++vcpu->stat.hypercalls;
	return kvm_skip_emulated_instruction(vcpu);
}

EXPORT_SYMBOL_GPL(kvm_emulate_hypercall);

Signature · 1 line
2.25.1
Signature · 1 line
Isaku Yamahata <[email protected]>
#557 lore raw

Re: [PATCH v19 097/130] KVM: x86: Split core of hypercall emulation to helper function

Quoted 14 lines · Isaku Yamahata
Quoted 10 lines · Chao Gao

arch/x86/kvm/x86.c int kvm_emulate_hypercall(struct kvm_vcpu *vcpu) · line 10154

Quoted 5 lines · On Mon, Feb 26, 2024 at 12:26:39AM -0800, [email protected] wrote:
arch/x86/kvm/x86.c +1 -0
@@ -10162,18 +10151,49 @@ int kvm_emulate_hypercall(struct kvm_vcpu *vcpu)
 
 		WARN_ON_ONCE(vcpu->run->hypercall.flags & KVM_EXIT_HYPERCALL_MBZ);
 		vcpu->arch.complete_userspace_io = complete_hypercall_exit;
+		/* stat is incremented on completion. */

Perhaps we could use a distinct return value to signal that the request is redirected to userspace. This way, more cases can be supported, e.g., accesses to MTRR MSRs, requests to service TDs, etc. And then ...

Quoted 4 lines

The convention here is the one for exit_handler vcpu_enter_guest() already uses. If we introduce something like KVM_VCPU_CONTINUE=1, KVM_VCPU_EXIT_TO_USER=0, it will touch many places. So if we will (I'm not sure it's worthwhile), the cleanup should be done as independently.

Yeah, this is far from the first time that someone has complained about KVM's awful 1/0 return magic. And every time we've looked at it, we've come to the conclusion that it's not worth the churn/risk.

And if we really need to further overload the return value, we can, e.g. KVM already does this for MSR accesses:

/*
* Internal error codes that are used to indicate that MSR emulation encountered
* an error that should result in #GP in the guest, unless userspace
* handles it.
*/
#define KVM_MSR_RET_INVALID 2 /* in-kernel MSR emulation #GP condition */
#define KVM_MSR_RET_FILTERED 3 /* #GP due to userspace MSR filter */

#558 lore raw

Re: [PATCH v19 097/130] KVM: x86: Split core of hypercall emulation to helper function

In reply to Isaku Yamahata
Quoted 29 lines · Isaku Yamahata
Quoted 18 lines · Chao Gao
Quoted 17 lines · On Mon, Feb 26, 2024 at 12:26:39AM -0800, [email protected] wrote:
arch/x86/kvm/x86.c +17 -0
+
+int kvm_emulate_hypercall(struct kvm_vcpu *vcpu)
+{
+	unsigned long nr, a0, a1, a2, a3, ret;
+	int op_64_bit;
+	int cpl;
+
+	if (kvm_xen_hypercall_enabled(vcpu->kvm))
+		return kvm_xen_hypercall(vcpu);
+
+	if (kvm_hv_hypercall_enabled(vcpu))
+		return kvm_hv_hypercall(vcpu);
+
+	nr = kvm_rax_read(vcpu);
+	a0 = kvm_rbx_read(vcpu);
+	a1 = kvm_rcx_read(vcpu);
+	a2 = kvm_rdx_read(vcpu);
Quoted 8 lines

arch/x86/kvm/x86.c int kvm_emulate_hypercall(struct kvm_vcpu *vcpu) · line 10190

Quoted 7 lines
arch/x86/kvm/x86.c +7 -0
+	a3 = kvm_rsi_read(vcpu);
+	op_64_bit = is_64_bit_hypercall(vcpu);
+	cpl = static_call(kvm_x86_get_cpl)(vcpu);
+
+	ret = __kvm_emulate_hypercall(vcpu, nr, a0, a1, a2, a3, op_64_bit, cpl);
+	if (nr == KVM_HC_MAP_GPA_RANGE && !ret)
+		/* MAP_GPA tosses the request to the user space. */

no need to check what the request is. Just checking the return value will suffice.

Quoted 3 lines

This is needed to avoid updating rax etc. KVM_HC_MAP_GPA_RANGE is only an exception to go to the user space. This check is a bit weird, but I couldn't find a good way.

To be generic, I think we can use
"vcpu->kvm->arch.hypercall_exit_enabled & (1 << nr)" to check if it needs to exit to userspace.

i.e.,

+       ...
+       ret = __kvm_emulate_hypercall(vcpu, nr, a0, a1, a2, a3, op_64_bit, cpl); +       if (!ret && (vcpu->kvm->arch.hypercall_exit_enabled & (1 << nr))) +               /* The hypercall is requested to exit to userspace. */ +               return 0;

Quoted 15 lines
Quoted 15 lines
Quoted 15 lines
arch/x86/kvm/x86.c +2 -0
+		return 0;
+
	if (!op_64_bit)
		ret = (u32)ret;
	kvm_rax_write(vcpu, ret);
arch/x86/kvm/x86.c +0 -1
-	++vcpu->stat.hypercalls;
	return kvm_skip_emulated_instruction(vcpu);
}

EXPORT_SYMBOL_GPL(kvm_emulate_hypercall);

Signature · 1 line
2.25.1
#559 lore raw

Re: [PATCH v19 097/130] KVM: x86: Split core of hypercall emulation to helper function

In reply to [email protected]
Quoted 84 lines · On 2/26/2024 4:26 PM, [email protected] wrote:

From: Sean Christopherson <[email protected]>

By necessity, TDX will use a different register ABI for hypercalls. Break out the core functionality so that it may be reused for TDX.

---
  arch/x86/include/asm/kvm_host.h |  4 +++
  arch/x86/kvm/x86.c              | 56 ++++++++++++++++++++++-----------
  2 files changed, 42 insertions(+), 18 deletions(-)
arch/x86/include/asm/kvm_host.h +4 -0
index e0ffef1d377d..bb8be091f996 100644
@@ -2177,6 +2177,10 @@ static inline void kvm_clear_apicv_inhibit(struct kvm *kvm,
  	kvm_set_or_clear_apicv_inhibit(kvm, reason, false);
  }
 
+unsigned long __kvm_emulate_hypercall(struct kvm_vcpu *vcpu, unsigned long nr,
+				      unsigned long a0, unsigned long a1,
+				      unsigned long a2, unsigned long a3,
+				      int op_64_bit, int cpl);
  int kvm_emulate_hypercall(struct kvm_vcpu *vcpu);
 
  int kvm_mmu_page_fault(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa, u64 error_code,
arch/x86/kvm/x86.c +8 -17
index fb7597c22f31..03950368d8db 100644
@@ -10073,26 +10073,15 @@ static int complete_hypercall_exit(struct kvm_vcpu *vcpu)
  	return kvm_skip_emulated_instruction(vcpu);
  }
 
-int kvm_emulate_hypercall(struct kvm_vcpu *vcpu)
+unsigned long __kvm_emulate_hypercall(struct kvm_vcpu *vcpu, unsigned long nr,
+				      unsigned long a0, unsigned long a1,
+				      unsigned long a2, unsigned long a3,
+				      int op_64_bit, int cpl)
  {
-	unsigned long nr, a0, a1, a2, a3, ret;
-	int op_64_bit;
-
-	if (kvm_xen_hypercall_enabled(vcpu->kvm))
-		return kvm_xen_hypercall(vcpu);
-
-	if (kvm_hv_hypercall_enabled(vcpu))
-		return kvm_hv_hypercall(vcpu);
-
-	nr = kvm_rax_read(vcpu);
-	a0 = kvm_rbx_read(vcpu);
-	a1 = kvm_rcx_read(vcpu);
-	a2 = kvm_rdx_read(vcpu);
-	a3 = kvm_rsi_read(vcpu);
+	unsigned long ret;
 
  	trace_kvm_hypercall(nr, a0, a1, a2, a3);
 
-	op_64_bit = is_64_bit_hypercall(vcpu);
  	if (!op_64_bit) {
  		nr &= 0xFFFFFFFF;
  		a0 &= 0xFFFFFFFF;
@@ -10101,7 +10090,7 @@ int kvm_emulate_hypercall(struct kvm_vcpu *vcpu)
  		a3 &= 0xFFFFFFFF;
  	}
 
-	if (static_call(kvm_x86_get_cpl)(vcpu) != 0) {
+	if (cpl) {
  		ret = -KVM_EPERM;
  		goto out;
  	}
@@ -10162,18 +10151,49 @@ int kvm_emulate_hypercall(struct kvm_vcpu *vcpu)
 
  		WARN_ON_ONCE(vcpu->run->hypercall.flags & KVM_EXIT_HYPERCALL_MBZ);
  		vcpu->arch.complete_userspace_io = complete_hypercall_exit;
+		/* stat is incremented on completion. */
  		return 0;
  	}
  	default:
  		ret = -KVM_ENOSYS;
  		break;
  	}
+

arch/x86/kvm/x86.c int kvm_emulate_hypercall(struct kvm_vcpu *vcpu) · line 10171

Quoted 10 lines
arch/x86/kvm/x86.c +9 -0
  out:
+	++vcpu->stat.hypercalls;
+	return ret;
+}
+EXPORT_SYMBOL_GPL(__kvm_emulate_hypercall);
+
+int kvm_emulate_hypercall(struct kvm_vcpu *vcpu)
+{
+	unsigned long nr, a0, a1, a2, a3, ret;
+	int op_64_bit;

Can it be opportunistically changed to bool type, as well as the argument type of "op_64_bit" in __kvm_emulate_hypercall()?

Quoted 29 lines
arch/x86/kvm/x86.c +21 -1
+	int cpl;
+
+	if (kvm_xen_hypercall_enabled(vcpu->kvm))
+		return kvm_xen_hypercall(vcpu);
+
+	if (kvm_hv_hypercall_enabled(vcpu))
+		return kvm_hv_hypercall(vcpu);
+
+	nr = kvm_rax_read(vcpu);
+	a0 = kvm_rbx_read(vcpu);
+	a1 = kvm_rcx_read(vcpu);
+	a2 = kvm_rdx_read(vcpu);
+	a3 = kvm_rsi_read(vcpu);
+	op_64_bit = is_64_bit_hypercall(vcpu);
+	cpl = static_call(kvm_x86_get_cpl)(vcpu);
+
+	ret = __kvm_emulate_hypercall(vcpu, nr, a0, a1, a2, a3, op_64_bit, cpl);
+	if (nr == KVM_HC_MAP_GPA_RANGE && !ret)
+		/* MAP_GPA tosses the request to the user space. */
+		return 0;
+
  	if (!op_64_bit)
  		ret = (u32)ret;
  	kvm_rax_write(vcpu, ret);
 
-	++vcpu->stat.hypercalls;
  	return kvm_skip_emulated_instruction(vcpu);
  }
  EXPORT_SYMBOL_GPL(kvm_emulate_hypercall);
#560 lore raw

Re: [PATCH v19 097/130] KVM: x86: Split core of hypercall emulation to helper function

Quoted 7 lines · Binbin Wu

arch/x86/kvm/x86.c int kvm_emulate_hypercall(struct kvm_vcpu *vcpu) · line 10171

Quoted 4 lines
arch/x86/kvm/x86.c +4 -0
+int kvm_emulate_hypercall(struct kvm_vcpu *vcpu)
+{
+	unsigned long nr, a0, a1, a2, a3, ret;
+	int op_64_bit;

Can it be opportunistically changed to bool type, as well as the argument type of "op_64_bit" in __kvm_emulate_hypercall()?

Yes. We can also fix kvm_pv_send_ipi(op_64_bit).

Signature · 1 line
Isaku Yamahata <[email protected]>
#561 lore raw

PATCH v19 98/130 KVM: TDX: Add a place holder to handle TDX VM exit

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

Wire up handle_exit and handle_exit_irqoff methods and add a place holder to handle VM exit. Add helper functions to get exit info, exit qualification, etc.

---
 arch/x86/kvm/vmx/main.c    |  37 ++++++++++++-
 arch/x86/kvm/vmx/tdx.c     | 110 +++++++++++++++++++++++++++++++++++++
 arch/x86/kvm/vmx/x86_ops.h |  10 ++++
 3 files changed, 154 insertions(+), 3 deletions(-)
arch/x86/kvm/vmx/main.c +34 -3
index 6d6d443a2bbd..c9a40456d965 100644
@@ -228,6 +228,25 @@ static bool vt_protected_apic_has_interrupt(struct kvm_vcpu *vcpu)
 	return tdx_protected_apic_has_interrupt(vcpu);
 }
 
+static int vt_handle_exit(struct kvm_vcpu *vcpu,
+			  enum exit_fastpath_completion fastpath)
+{
+	if (is_td_vcpu(vcpu))
+		return tdx_handle_exit(vcpu, fastpath);
+
+	return vmx_handle_exit(vcpu, fastpath);
+}
+
+static void vt_handle_exit_irqoff(struct kvm_vcpu *vcpu)
+{
+	if (is_td_vcpu(vcpu)) {
+		tdx_handle_exit_irqoff(vcpu);
+		return;
+	}
+
+	vmx_handle_exit_irqoff(vcpu);
+}
+
 static void vt_apicv_pre_state_restore(struct kvm_vcpu *vcpu)
 {
 	struct pi_desc *pi = vcpu_to_pi_desc(vcpu);
@@ -436,6 +455,18 @@ static void vt_request_immediate_exit(struct kvm_vcpu *vcpu)
 	vmx_request_immediate_exit(vcpu);
 }
 
+static void vt_get_exit_info(struct kvm_vcpu *vcpu, u32 *reason,
+			u64 *info1, u64 *info2, u32 *intr_info, u32 *error_code)
+{
+	if (is_td_vcpu(vcpu)) {
+		tdx_get_exit_info(vcpu, reason, info1, info2, intr_info,
+				  error_code);
+		return;
+	}
+
+	vmx_get_exit_info(vcpu, reason, info1, info2, intr_info, error_code);
+}
+
 static u8 vt_get_mt_mask(struct kvm_vcpu *vcpu, gfn_t gfn, bool is_mmio)
 {
 	if (is_td_vcpu(vcpu))
@@ -562,7 +593,7 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
 
 	.vcpu_pre_run = vt_vcpu_pre_run,
 	.vcpu_run = vt_vcpu_run,
-	.handle_exit = vmx_handle_exit,
+	.handle_exit = vt_handle_exit,
 	.skip_emulated_instruction = vmx_skip_emulated_instruction,
 	.update_emulated_instruction = vmx_update_emulated_instruction,
 	.set_interrupt_shadow = vt_set_interrupt_shadow,
@@ -597,7 +628,7 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
 	.set_identity_map_addr = vmx_set_identity_map_addr,
 	.get_mt_mask = vt_get_mt_mask,
 
-	.get_exit_info = vmx_get_exit_info,
+	.get_exit_info = vt_get_exit_info,
 
 	.vcpu_after_set_cpuid = vmx_vcpu_after_set_cpuid,
 
@@ -611,7 +642,7 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
 	.load_mmu_pgd = vt_load_mmu_pgd,
 
 	.check_intercept = vmx_check_intercept,
-	.handle_exit_irqoff = vmx_handle_exit_irqoff,
+	.handle_exit_irqoff = vt_handle_exit_irqoff,
 
 	.request_immediate_exit = vt_request_immediate_exit,
 
arch/x86/kvm/vmx/tdx.c +110 -0

134 lines not on this page · raw message

arch/x86/kvm/vmx/x86_ops.h +10 -0
index 539f3f9686fe..a12e3bfc96dd 100644
@@ -155,11 +155,16 @@ void tdx_prepare_switch_to_guest(struct kvm_vcpu *vcpu);
 void tdx_vcpu_put(struct kvm_vcpu *vcpu);
 void tdx_vcpu_load(struct kvm_vcpu *vcpu, int cpu);
 bool tdx_protected_apic_has_interrupt(struct kvm_vcpu *vcpu);
+void tdx_handle_exit_irqoff(struct kvm_vcpu *vcpu);
+int tdx_handle_exit(struct kvm_vcpu *vcpu,
+		enum exit_fastpath_completion fastpath);
 u8 tdx_get_mt_mask(struct kvm_vcpu *vcpu, gfn_t gfn, bool is_mmio);
 
 void tdx_deliver_interrupt(struct kvm_lapic *apic, int delivery_mode,
 			   int trig_mode, int vector);
 void tdx_inject_nmi(struct kvm_vcpu *vcpu);
+void tdx_get_exit_info(struct kvm_vcpu *vcpu, u32 *reason,
+		u64 *info1, u64 *info2, u32 *intr_info, u32 *error_code);
 
 int tdx_vcpu_ioctl(struct kvm_vcpu *vcpu, void __user *argp);
 
@@ -199,11 +204,16 @@ static inline void tdx_prepare_switch_to_guest(struct kvm_vcpu *vcpu) {}
 static inline void tdx_vcpu_put(struct kvm_vcpu *vcpu) {}
 static inline void tdx_vcpu_load(struct kvm_vcpu *vcpu, int cpu) {}
 static inline bool tdx_protected_apic_has_interrupt(struct kvm_vcpu *vcpu) { return false; }
+static inline void tdx_handle_exit_irqoff(struct kvm_vcpu *vcpu) {}
+static inline int tdx_handle_exit(struct kvm_vcpu *vcpu,
+		enum exit_fastpath_completion fastpath) { return 0; }
 static inline u8 tdx_get_mt_mask(struct kvm_vcpu *vcpu, gfn_t gfn, bool is_mmio) { return 0; }
 
 static inline void tdx_deliver_interrupt(struct kvm_lapic *apic, int delivery_mode,
 					 int trig_mode, int vector) {}
 static inline void tdx_inject_nmi(struct kvm_vcpu *vcpu) {}
+static inline void tdx_get_exit_info(struct kvm_vcpu *vcpu, u32 *reason, u64 *info1,
+				     u64 *info2, u32 *intr_info, u32 *error_code) {}
 
 static inline int tdx_vcpu_ioctl(struct kvm_vcpu *vcpu, void __user *argp) { return -EOPNOTSUPP; }
 
Signature · 1 line
2.25.1
#562 lore raw

Re: [PATCH v19 098/130] KVM: TDX: Add a place holder to handle TDX VM exit

Quoted 12 lines · On Mon, Feb 26, 2024, [email protected] wrote:
arch/x86/kvm/vmx/tdx.c +12 -0
+int tdx_handle_exit(struct kvm_vcpu *vcpu, fastpath_t fastpath)
+{
+	union tdx_exit_reason exit_reason = to_tdx(vcpu)->exit_reason;
+
+	/* See the comment of tdh_sept_seamcall(). */
+	if (unlikely(exit_reason.full == (TDX_OPERAND_BUSY | TDX_OPERAND_ID_SEPT)))
+		return 1;
+
+	/*
+	 * TDH.VP.ENTRY checks TD EPOCH which contend with TDH.MEM.TRACK and
+	 * vcpu TDH.VP.ENTER.
+	 */

arch/x86/kvm/vmx/tdx.c void tdx_deliver_interrupt(struct kvm_lapic *apic, int delivery_mode, · line 1309

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +10 -0
+	if (unlikely(exit_reason.full == (TDX_OPERAND_BUSY | TDX_OPERAND_ID_TD_EPOCH)))
+		return 1;
+
+	if (unlikely(exit_reason.full == TDX_SEAMCALL_UD)) {
+		kvm_spurious_fault();
+		/*
+		 * In the case of reboot or kexec, loop with TDH.VP.ENTER and
+		 * TDX_SEAMCALL_UD to avoid unnecessarily activity.
+		 */
+		return 1;

No. This is unnecessarily risky. KVM_BUG_ON() and exit to userspace. The response to "SEAMCALL faulted" should never be, "well, let's try again!".

Also, what about #GP on SEAMCALL? In general, the error handling here seems lacking.

#563 lore raw

Re: [PATCH v19 098/130] KVM: TDX: Add a place holder to handle TDX VM exit

Quoted 27 lines · Sean Christopherson
Quoted 12 lines · On Mon, Feb 26, 2024, [email protected] wrote:
arch/x86/kvm/vmx/tdx.c +12 -0
+int tdx_handle_exit(struct kvm_vcpu *vcpu, fastpath_t fastpath)
+{
+	union tdx_exit_reason exit_reason = to_tdx(vcpu)->exit_reason;
+
+	/* See the comment of tdh_sept_seamcall(). */
+	if (unlikely(exit_reason.full == (TDX_OPERAND_BUSY | TDX_OPERAND_ID_SEPT)))
+		return 1;
+
+	/*
+	 * TDH.VP.ENTRY checks TD EPOCH which contend with TDH.MEM.TRACK and
+	 * vcpu TDH.VP.ENTER.
+	 */

arch/x86/kvm/vmx/tdx.c void tdx_deliver_interrupt(struct kvm_lapic *apic, int delivery_mode, · line 1309

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +10 -0
+	if (unlikely(exit_reason.full == (TDX_OPERAND_BUSY | TDX_OPERAND_ID_TD_EPOCH)))
+		return 1;
+
+	if (unlikely(exit_reason.full == TDX_SEAMCALL_UD)) {
+		kvm_spurious_fault();
+		/*
+		 * In the case of reboot or kexec, loop with TDH.VP.ENTER and
+		 * TDX_SEAMCALL_UD to avoid unnecessarily activity.
+		 */
+		return 1;

No. This is unnecessarily risky. KVM_BUG_ON() and exit to userspace. The response to "SEAMCALL faulted" should never be, "well, let's try again!".

Quoted 2 lines

Also, what about #GP on SEAMCALL? In general, the error handling here seems lacking.

As I replied at [1], let me revise error handling in general TDX KVM code.
[1] https://lore.kernel.org/kvm/[email protected]/T/#macc431c87676995d65ddcd8de632261a2dedc525

Signature · 1 line
Isaku Yamahata <[email protected]>
#564 lore raw

Re: [PATCH v19 098/130] KVM: TDX: Add a place holder to handle TDX VM exit

In reply to [email protected]
Quoted 3 lines · On 2/26/2024 4:26 PM, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

Wire up handle_exit and handle_exit_irqoff methods

This patch also wires up get_exit_info.

Quoted 12 lines

and add a place holder
to handle VM exit. Add helper functions to get exit info, exit qualification, etc.

---
  arch/x86/kvm/vmx/main.c    |  37 ++++++++++++-
  arch/x86/kvm/vmx/tdx.c     | 110 +++++++++++++++++++++++++++++++++++++
  arch/x86/kvm/vmx/x86_ops.h |  10 ++++
  3 files changed, 154 insertions(+), 3 deletions(-)

[...]

Quoted 27 lines
arch/x86/kvm/vmx/main.c +3 -3
@@ -562,7 +593,7 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
 
  	.vcpu_pre_run = vt_vcpu_pre_run,
  	.vcpu_run = vt_vcpu_run,
-	.handle_exit = vmx_handle_exit,
+	.handle_exit = vt_handle_exit,
  	.skip_emulated_instruction = vmx_skip_emulated_instruction,
  	.update_emulated_instruction = vmx_update_emulated_instruction,
  	.set_interrupt_shadow = vt_set_interrupt_shadow,
@@ -597,7 +628,7 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
  	.set_identity_map_addr = vmx_set_identity_map_addr,
  	.get_mt_mask = vt_get_mt_mask,
 
-	.get_exit_info = vmx_get_exit_info,
+	.get_exit_info = vt_get_exit_info,
 
  	.vcpu_after_set_cpuid = vmx_vcpu_after_set_cpuid,
 
@@ -611,7 +642,7 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
  	.load_mmu_pgd = vt_load_mmu_pgd,
 
  	.check_intercept = vmx_check_intercept,
-	.handle_exit_irqoff = vmx_handle_exit_irqoff,
+	.handle_exit_irqoff = vt_handle_exit_irqoff,
 
  	.request_immediate_exit = vt_request_immediate_exit,
 

[...]

arch/x86/kvm/vmx/tdx.c void tdx_deliver_interrupt(struct kvm_lapic *apic, int delivery_mode, · line 1292

Quoted 6 lines
arch/x86/kvm/vmx/tdx.c +5 -0
+int tdx_handle_exit(struct kvm_vcpu *vcpu, fastpath_t fastpath)
+{
+	union tdx_exit_reason exit_reason = to_tdx(vcpu)->exit_reason;
+
+	/* See the comment of tdh_sept_seamcall(). */

Should be tdx_seamcall_sept().

arch/x86/kvm/vmx/tdx.c void tdx_deliver_interrupt(struct kvm_lapic *apic, int delivery_mode, · line 1293

Quoted 1 line
arch/x86/kvm/vmx/tdx.c +1 -0
+	if (unlikely(exit_reason.full == (TDX_OPERAND_BUSY | TDX_OPERAND_ID_SEPT)))

Can use "TDX_ERROR_SEPT_BUSY" instead.

arch/x86/kvm/vmx/tdx.c void tdx_deliver_interrupt(struct kvm_lapic *apic, int delivery_mode,

Quoted 4 lines
arch/x86/kvm/vmx/tdx.c +4 -0
+		return 1;
+
+	/*
+	 * TDH.VP.ENTRY

"TDH.VP.ENTRY" -> "TDH.VP.ENTER"

arch/x86/kvm/vmx/tdx.c void tdx_deliver_interrupt(struct kvm_lapic *apic, int delivery_mode, · line 1298

Quoted 2 lines

checks TD EPOCH which contend with TDH.MEM.TRACK and

arch/x86/kvm/vmx/tdx.c +1 -0
+	 * vcpu TDH.VP.ENTER.

Do you mean TDH.VP.ENTER on one vcpu can contend with TDH.MEM.TRACK and TDH.VP.ENTER on another vcpu?

Quoted 2 lines
(quoted diff) +1 -0
+	 */
#565 lore raw

Re: [PATCH v19 098/130] KVM: TDX: Add a place holder to handle TDX VM exit

Quoted 11 lines · Binbin Wu

arch/x86/kvm/vmx/tdx.c void tdx_deliver_interrupt(struct kvm_lapic *apic, int delivery_mode,

Quoted 4 lines
arch/x86/kvm/vmx/tdx.c +4 -0
+		return 1;
+
+	/*
+	 * TDH.VP.ENTRY

"TDH.VP.ENTRY" -> "TDH.VP.ENTER"

arch/x86/kvm/vmx/tdx.c void tdx_deliver_interrupt(struct kvm_lapic *apic, int delivery_mode, · line 1298

Quoted 2 lines

checks TD EPOCH which contend with TDH.MEM.TRACK and

arch/x86/kvm/vmx/tdx.c +1 -0
+	 * vcpu TDH.VP.ENTER.

Do you mean TDH.VP.ENTER on one vcpu can contend with TDH.MEM.TRACK and TDH.VP.ENTER on another vcpu?

Yes. The caller of TDH.MEM.TRACK() must ensure that other vCPUS go through inactive (not running vCPU) after TDH.MEM.TRACK().

Signature · 1 line
Isaku Yamahata <[email protected]>
#566 lore raw

PATCH v19 99/130 KVM: TDX: Handle vmentry failure for INTEL TD guest

In reply to [email protected]

From: Yao Yuan <[email protected]>

TDX module passes control back to VMM if it failed to vmentry for a TD, use same exit reason to notify user space, align with VMX.
If VMM corrupted TD VMCS, machine check during entry can happens. vm exit reason will be EXIT_REASON_MCE_DURING_VMENTRY. If VMM corrupted TD VMCS with debug TD by TDH.VP.WR, the exit reason would be EXIT_REASON_INVALID_STATE or EXIT_REASON_MSR_LOAD_FAIL.

---
 arch/x86/kvm/vmx/tdx.c | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)
arch/x86/kvm/vmx/tdx.c +22 -0
index 71ab48cf72ba..cba0fd5029be 100644
@@ -1320,6 +1320,28 @@ int tdx_handle_exit(struct kvm_vcpu *vcpu, fastpath_t fastpath)
 		goto unhandled_exit;
 	}
 
+	/*
+	 * When TDX module saw VMEXIT_REASON_FAILED_VMENTER_MC etc, TDH.VP.ENTER
+	 * returns with TDX_SUCCESS | exit_reason with failed_vmentry = 1.
+	 * Because TDX module maintains TD VMCS correctness, usually vmentry
+	 * failure shouldn't happen.  In some corner cases it can happen.  For
+	 * example
+	 * - machine check during entry: EXIT_REASON_MCE_DURING_VMENTRY
+	 * - TDH.VP.WR with debug TD.  VMM can corrupt TD VMCS
+	 *   - EXIT_REASON_INVALID_STATE
+	 *   - EXIT_REASON_MSR_LOAD_FAIL
+	 */
+	if (unlikely(exit_reason.failed_vmentry)) {
+		pr_err("TDExit: exit_reason 0x%016llx qualification=%016lx ext_qualification=%016lx\n",
+		       exit_reason.full, tdexit_exit_qual(vcpu), tdexit_ext_exit_qual(vcpu));
+		vcpu->run->exit_reason = KVM_EXIT_FAIL_ENTRY;
+		vcpu->run->fail_entry.hardware_entry_failure_reason
+			= exit_reason.full;
+		vcpu->run->fail_entry.cpu = vcpu->arch.last_vmentry_cpu;
+
+		return 0;
+	}
+
 	WARN_ON_ONCE(fastpath != EXIT_FASTPATH_NONE);
 
 	switch (exit_reason.basic) {
Signature · 1 line
2.25.1
#567 lore raw

PATCH v19 100/130 KVM: TDX: handle EXIT_REASON_OTHER_SMI

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

If the control reaches EXIT_REASON_OTHER_SMI, #SMI is delivered and handled right after returning from the TDX module to KVM nothing needs to be done in KVM. Continue TDX vcpu execution.

---
 arch/x86/include/uapi/asm/vmx.h | 1 +
 arch/x86/kvm/vmx/tdx.c          | 7 +++++++
 2 files changed, 8 insertions(+)
arch/x86/include/uapi/asm/vmx.h +1 -0
index a5faf6d88f1b..b3a30ef3efdd 100644
@@ -34,6 +34,7 @@
 #define EXIT_REASON_TRIPLE_FAULT        2
 #define EXIT_REASON_INIT_SIGNAL			3
 #define EXIT_REASON_SIPI_SIGNAL         4
+#define EXIT_REASON_OTHER_SMI           6
 
 #define EXIT_REASON_INTERRUPT_WINDOW    7
 #define EXIT_REASON_NMI_WINDOW          8
arch/x86/kvm/vmx/tdx.c +7 -0
index cba0fd5029be..2f68e6f2b53a 100644
@@ -1345,6 +1345,13 @@ int tdx_handle_exit(struct kvm_vcpu *vcpu, fastpath_t fastpath)
 	WARN_ON_ONCE(fastpath != EXIT_FASTPATH_NONE);
 
 	switch (exit_reason.basic) {
+	case EXIT_REASON_OTHER_SMI:
+		/*
+		 * If reach here, it's not a Machine Check System Management
+		 * Interrupt(MSMI).  #SMI is delivered and handled right after
+		 * SEAMRET, nothing needs to be done in KVM.
+		 */
+		return 1;
 	default:
 		break;
 	}
Signature · 1 line
2.25.1
#568 lore raw

Re: [PATCH v19 100/130] KVM: TDX: handle EXIT_REASON_OTHER_SMI

Quoted 4 lines · On 2/26/2024 4:26 PM, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

If the control reaches EXIT_REASON_OTHER_SMI, #SMI is delivered and handled right after returning from the TDX module to KVM

need a "," here

Quoted 15 lines

nothing needs to
be done in KVM. Continue TDX vcpu execution.

---
  arch/x86/include/uapi/asm/vmx.h | 1 +
  arch/x86/kvm/vmx/tdx.c          | 7 +++++++
  2 files changed, 8 insertions(+)
arch/x86/include/uapi/asm/vmx.h +0 -0
index a5faf6d88f1b..b3a30ef3efdd 100644

arch/x86/include/uapi/asm/vmx.h line 37

Quoted 4 lines
arch/x86/include/uapi/asm/vmx.h +1 -0
@@ -34,6 +34,7 @@
  #define EXIT_REASON_TRIPLE_FAULT        2
  #define EXIT_REASON_INIT_SIGNAL			3
  #define EXIT_REASON_SIPI_SIGNAL         4
+#define EXIT_REASON_OTHER_SMI           6

What does "OTHER" mean in this macro?

Quoted 8 lines
arch/x86/include/uapi/asm/vmx.h +0 -0
  #define EXIT_REASON_INTERRUPT_WINDOW    7
  #define EXIT_REASON_NMI_WINDOW          8
arch/x86/kvm/vmx/tdx.c +0 -0
index cba0fd5029be..2f68e6f2b53a 100644

arch/x86/kvm/vmx/tdx.c int tdx_handle_exit(struct kvm_vcpu *vcpu, fastpath_t fastpath) · line 1351

Quoted 7 lines
arch/x86/kvm/vmx/tdx.c +4 -0
@@ -1345,6 +1345,13 @@ int tdx_handle_exit(struct kvm_vcpu *vcpu, fastpath_t fastpath)
  	WARN_ON_ONCE(fastpath != EXIT_FASTPATH_NONE);
 
  	switch (exit_reason.basic) {
+	case EXIT_REASON_OTHER_SMI:
+		/*
+		 * If reach here, it's not a Machine Check System Management
+		 * Interrupt(MSMI).

Since it's the first patch that mentions MSMI, maybe some description about it in the changelog can make it easier to understand.

Quoted 7 lines
arch/x86/kvm/vmx/tdx.c +3 -0
   #SMI is delivered and handled right after
+		 * SEAMRET, nothing needs to be done in KVM.
+		 */
+		return 1;
  	default:
  		break;
  	}
#569 lore raw

PATCH v19 101/130 KVM: TDX: handle ept violation/misconfig exit

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

On EPT violation, call a common function, __vmx_handle_ept_violation() to trigger x86 MMU code. On EPT misconfiguration, exit to ring 3 with KVM_EXIT_UNKNOWN. because EPT misconfiguration can't happen as MMIO is trigged by TDG.VP.VMCALL. No point to set a misconfiguration value for the fast path.

---
v14 -> v15:
- use PFERR_GUEST_ENC_MASK to tell the fault is private

---
 arch/x86/kvm/vmx/common.h |  3 +++
 arch/x86/kvm/vmx/tdx.c    | 49 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 52 insertions(+)
arch/x86/kvm/vmx/common.h +3 -0
index 632af7a76d0a..027aa4175d2c 100644
@@ -87,6 +87,9 @@ static inline int __vmx_handle_ept_violation(struct kvm_vcpu *vcpu, gpa_t gpa,
 	error_code |= (exit_qualification & EPT_VIOLATION_GVA_TRANSLATED) != 0 ?
 	       PFERR_GUEST_FINAL_MASK : PFERR_GUEST_PAGE_MASK;
 
+	if (kvm_is_private_gpa(vcpu->kvm, gpa))
+		error_code |= PFERR_GUEST_ENC_MASK;
+
 	return kvm_mmu_page_fault(vcpu, gpa, error_code, NULL, 0);
 }
 
arch/x86/kvm/vmx/tdx.c +49 -0

61 lines not on this page · raw message

Signature · 1 line
2.25.1
#570 lore raw

Re: [PATCH v19 101/130] KVM: TDX: handle ept violation/misconfig exit

Quoted 6 lines · On 2/26/2024 4:26 PM, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

On EPT violation, call a common function, __vmx_handle_ept_violation() to trigger x86 MMU code. On EPT misconfiguration, exit to ring 3 with KVM_EXIT_UNKNOWN. because EPT misconfiguration can't happen as MMIO is trigged by TDG.VP.VMCALL. No point to set a misconfiguration value for the

s/trigged/triggered

Quoted 9 lines

fast path.

---
v14 -> v15:
- use PFERR_GUEST_ENC_MASK to tell the fault is private

duplicated SOB

Quoted 86 lines
---
 arch/x86/kvm/vmx/common.h |  3 +++
 arch/x86/kvm/vmx/tdx.c    | 49 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 52 insertions(+)
arch/x86/kvm/vmx/common.h +3 -0
index 632af7a76d0a..027aa4175d2c 100644
@@ -87,6 +87,9 @@ static inline int __vmx_handle_ept_violation(struct kvm_vcpu *vcpu, gpa_t gpa,
 	error_code |= (exit_qualification & EPT_VIOLATION_GVA_TRANSLATED) != 0 ?
 	       PFERR_GUEST_FINAL_MASK : PFERR_GUEST_PAGE_MASK;
 
+	if (kvm_is_private_gpa(vcpu->kvm, gpa))
+		error_code |= PFERR_GUEST_ENC_MASK;
+
 	return kvm_mmu_page_fault(vcpu, gpa, error_code, NULL, 0);
 }
 
arch/x86/kvm/vmx/tdx.c +49 -0
index 2f68e6f2b53a..0db80fa020d2 100644
@@ -1285,6 +1285,51 @@ void tdx_deliver_interrupt(struct kvm_lapic *apic, int delivery_mode,
 	__vmx_deliver_posted_interrupt(vcpu, &tdx->pi_desc, vector);
 }
 
+static int tdx_handle_ept_violation(struct kvm_vcpu *vcpu)
+{
+	unsigned long exit_qual;
+
+	if (kvm_is_private_gpa(vcpu->kvm, tdexit_gpa(vcpu))) {
+		/*
+		 * Always treat SEPT violations as write faults.  Ignore the
+		 * EXIT_QUALIFICATION reported by TDX-SEAM for SEPT violations.
+		 * TD private pages are always RWX in the SEPT tables,
+		 * i.e. they're always mapped writable.  Just as importantly,
+		 * treating SEPT violations as write faults is necessary to
+		 * avoid COW allocations, which will cause TDAUGPAGE failures
+		 * due to aliasing a single HPA to multiple GPAs.
+		 */
+#define TDX_SEPT_VIOLATION_EXIT_QUAL	EPT_VIOLATION_ACC_WRITE
+		exit_qual = TDX_SEPT_VIOLATION_EXIT_QUAL;
+	} else {
+		exit_qual = tdexit_exit_qual(vcpu);
+		if (exit_qual & EPT_VIOLATION_ACC_INSTR) {
+			pr_warn("kvm: TDX instr fetch to shared GPA = 0x%lx @ RIP = 0x%lx\n",
+				tdexit_gpa(vcpu), kvm_rip_read(vcpu));
+			vcpu->run->exit_reason = KVM_EXIT_EXCEPTION;
+			vcpu->run->ex.exception = PF_VECTOR;
+			vcpu->run->ex.error_code = exit_qual;
+			return 0;
+		}
+	}
+
+	trace_kvm_page_fault(vcpu, tdexit_gpa(vcpu), exit_qual);
+	return __vmx_handle_ept_violation(vcpu, tdexit_gpa(vcpu), exit_qual);
+}
+
+static int tdx_handle_ept_misconfig(struct kvm_vcpu *vcpu)
+{
+	WARN_ON_ONCE(1);
+
+	vcpu->run->exit_reason = KVM_EXIT_INTERNAL_ERROR;
+	vcpu->run->internal.suberror = KVM_INTERNAL_ERROR_UNEXPECTED_EXIT_REASON;
+	vcpu->run->internal.ndata = 2;
+	vcpu->run->internal.data[0] = EXIT_REASON_EPT_MISCONFIG;
+	vcpu->run->internal.data[1] = vcpu->arch.last_vmentry_cpu;
+
+	return 0;
+}
+
 int tdx_handle_exit(struct kvm_vcpu *vcpu, fastpath_t fastpath)
 {
 	union tdx_exit_reason exit_reason = to_tdx(vcpu)->exit_reason;
@@ -1345,6 +1390,10 @@ int tdx_handle_exit(struct kvm_vcpu *vcpu, fastpath_t fastpath)
 	WARN_ON_ONCE(fastpath != EXIT_FASTPATH_NONE);
 
 	switch (exit_reason.basic) {
+	case EXIT_REASON_EPT_VIOLATION:
+		return tdx_handle_ept_violation(vcpu);
+	case EXIT_REASON_EPT_MISCONFIG:
+		return tdx_handle_ept_misconfig(vcpu);
 	case EXIT_REASON_OTHER_SMI:
 		/*
 		 * If reach here, it's not a Machine Check System Management
#571 lore raw

Re: [PATCH v19 101/130] KVM: TDX: handle ept violation/misconfig exit

In reply to [email protected]
Quoted 9 lines
arch/x86/kvm/vmx/tdx.c +9 -0
+static int tdx_handle_ept_violation(struct kvm_vcpu *vcpu)
+{
+	unsigned long exit_qual;
+
+	if (kvm_is_private_gpa(vcpu->kvm, tdexit_gpa(vcpu))) {
+		/*
+		 * Always treat SEPT violations as write faults.  Ignore the
+		 * EXIT_QUALIFICATION reported by TDX-SEAM for SEPT violations.
+		 * TD private pages are always RWX in the SEPT tables,

arch/x86/kvm/vmx/tdx.c void tdx_deliver_interrupt(struct kvm_lapic *apic, int delivery_mode, · line 1306

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +10 -0
+		 * i.e. they're always mapped writable.  Just as importantly,
+		 * treating SEPT violations as write faults is necessary to
+		 * avoid COW allocations, which will cause TDAUGPAGE failures
+		 * due to aliasing a single HPA to multiple GPAs.
+		 */
+#define TDX_SEPT_VIOLATION_EXIT_QUAL	EPT_VIOLATION_ACC_WRITE
+		exit_qual = TDX_SEPT_VIOLATION_EXIT_QUAL;
+	} else {
+		exit_qual = tdexit_exit_qual(vcpu);
+		if (exit_qual & EPT_VIOLATION_ACC_INSTR) {

Unless the CPU has a bug, instruction fetch in TD from shared memory causes a #PF. I think you can add a comment for this.

Maybe KVM_BUG_ON() is more appropriate as it signifies a potential bug.

Quoted 30 lines
arch/x86/kvm/vmx/tdx.c +26 -0
+			pr_warn("kvm: TDX instr fetch to shared GPA = 0x%lx @ RIP = 0x%lx\n",
+				tdexit_gpa(vcpu), kvm_rip_read(vcpu));
+			vcpu->run->exit_reason = KVM_EXIT_EXCEPTION;
+			vcpu->run->ex.exception = PF_VECTOR;
+			vcpu->run->ex.error_code = exit_qual;
+			return 0;
+		}
+	}
+
+	trace_kvm_page_fault(vcpu, tdexit_gpa(vcpu), exit_qual);
+	return __vmx_handle_ept_violation(vcpu, tdexit_gpa(vcpu), exit_qual);
+}
+
+static int tdx_handle_ept_misconfig(struct kvm_vcpu *vcpu)
+{
+	WARN_ON_ONCE(1);
+
+	vcpu->run->exit_reason = KVM_EXIT_INTERNAL_ERROR;
+	vcpu->run->internal.suberror = KVM_INTERNAL_ERROR_UNEXPECTED_EXIT_REASON;
+	vcpu->run->internal.ndata = 2;
+	vcpu->run->internal.data[0] = EXIT_REASON_EPT_MISCONFIG;
+	vcpu->run->internal.data[1] = vcpu->arch.last_vmentry_cpu;
+
+	return 0;
+}
+

int tdx_handle_exit(struct kvm_vcpu *vcpu, fastpath_t fastpath)

{
	union tdx_exit_reason exit_reason = to_tdx(vcpu)->exit_reason;

arch/x86/kvm/vmx/tdx.c int tdx_handle_exit(struct kvm_vcpu *vcpu, fastpath_t fastpath) · line 1396

Quoted 7 lines
arch/x86/kvm/vmx/tdx.c +4 -0
@@ -1345,6 +1390,10 @@ int tdx_handle_exit(struct kvm_vcpu *vcpu, fastpath_t fastpath)
 	WARN_ON_ONCE(fastpath != EXIT_FASTPATH_NONE);
 
 	switch (exit_reason.basic) {
+	case EXIT_REASON_EPT_VIOLATION:
+		return tdx_handle_ept_violation(vcpu);
+	case EXIT_REASON_EPT_MISCONFIG:
+		return tdx_handle_ept_misconfig(vcpu);

Handling EPT misconfiguration can be dropped because the "default" case handles all unexpected exits in the same way

Quoted 7 lines
arch/x86/kvm/vmx/tdx.c +0 -0
 	case EXIT_REASON_OTHER_SMI:
 		/*
 		 * If reach here, it's not a Machine Check System Management
Signature · 1 line
2.25.1
#572 lore raw

Re: [PATCH v19 101/130] KVM: TDX: handle ept violation/misconfig exit

Quoted 14 lines · Chao Gao
Quoted 14 lines
arch/x86/kvm/vmx/tdx.c +14 -0
+static int tdx_handle_ept_violation(struct kvm_vcpu *vcpu)
+{
+	unsigned long exit_qual;
+
+	if (kvm_is_private_gpa(vcpu->kvm, tdexit_gpa(vcpu))) {
+		/*
+		 * Always treat SEPT violations as write faults.  Ignore the
+		 * EXIT_QUALIFICATION reported by TDX-SEAM for SEPT violations.
+		 * TD private pages are always RWX in the SEPT tables,
+		 * i.e. they're always mapped writable.  Just as importantly,
+		 * treating SEPT violations as write faults is necessary to
+		 * avoid COW allocations, which will cause TDAUGPAGE failures
+		 * due to aliasing a single HPA to multiple GPAs.
+		 */
Quoted 8 lines

arch/x86/kvm/vmx/tdx.c void tdx_deliver_interrupt(struct kvm_lapic *apic, int delivery_mode, · line 1306

Quoted 5 lines
arch/x86/kvm/vmx/tdx.c +5 -0
+#define TDX_SEPT_VIOLATION_EXIT_QUAL	EPT_VIOLATION_ACC_WRITE
+		exit_qual = TDX_SEPT_VIOLATION_EXIT_QUAL;
+	} else {
+		exit_qual = tdexit_exit_qual(vcpu);
+		if (exit_qual & EPT_VIOLATION_ACC_INSTR) {

Unless the CPU has a bug, instruction fetch in TD from shared memory causes a #PF. I think you can add a comment for this.

Yes.

Quoted 1 line

Maybe KVM_BUG_ON() is more appropriate as it signifies a potential bug.

Bug of what component? CPU. If so, I think KVM_EXIT_INTERNAL_ERROR + KVM_INTERNAL_ERROR_UNEXPECTED_EXIT_REASON is more appropriate.

Quoted 32 lines
Quoted 32 lines
arch/x86/kvm/vmx/tdx.c +26 -0
+			pr_warn("kvm: TDX instr fetch to shared GPA = 0x%lx @ RIP = 0x%lx\n",
+				tdexit_gpa(vcpu), kvm_rip_read(vcpu));
+			vcpu->run->exit_reason = KVM_EXIT_EXCEPTION;
+			vcpu->run->ex.exception = PF_VECTOR;
+			vcpu->run->ex.error_code = exit_qual;
+			return 0;
+		}
+	}
+
+	trace_kvm_page_fault(vcpu, tdexit_gpa(vcpu), exit_qual);
+	return __vmx_handle_ept_violation(vcpu, tdexit_gpa(vcpu), exit_qual);
+}
+
+static int tdx_handle_ept_misconfig(struct kvm_vcpu *vcpu)
+{
+	WARN_ON_ONCE(1);
+
+	vcpu->run->exit_reason = KVM_EXIT_INTERNAL_ERROR;
+	vcpu->run->internal.suberror = KVM_INTERNAL_ERROR_UNEXPECTED_EXIT_REASON;
+	vcpu->run->internal.ndata = 2;
+	vcpu->run->internal.data[0] = EXIT_REASON_EPT_MISCONFIG;
+	vcpu->run->internal.data[1] = vcpu->arch.last_vmentry_cpu;
+
+	return 0;
+}
+

int tdx_handle_exit(struct kvm_vcpu *vcpu, fastpath_t fastpath)

{
	union tdx_exit_reason exit_reason = to_tdx(vcpu)->exit_reason;
arch/x86/kvm/vmx/tdx.c +0 -0
@@ -1345,6 +1390,10 @@ int tdx_handle_exit(struct kvm_vcpu *vcpu, fastpath_t fastpath)
 	WARN_ON_ONCE(fastpath != EXIT_FASTPATH_NONE);
 
Quoted 8 lines

arch/x86/kvm/vmx/tdx.c int tdx_handle_exit(struct kvm_vcpu *vcpu, fastpath_t fastpath) · line 1396

Quoted 5 lines
arch/x86/kvm/vmx/tdx.c +4 -0
 	switch (exit_reason.basic) {
+	case EXIT_REASON_EPT_VIOLATION:
+		return tdx_handle_ept_violation(vcpu);
+	case EXIT_REASON_EPT_MISCONFIG:
+		return tdx_handle_ept_misconfig(vcpu);

Handling EPT misconfiguration can be dropped because the "default" case handles all unexpected exits in the same way

Ah, right. Will update it.

Signature · 1 line
Isaku Yamahata <[email protected]>
#573 lore raw

Re: [PATCH v19 101/130] KVM: TDX: handle ept violation/misconfig exit

Hi Isaku,

Quoted 26 lines · Isaku Yamahata
Quoted 14 lines · Chao Gao
Quoted 14 lines
(quoted diff) +14 -0
+static int tdx_handle_ept_violation(struct kvm_vcpu *vcpu)
+{
+	unsigned long exit_qual;
+
+	if (kvm_is_private_gpa(vcpu->kvm, tdexit_gpa(vcpu))) {
+		/*
+		 * Always treat SEPT violations as write faults.  Ignore the
+		 * EXIT_QUALIFICATION reported by TDX-SEAM for SEPT violations.
+		 * TD private pages are always RWX in the SEPT tables,
+		 * i.e. they're always mapped writable.  Just as importantly,
+		 * treating SEPT violations as write faults is necessary to
+		 * avoid COW allocations, which will cause TDAUGPAGE failures
+		 * due to aliasing a single HPA to multiple GPAs.
+		 */
Quoted 8 lines

Review

Quoted 5 lines
(quoted diff) +5 -0
+#define TDX_SEPT_VIOLATION_EXIT_QUAL	EPT_VIOLATION_ACC_WRITE
+		exit_qual = TDX_SEPT_VIOLATION_EXIT_QUAL;
+	} else {
+		exit_qual = tdexit_exit_qual(vcpu);
+		if (exit_qual & EPT_VIOLATION_ACC_INSTR) {

Unless the CPU has a bug, instruction fetch in TD from shared memory causes a #PF. I think you can add a comment for this.

Quoted 8 lines

Yes.

Quoted 1 line

Maybe KVM_BUG_ON() is more appropriate as it signifies a potential bug.

Bug of what component? CPU. If so, I think KVM_EXIT_INTERNAL_ERROR + KVM_INTERNAL_ERROR_UNEXPECTED_EXIT_REASON is more appropriate.

Is below what you have in mind?

arch/x86/kvm/vmx/tdx.c +10 -3
index 499c6cd9633f..bd30b4c4d710 100644
@@ -1305,11 +1305,18 @@ static int tdx_handle_ept_violation(struct kvm_vcpu *vcpu)
 	} else {
 		exit_qual = tdexit_exit_qual(vcpu);
 		if (exit_qual & EPT_VIOLATION_ACC_INSTR) {
+			/*
+			 * Instruction fetch in TD from shared memory
+			 * causes a #PF.
+			 */
 			pr_warn("kvm: TDX instr fetch to shared GPA = 0x%lx @ RIP = 0x%lx\n",
 				tdexit_gpa(vcpu), kvm_rip_read(vcpu));
-			vcpu->run->exit_reason = KVM_EXIT_EXCEPTION;
-			vcpu->run->ex.exception = PF_VECTOR;
-			vcpu->run->ex.error_code = exit_qual;
+			vcpu->run->exit_reason = KVM_EXIT_INTERNAL_ERROR;
+			vcpu->run->internal.suberror =
+				KVM_INTERNAL_ERROR_UNEXPECTED_EXIT_REASON;
+			vcpu->run->internal.ndata = 2;
+			vcpu->run->internal.data[0] = EXIT_REASON_EPT_VIOLATION;
+			vcpu->run->internal.data[1] = vcpu->arch.last_vmentry_cpu;
 			return 0;
 		}
 	}

Thank you

Reinette

#574 lore raw

Re: [PATCH v19 101/130] KVM: TDX: handle ept violation/misconfig exit

Quoted 32 lines · Reinette Chatre

Hi Isaku,

Quoted 29 lines · Isaku Yamahata
Quoted 14 lines · Chao Gao
Quoted 14 lines
(quoted diff) +14 -0
+static int tdx_handle_ept_violation(struct kvm_vcpu *vcpu)
+{
+	unsigned long exit_qual;
+
+	if (kvm_is_private_gpa(vcpu->kvm, tdexit_gpa(vcpu))) {
+		/*
+		 * Always treat SEPT violations as write faults.  Ignore the
+		 * EXIT_QUALIFICATION reported by TDX-SEAM for SEPT violations.
+		 * TD private pages are always RWX in the SEPT tables,
+		 * i.e. they're always mapped writable.  Just as importantly,
+		 * treating SEPT violations as write faults is necessary to
+		 * avoid COW allocations, which will cause TDAUGPAGE failures
+		 * due to aliasing a single HPA to multiple GPAs.
+		 */
Quoted 8 lines

Review

Quoted 5 lines
(quoted diff) +5 -0
+#define TDX_SEPT_VIOLATION_EXIT_QUAL	EPT_VIOLATION_ACC_WRITE
+		exit_qual = TDX_SEPT_VIOLATION_EXIT_QUAL;
+	} else {
+		exit_qual = tdexit_exit_qual(vcpu);
+		if (exit_qual & EPT_VIOLATION_ACC_INSTR) {

Unless the CPU has a bug, instruction fetch in TD from shared memory causes a #PF. I think you can add a comment for this.

Yes.

Quoted 7 lines
Quoted 5 lines
Quoted 1 line

Maybe KVM_BUG_ON() is more appropriate as it signifies a potential bug.

Bug of what component? CPU. If so, I think KVM_EXIT_INTERNAL_ERROR + KVM_INTERNAL_ERROR_UNEXPECTED_EXIT_REASON is more appropriate.

Is below what you have in mind?

Yes. data[0] should be the raw value of exit reason if possible. data[2] should be exit_qual. Hmm, I don't find document on data[] for KVM_INTERNAL_ERROR_UNEXPECTED_EXIT_REASON.
Qemu doesn't assumt ndata = 2. Just report all data within ndata.

Quoted 33 lines
arch/x86/kvm/vmx/tdx.c +10 -3
index 499c6cd9633f..bd30b4c4d710 100644
@@ -1305,11 +1305,18 @@ static int tdx_handle_ept_violation(struct kvm_vcpu *vcpu)
 	} else {
 		exit_qual = tdexit_exit_qual(vcpu);
 		if (exit_qual & EPT_VIOLATION_ACC_INSTR) {
+			/*
+			 * Instruction fetch in TD from shared memory
+			 * causes a #PF.
+			 */
 			pr_warn("kvm: TDX instr fetch to shared GPA = 0x%lx @ RIP = 0x%lx\n",
 				tdexit_gpa(vcpu), kvm_rip_read(vcpu));
-			vcpu->run->exit_reason = KVM_EXIT_EXCEPTION;
-			vcpu->run->ex.exception = PF_VECTOR;
-			vcpu->run->ex.error_code = exit_qual;
+			vcpu->run->exit_reason = KVM_EXIT_INTERNAL_ERROR;
+			vcpu->run->internal.suberror =
+				KVM_INTERNAL_ERROR_UNEXPECTED_EXIT_REASON;
+			vcpu->run->internal.ndata = 2;
+			vcpu->run->internal.data[0] = EXIT_REASON_EPT_VIOLATION;
+			vcpu->run->internal.data[1] = vcpu->arch.last_vmentry_cpu;
 			return 0;
 		}
 	}

Thank you

Reinette

Signature · 1 line
Isaku Yamahata <[email protected]>
#575 lore raw

Re: [PATCH v19 101/130] KVM: TDX: handle ept violation/misconfig exit

Hi Isaku,

Quoted 34 lines · Isaku Yamahata
Quoted 32 lines · Reinette Chatre
Quoted 31 lines · Isaku Yamahata
Quoted 14 lines · Chao Gao
Quoted 14 lines
(quoted diff) +14 -0
+static int tdx_handle_ept_violation(struct kvm_vcpu *vcpu)
+{
+	unsigned long exit_qual;
+
+	if (kvm_is_private_gpa(vcpu->kvm, tdexit_gpa(vcpu))) {
+		/*
+		 * Always treat SEPT violations as write faults.  Ignore the
+		 * EXIT_QUALIFICATION reported by TDX-SEAM for SEPT violations.
+		 * TD private pages are always RWX in the SEPT tables,
+		 * i.e. they're always mapped writable.  Just as importantly,
+		 * treating SEPT violations as write faults is necessary to
+		 * avoid COW allocations, which will cause TDAUGPAGE failures
+		 * due to aliasing a single HPA to multiple GPAs.
+		 */
Quoted 8 lines

Review

Quoted 5 lines
(quoted diff) +5 -0
+#define TDX_SEPT_VIOLATION_EXIT_QUAL	EPT_VIOLATION_ACC_WRITE
+		exit_qual = TDX_SEPT_VIOLATION_EXIT_QUAL;
+	} else {
+		exit_qual = tdexit_exit_qual(vcpu);
+		if (exit_qual & EPT_VIOLATION_ACC_INSTR) {

Unless the CPU has a bug, instruction fetch in TD from shared memory causes a #PF. I think you can add a comment for this.

Yes.

Quoted 1 line

Maybe KVM_BUG_ON() is more appropriate as it signifies a potential bug.

Quoted 8 lines
Quoted 5 lines
Quoted 3 lines

Bug of what component? CPU. If so, I think KVM_EXIT_INTERNAL_ERROR + KVM_INTERNAL_ERROR_UNEXPECTED_EXIT_REASON is more appropriate.

Is below what you have in mind?

Yes. data[0] should be the raw value of exit reason if possible. data[2] should be exit_qual. Hmm, I don't find document on data[] for

Did you perhaps intend to write "data[1] should be exit_qual" or would you like to see ndata = 3? I followed existing usages, for example [1] and [2], that have ndata = 2 with "data[1] = vcpu->arch.last_vmentry_cpu".

Quoted 2 lines

KVM_INTERNAL_ERROR_UNEXPECTED_EXIT_REASON.
Qemu doesn't assumt ndata = 2. Just report all data within ndata.

I am not sure I interpreted your response correctly so I share one possible snippet below as I interpret it. Could you please check where I misinterpreted you? I could also make ndata = 3 to break the existing custom and add "data[2] = vcpu->arch.last_vmentry_cpu" to match existing pattern. What do you think?

arch/x86/kvm/vmx/tdx.c +12 -3
index 499c6cd9633f..ba81e6f68c97 100644
@@ -1305,11 +1305,20 @@ static int tdx_handle_ept_violation(struct kvm_vcpu *vcpu)
 	} else {
 		exit_qual = tdexit_exit_qual(vcpu);
 		if (exit_qual & EPT_VIOLATION_ACC_INSTR) {
+			union tdx_exit_reason exit_reason = to_tdx(vcpu)->exit_reason;
+
+			/*
+			 * Instruction fetch in TD from shared memory
+			 * causes a #PF.
+			 */
 			pr_warn("kvm: TDX instr fetch to shared GPA = 0x%lx @ RIP = 0x%lx\n",
 				tdexit_gpa(vcpu), kvm_rip_read(vcpu));
-			vcpu->run->exit_reason = KVM_EXIT_EXCEPTION;
-			vcpu->run->ex.exception = PF_VECTOR;
-			vcpu->run->ex.error_code = exit_qual;
+			vcpu->run->exit_reason = KVM_EXIT_INTERNAL_ERROR;
+			vcpu->run->internal.suberror =
+				KVM_INTERNAL_ERROR_UNEXPECTED_EXIT_REASON;
+			vcpu->run->internal.ndata = 2;
+			vcpu->run->internal.data[0] = exit_reason.full;
+			vcpu->run->internal.data[1] = exit_qual;
 			return 0;
 		}
 	}

Reinette

[1] https://github.com/kvm-x86/linux/blob/next/arch/x86/kvm/vmx/vmx.c#L6587
[2] https://github.com/kvm-x86/linux/blob/next/arch/x86/kvm/svm/svm.c#L3436

#576 lore raw

Re: [PATCH v19 101/130] KVM: TDX: handle ept violation/misconfig exit

Quoted 50 lines · Reinette Chatre

Hi Isaku,

Quoted 34 lines · Isaku Yamahata
Quoted 32 lines · Reinette Chatre
Quoted 31 lines · Isaku Yamahata
Quoted 14 lines · Chao Gao
Quoted 14 lines
(quoted diff) +14 -0
+static int tdx_handle_ept_violation(struct kvm_vcpu *vcpu)
+{
+	unsigned long exit_qual;
+
+	if (kvm_is_private_gpa(vcpu->kvm, tdexit_gpa(vcpu))) {
+		/*
+		 * Always treat SEPT violations as write faults.  Ignore the
+		 * EXIT_QUALIFICATION reported by TDX-SEAM for SEPT violations.
+		 * TD private pages are always RWX in the SEPT tables,
+		 * i.e. they're always mapped writable.  Just as importantly,
+		 * treating SEPT violations as write faults is necessary to
+		 * avoid COW allocations, which will cause TDAUGPAGE failures
+		 * due to aliasing a single HPA to multiple GPAs.
+		 */
Quoted 8 lines

Review

Quoted 5 lines
(quoted diff) +5 -0
+#define TDX_SEPT_VIOLATION_EXIT_QUAL	EPT_VIOLATION_ACC_WRITE
+		exit_qual = TDX_SEPT_VIOLATION_EXIT_QUAL;
+	} else {
+		exit_qual = tdexit_exit_qual(vcpu);
+		if (exit_qual & EPT_VIOLATION_ACC_INSTR) {

Unless the CPU has a bug, instruction fetch in TD from shared memory causes a #PF. I think you can add a comment for this.

Yes.

Quoted 1 line

Maybe KVM_BUG_ON() is more appropriate as it signifies a potential bug.

Quoted 8 lines
Quoted 5 lines
Quoted 3 lines

Bug of what component? CPU. If so, I think KVM_EXIT_INTERNAL_ERROR + KVM_INTERNAL_ERROR_UNEXPECTED_EXIT_REASON is more appropriate.

Is below what you have in mind?

Yes. data[0] should be the raw value of exit reason if possible. data[2] should be exit_qual. Hmm, I don't find document on data[] for

Did you perhaps intend to write "data[1] should be exit_qual" or would you like to see ndata = 3? I followed existing usages, for example [1] and [2], that have ndata = 2 with "data[1] = vcpu->arch.last_vmentry_cpu".

Quoted 9 lines
Quoted 2 lines

KVM_INTERNAL_ERROR_UNEXPECTED_EXIT_REASON.
Qemu doesn't assumt ndata = 2. Just report all data within ndata.

I am not sure I interpreted your response correctly so I share one possible snippet below as I interpret it. Could you please check where I misinterpreted you? I could also make ndata = 3 to break the existing custom and add "data[2] = vcpu->arch.last_vmentry_cpu" to match existing pattern. What do you think?

Sorry, I wasn't clear enough. I meant
ndata = 3;
data[0] = exit_reason.full;
data[1] = vcpu->arch.last_vmentry_cpu;
data[2] = exit_qual;

Because I hesitate to change the meaning of data[1] from other usage, I appended exit_qual as data[2].

Signature · 1 line
Isaku Yamahata <[email protected]>
#577 lore raw

Re: [PATCH v19 101/130] KVM: TDX: handle ept violation/misconfig exit

Hi Isaku,

Quoted 62 lines · Isaku Yamahata
Quoted 50 lines · Reinette Chatre

Hi Isaku,

Quoted 34 lines · Isaku Yamahata
Quoted 32 lines · Reinette Chatre
Quoted 31 lines · Isaku Yamahata
Quoted 14 lines · Chao Gao
Quoted 14 lines
(quoted diff) +14 -0
+static int tdx_handle_ept_violation(struct kvm_vcpu *vcpu)
+{
+	unsigned long exit_qual;
+
+	if (kvm_is_private_gpa(vcpu->kvm, tdexit_gpa(vcpu))) {
+		/*
+		 * Always treat SEPT violations as write faults.  Ignore the
+		 * EXIT_QUALIFICATION reported by TDX-SEAM for SEPT violations.
+		 * TD private pages are always RWX in the SEPT tables,
+		 * i.e. they're always mapped writable.  Just as importantly,
+		 * treating SEPT violations as write faults is necessary to
+		 * avoid COW allocations, which will cause TDAUGPAGE failures
+		 * due to aliasing a single HPA to multiple GPAs.
+		 */
Quoted 8 lines

Review

Quoted 5 lines
(quoted diff) +5 -0
+#define TDX_SEPT_VIOLATION_EXIT_QUAL	EPT_VIOLATION_ACC_WRITE
+		exit_qual = TDX_SEPT_VIOLATION_EXIT_QUAL;
+	} else {
+		exit_qual = tdexit_exit_qual(vcpu);
+		if (exit_qual & EPT_VIOLATION_ACC_INSTR) {

Unless the CPU has a bug, instruction fetch in TD from shared memory causes a #PF. I think you can add a comment for this.

Yes.

Quoted 1 line

Maybe KVM_BUG_ON() is more appropriate as it signifies a potential bug.

Quoted 8 lines
Quoted 5 lines
Quoted 3 lines

Bug of what component? CPU. If so, I think KVM_EXIT_INTERNAL_ERROR + KVM_INTERNAL_ERROR_UNEXPECTED_EXIT_REASON is more appropriate.

Is below what you have in mind?

Yes. data[0] should be the raw value of exit reason if possible. data[2] should be exit_qual. Hmm, I don't find document on data[] for

Did you perhaps intend to write "data[1] should be exit_qual" or would you like to see ndata = 3? I followed existing usages, for example [1] and [2], that have ndata = 2 with "data[1] = vcpu->arch.last_vmentry_cpu".

Quoted 9 lines
Quoted 2 lines

KVM_INTERNAL_ERROR_UNEXPECTED_EXIT_REASON.
Qemu doesn't assumt ndata = 2. Just report all data within ndata.

I am not sure I interpreted your response correctly so I share one possible snippet below as I interpret it. Could you please check where I misinterpreted you? I could also make ndata = 3 to break the existing custom and add "data[2] = vcpu->arch.last_vmentry_cpu" to match existing pattern. What do you think?

Quoted 8 lines

Sorry, I wasn't clear enough. I meant
ndata = 3;
data[0] = exit_reason.full;
data[1] = vcpu->arch.last_vmentry_cpu;
data[2] = exit_qual;

Because I hesitate to change the meaning of data[1] from other usage, I appended exit_qual as data[2].

I understand it now. Thank you very much Isaku.

Reinette

#578 lore raw

Re: [PATCH v19 101/130] KVM: TDX: handle ept violation/misconfig exit

In reply to Reinette Chatre
Quoted 5 lines · Reinette Chatre
arch/x86/kvm/vmx/tdx.c +0 -0
index 499c6cd9633f..ba81e6f68c97 100644

arch/x86/kvm/vmx/tdx.c static int tdx_handle_ept_violation(struct kvm_vcpu *vcpu) · line 1313

Quoted 9 lines
arch/x86/kvm/vmx/tdx.c +6 -0
@@ -1305,11 +1305,20 @@ static int tdx_handle_ept_violation(struct kvm_vcpu *vcpu)
 	} else {
 		exit_qual = tdexit_exit_qual(vcpu);
 		if (exit_qual & EPT_VIOLATION_ACC_INSTR) {
+			union tdx_exit_reason exit_reason = to_tdx(vcpu)->exit_reason;
+
+			/*
+			 * Instruction fetch in TD from shared memory
+			 * causes a #PF.
+			 */

It is better to hoist this comment above the if-statement.

/*
* Instruction fetch in TD from shared memory never causes EPT
* violation. Warn if such an EPT violation occurs as the CPU
* probably is buggy.
*/

 		if (exit_qual & EPT_VIOLATION_ACC_INSTR) {
		...
		}

but, I am wondering why KVM needs to perform this check. I prefer to drop this check if KVM doesn't rely on it to handle EPT violation correctly.

arch/x86/kvm/vmx/tdx.c static int tdx_handle_ept_violation(struct kvm_vcpu *vcpu) · line 1315

Quoted 2 lines
arch/x86/kvm/vmx/tdx.c +0 -0
 			pr_warn("kvm: TDX instr fetch to shared GPA = 0x%lx @ RIP = 0x%lx\n",
 				tdexit_gpa(vcpu), kvm_rip_read(vcpu));

how about using vcpu_unimpl()? it is a wrapper for printing such a message.

Quoted 17 lines
arch/x86/kvm/vmx/tdx.c +6 -3
-			vcpu->run->exit_reason = KVM_EXIT_EXCEPTION;
-			vcpu->run->ex.exception = PF_VECTOR;
-			vcpu->run->ex.error_code = exit_qual;
+			vcpu->run->exit_reason = KVM_EXIT_INTERNAL_ERROR;
+			vcpu->run->internal.suberror =
+				KVM_INTERNAL_ERROR_UNEXPECTED_EXIT_REASON;
+			vcpu->run->internal.ndata = 2;
+			vcpu->run->internal.data[0] = exit_reason.full;
+			vcpu->run->internal.data[1] = exit_qual;
 			return 0;
 		}
 	}

Reinette

[1] https://github.com/kvm-x86/linux/blob/next/arch/x86/kvm/vmx/vmx.c#L6587
[2] https://github.com/kvm-x86/linux/blob/next/arch/x86/kvm/svm/svm.c#L3436

#579 lore raw

Re: [PATCH v19 101/130] KVM: TDX: handle ept violation/misconfig exit

Quoted 28 lines · Chao Gao
Quoted 5 lines · Reinette Chatre
arch/x86/kvm/vmx/tdx.c +0 -0
index 499c6cd9633f..ba81e6f68c97 100644

arch/x86/kvm/vmx/tdx.c static int tdx_handle_ept_violation(struct kvm_vcpu *vcpu) · line 1313

Quoted 9 lines
arch/x86/kvm/vmx/tdx.c +6 -0
@@ -1305,11 +1305,20 @@ static int tdx_handle_ept_violation(struct kvm_vcpu *vcpu)
 	} else {
 		exit_qual = tdexit_exit_qual(vcpu);
 		if (exit_qual & EPT_VIOLATION_ACC_INSTR) {
+			union tdx_exit_reason exit_reason = to_tdx(vcpu)->exit_reason;
+
+			/*
+			 * Instruction fetch in TD from shared memory
+			 * causes a #PF.
+			 */

It is better to hoist this comment above the if-statement.

/*
* Instruction fetch in TD from shared memory never causes EPT
* violation. Warn if such an EPT violation occurs as the CPU
* probably is buggy.
*/

 		if (exit_qual & EPT_VIOLATION_ACC_INSTR) {
		...
		}
Quoted 7 lines

but, I am wondering why KVM needs to perform this check. I prefer to drop this check if KVM doesn't rely on it to handle EPT violation correctly.

arch/x86/kvm/vmx/tdx.c static int tdx_handle_ept_violation(struct kvm_vcpu *vcpu) · line 1315

Quoted 2 lines
arch/x86/kvm/vmx/tdx.c +0 -0
 			pr_warn("kvm: TDX instr fetch to shared GPA = 0x%lx @ RIP = 0x%lx\n",
 				tdexit_gpa(vcpu), kvm_rip_read(vcpu));

how about using vcpu_unimpl()? it is a wrapper for printing such a message.

This isn't an "unimplemented" scenario in KVM, this is a "hardware is broken" scenario. Just WARN_ON_ONCE() and move on. Or I suppose since EPT misconfig needs to do something as well:

		if (KVM_BUG_ON(exit_qual & EPT_VIOLATION_ACC_INSTR, vcpu->kvm))
			return -EIO;
#580 lore raw

Re: [PATCH v19 101/130] KVM: TDX: handle ept violation/misconfig exit

In reply to [email protected]
Quoted 5 lines · On Mon, Feb 26, 2024, [email protected] wrote:
arch/x86/kvm/vmx/tdx.c +5 -0
+static int tdx_handle_ept_violation(struct kvm_vcpu *vcpu)
+{
+	unsigned long exit_qual;
+
+	if (kvm_is_private_gpa(vcpu->kvm, tdexit_gpa(vcpu))) {

arch/x86/kvm/vmx/tdx.c void tdx_deliver_interrupt(struct kvm_lapic *apic, int delivery_mode, · line 1302

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +10 -0
+		/*
+		 * Always treat SEPT violations as write faults.  Ignore the
+		 * EXIT_QUALIFICATION reported by TDX-SEAM for SEPT violations.
+		 * TD private pages are always RWX in the SEPT tables,
+		 * i.e. they're always mapped writable.  Just as importantly,
+		 * treating SEPT violations as write faults is necessary to
+		 * avoid COW allocations, which will cause TDAUGPAGE failures
+		 * due to aliasing a single HPA to multiple GPAs.
+		 */
+#define TDX_SEPT_VIOLATION_EXIT_QUAL	EPT_VIOLATION_ACC_WRITE

This does not needd a #define. It's use in literally one place, one line below.

#581 lore raw

PATCH v19 102/130 KVM: TDX: handle EXCEPTION_NMI and EXTERNAL_INTERRUPT

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

Because guest TD state is protected, exceptions in guest TDs can't be intercepted. TDX VMM doesn't need to handle exceptions. tdx_handle_exit_irqoff() handles NMI and machine check. Ignore NMI and machine check and continue guest TD execution.

For external interrupt, increment stats same to the VMX case.

---
 arch/x86/kvm/vmx/tdx.c | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)
arch/x86/kvm/vmx/tdx.c +23 -0
index 0db80fa020d2..bdd74682b474 100644
@@ -918,6 +918,25 @@ void tdx_handle_exit_irqoff(struct kvm_vcpu *vcpu)
 		vmx_handle_exception_irqoff(vcpu, tdexit_intr_info(vcpu));
 }
 
+static int tdx_handle_exception(struct kvm_vcpu *vcpu)
+{
+	u32 intr_info = tdexit_intr_info(vcpu);
+
+	if (is_nmi(intr_info) || is_machine_check(intr_info))
+		return 1;
+
+	kvm_pr_unimpl("unexpected exception 0x%x(exit_reason 0x%llx qual 0x%lx)\n",
+		intr_info,
+		to_tdx(vcpu)->exit_reason.full, tdexit_exit_qual(vcpu));
+	return -EFAULT;
+}
+
+static int tdx_handle_external_interrupt(struct kvm_vcpu *vcpu)
+{
+	++vcpu->stat.irq_exits;
+	return 1;
+}
+
 static int tdx_handle_triple_fault(struct kvm_vcpu *vcpu)
 {
 	vcpu->run->exit_reason = KVM_EXIT_SHUTDOWN;
@@ -1390,6 +1409,10 @@ int tdx_handle_exit(struct kvm_vcpu *vcpu, fastpath_t fastpath)
 	WARN_ON_ONCE(fastpath != EXIT_FASTPATH_NONE);
 
 	switch (exit_reason.basic) {
+	case EXIT_REASON_EXCEPTION_NMI:
+		return tdx_handle_exception(vcpu);
+	case EXIT_REASON_EXTERNAL_INTERRUPT:
+		return tdx_handle_external_interrupt(vcpu);
 	case EXIT_REASON_EPT_VIOLATION:
 		return tdx_handle_ept_violation(vcpu);
 	case EXIT_REASON_EPT_MISCONFIG:
Signature · 1 line
2.25.1
#582 lore raw

Re: [PATCH v19 102/130] KVM: TDX: handle EXCEPTION_NMI and EXTERNAL_INTERRUPT

Quoted 5 lines · On Mon, Feb 26, 2024 at 12:26:44AM -0800, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

Because guest TD state is protected, exceptions in guest TDs can't be intercepted. TDX VMM doesn't need to handle exceptions. tdx_handle_exit_irqoff() handles NMI and machine check. Ignore NMI and

tdx_handle_exit_irqoff() doesn't handle NMIs.

Quoted 15 lines

machine check and continue guest TD execution.

For external interrupt, increment stats same to the VMX case.

---
arch/x86/kvm/vmx/tdx.c | 23 +++++++++++++++++++++++

1 file changed, 23 insertions(+)
arch/x86/kvm/vmx/tdx.c +0 -0
index 0db80fa020d2..bdd74682b474 100644

arch/x86/kvm/vmx/tdx.c void tdx_handle_exit_irqoff(struct kvm_vcpu *vcpu) · line 926

Quoted 9 lines
arch/x86/kvm/vmx/tdx.c +6 -0
@@ -918,6 +918,25 @@ void tdx_handle_exit_irqoff(struct kvm_vcpu *vcpu)
 		vmx_handle_exception_irqoff(vcpu, tdexit_intr_info(vcpu));
 }
 
+static int tdx_handle_exception(struct kvm_vcpu *vcpu)
+{
+	u32 intr_info = tdexit_intr_info(vcpu);
+
+	if (is_nmi(intr_info) || is_machine_check(intr_info))
+		return 1;

Add a comment in code as well.

arch/x86/kvm/vmx/tdx.c void tdx_handle_exit_irqoff(struct kvm_vcpu *vcpu) · line 931

Quoted 5 lines
arch/x86/kvm/vmx/tdx.c +5 -0
+
+	kvm_pr_unimpl("unexpected exception 0x%x(exit_reason 0x%llx qual 0x%lx)\n",
+		intr_info,
+		to_tdx(vcpu)->exit_reason.full, tdexit_exit_qual(vcpu));
+	return -EFAULT;

-EFAULT looks incorrect.

Quoted 26 lines
arch/x86/kvm/vmx/tdx.c +12 -0
+}
+
+static int tdx_handle_external_interrupt(struct kvm_vcpu *vcpu)
+{
+	++vcpu->stat.irq_exits;
+	return 1;
+}
+
 static int tdx_handle_triple_fault(struct kvm_vcpu *vcpu)
 {
 	vcpu->run->exit_reason = KVM_EXIT_SHUTDOWN;
@@ -1390,6 +1409,10 @@ int tdx_handle_exit(struct kvm_vcpu *vcpu, fastpath_t fastpath)
 	WARN_ON_ONCE(fastpath != EXIT_FASTPATH_NONE);
 
 	switch (exit_reason.basic) {
+	case EXIT_REASON_EXCEPTION_NMI:
+		return tdx_handle_exception(vcpu);
+	case EXIT_REASON_EXTERNAL_INTERRUPT:
+		return tdx_handle_external_interrupt(vcpu);
 	case EXIT_REASON_EPT_VIOLATION:
 		return tdx_handle_ept_violation(vcpu);
 	case EXIT_REASON_EPT_MISCONFIG:
Signature · 1 line
2.25.1
#583 lore raw

Re: [PATCH v19 102/130] KVM: TDX: handle EXCEPTION_NMI and EXTERNAL_INTERRUPT

Quoted 8 lines · Chao Gao
Quoted 5 lines · On Mon, Feb 26, 2024 at 12:26:44AM -0800, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

Because guest TD state is protected, exceptions in guest TDs can't be intercepted. TDX VMM doesn't need to handle exceptions. tdx_handle_exit_irqoff() handles NMI and machine check. Ignore NMI and

tdx_handle_exit_irqoff() doesn't handle NMIs.

Will it to tdx_handle_exception().

Quoted 18 lines
Quoted 18 lines

machine check and continue guest TD execution.

For external interrupt, increment stats same to the VMX case.

---
arch/x86/kvm/vmx/tdx.c | 23 +++++++++++++++++++++++

1 file changed, 23 insertions(+)
arch/x86/kvm/vmx/tdx.c +0 -0
index 0db80fa020d2..bdd74682b474 100644
@@ -918,6 +918,25 @@ void tdx_handle_exit_irqoff(struct kvm_vcpu *vcpu)
 		vmx_handle_exception_irqoff(vcpu, tdexit_intr_info(vcpu));
 }
 
Quoted 8 lines

arch/x86/kvm/vmx/tdx.c void tdx_handle_exit_irqoff(struct kvm_vcpu *vcpu) · line 926

Quoted 6 lines
arch/x86/kvm/vmx/tdx.c +6 -0
+static int tdx_handle_exception(struct kvm_vcpu *vcpu)
+{
+	u32 intr_info = tdexit_intr_info(vcpu);
+
+	if (is_nmi(intr_info) || is_machine_check(intr_info))
+		return 1;

Add a comment in code as well.

Sure.

Quoted 7 lines

arch/x86/kvm/vmx/tdx.c void tdx_handle_exit_irqoff(struct kvm_vcpu *vcpu) · line 931

Quoted 5 lines
arch/x86/kvm/vmx/tdx.c +5 -0
+
+	kvm_pr_unimpl("unexpected exception 0x%x(exit_reason 0x%llx qual 0x%lx)\n",
+		intr_info,
+		to_tdx(vcpu)->exit_reason.full, tdexit_exit_qual(vcpu));
+	return -EFAULT;

-EFAULT looks incorrect.

As this is unexpected exception, we should exit to to the user-space with KVM_EXIT_EXCEPTION. Then QEMU will abort with message.

Signature · 1 line
Isaku Yamahata <[email protected]>
#584 lore raw

Re: [PATCH v19 102/130] KVM: TDX: handle EXCEPTION_NMI and EXTERNAL_INTERRUPT

Quoted 11 lines · Isaku Yamahata
Quoted 7 lines · Chao Gao
Quoted 5 lines · On Mon, Feb 26, 2024 at 12:26:44AM -0800, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

Because guest TD state is protected, exceptions in guest TDs can't be intercepted. TDX VMM doesn't need to handle exceptions. tdx_handle_exit_irqoff() handles NMI and machine check. Ignore NMI and

tdx_handle_exit_irqoff() doesn't handle NMIs.

Will it to tdx_handle_exception().

I don't get  why tdx_handle_exception()?

NMI is handled in tdx_vcpu_enter_exit() prior to leaving the safety of noinstr, according to patch 098.
https://lore.kernel.org/kvm/88920c598dcb55c15219642f27d0781af6d0c044.1708933498.git.isaku.yamahata@intel.com/

@@ -837,6 +857,12 @@ static noinstr void tdx_vcpu_enter_exit(struct vcpu_tdx *tdx) WARN_ON_ONCE(!kvm_rebooting &&
              (tdx->exit_reason.full & TDX_SW_ERROR) == TDX_SW_ERROR);

+    if ((u16)tdx->exit_reason.basic == EXIT_REASON_EXCEPTION_NMI && +        is_nmi(tdexit_intr_info(vcpu))) {
+        kvm_before_interrupt(vcpu, KVM_HANDLING_NMI);
+        vmx_do_nmi_irqoff();
+        kvm_after_interrupt(vcpu);
+    }
     guest_state_exit_irqoff();
 }

Quoted 20 lines
Quoted 20 lines
Quoted 20 lines

machine check and continue guest TD execution.

For external interrupt, increment stats same to the VMX case.

---
arch/x86/kvm/vmx/tdx.c | 23 +++++++++++++++++++++++

1 file changed, 23 insertions(+)
arch/x86/kvm/vmx/tdx.c +0 -0
index 0db80fa020d2..bdd74682b474 100644
@@ -918,6 +918,25 @@ void tdx_handle_exit_irqoff(struct kvm_vcpu *vcpu)
 		vmx_handle_exception_irqoff(vcpu, tdexit_intr_info(vcpu));
 }
 
Quoted 1 line
Quoted 1 line

arch/x86/kvm/vmx/tdx.c void tdx_handle_exit_irqoff(struct kvm_vcpu *vcpu) · line 921

Quoted 1 line
arch/x86/kvm/vmx/tdx.c +1 -0
+static int tdx_handle_exception(struct kvm_vcpu *vcpu)

Should this function be named as tdx_handle_exception_nmi() since it's checking nmi as well?

Quoted 6 lines
Quoted 6 lines
Quoted 5 lines
arch/x86/kvm/vmx/tdx.c +5 -0
+{
+	u32 intr_info = tdexit_intr_info(vcpu);
+
+	if (is_nmi(intr_info) || is_machine_check(intr_info))
+		return 1;
#585 lore raw

Re: [PATCH v19 102/130] KVM: TDX: handle EXCEPTION_NMI and EXTERNAL_INTERRUPT

Quoted 26 lines · Binbin Wu
Quoted 11 lines · Isaku Yamahata
Quoted 7 lines · Chao Gao
Quoted 5 lines · On Mon, Feb 26, 2024 at 12:26:44AM -0800, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

Because guest TD state is protected, exceptions in guest TDs can't be intercepted. TDX VMM doesn't need to handle exceptions. tdx_handle_exit_irqoff() handles NMI and machine check. Ignore NMI and

tdx_handle_exit_irqoff() doesn't handle NMIs.

Will it to tdx_handle_exception().

I don't get  why tdx_handle_exception()?

NMI is handled in tdx_vcpu_enter_exit() prior to leaving the safety of noinstr, according to patch 098.
https://lore.kernel.org/kvm/88920c598dcb55c15219642f27d0781af6d0c044.1708933498.git.isaku.yamahata@intel.com/

(quoted diff) +0 -0
@@ -837,6 +857,12 @@ static noinstr void tdx_vcpu_enter_exit(struct vcpu_tdx
 *tdx)

     WARN_ON_ONCE(!kvm_rebooting &&
              (tdx->exit_reason.full & TDX_SW_ERROR) == TDX_SW_ERROR);

Quoted 8 lines

+    if ((u16)tdx->exit_reason.basic == EXIT_REASON_EXCEPTION_NMI && +        is_nmi(tdexit_intr_info(vcpu))) {
+        kvm_before_interrupt(vcpu, KVM_HANDLING_NMI);
+        vmx_do_nmi_irqoff();
+        kvm_after_interrupt(vcpu);
+    }
     guest_state_exit_irqoff();
 }

You're correct. tdx_vcpu_enter_exit() handles EXIT_REASON_EXCEPTION_NMI for NMI, and tdx_handle_exeption() ignores NMI case.

The commit message should be updated with tdx_vcpu_enter_exit().

Quoted 18 lines
Quoted 18 lines
Quoted 18 lines
Quoted 18 lines

machine check and continue guest TD execution.

For external interrupt, increment stats same to the VMX case.

---
arch/x86/kvm/vmx/tdx.c | 23 +++++++++++++++++++++++

1 file changed, 23 insertions(+)
arch/x86/kvm/vmx/tdx.c +0 -0
index 0db80fa020d2..bdd74682b474 100644
@@ -918,6 +918,25 @@ void tdx_handle_exit_irqoff(struct kvm_vcpu *vcpu)
 		vmx_handle_exception_irqoff(vcpu, tdexit_intr_info(vcpu));
 }
 
Quoted 4 lines
Quoted 1 line
Quoted 1 line

arch/x86/kvm/vmx/tdx.c void tdx_handle_exit_irqoff(struct kvm_vcpu *vcpu) · line 921

Quoted 1 line
arch/x86/kvm/vmx/tdx.c +1 -0
+static int tdx_handle_exception(struct kvm_vcpu *vcpu)

Should this function be named as tdx_handle_exception_nmi() since it's checking nmi as well?

Ok, tdx_handle_exception_nmi() is more consistent.

Signature · 1 line
Isaku Yamahata <[email protected]>
#586 lore raw

PATCH v19 103/130 KVM: TDX: Handle EXIT_REASON_OTHER_SMI with MSMI

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

When BIOS eMCA MCE-SMI morphing is enabled, the #MC is morphed to MSMI (Machine Check System Management Interrupt). Then the SMI causes TD exit with the read reason of EXIT_REASON_OTHER_SMI with MSMI bit set in the exit qualification to KVM instead of EXIT_REASON_EXCEPTION_NMI with MC exception.

Handle EXIT_REASON_OTHER_SMI with MSMI bit set in the exit qualification as MCE(Machine Check Exception) happened during TD guest running.

---
 arch/x86/kvm/vmx/tdx.c      | 40 ++++++++++++++++++++++++++++++++++---
 arch/x86/kvm/vmx/tdx_arch.h |  2 ++
 2 files changed, 39 insertions(+), 3 deletions(-)
arch/x86/kvm/vmx/tdx.c +37 -3
index bdd74682b474..117c2315f087 100644
@@ -916,6 +916,30 @@ void tdx_handle_exit_irqoff(struct kvm_vcpu *vcpu)
 						     tdexit_intr_info(vcpu));
 	else if (exit_reason == EXIT_REASON_EXCEPTION_NMI)
 		vmx_handle_exception_irqoff(vcpu, tdexit_intr_info(vcpu));
+	else if (unlikely(tdx->exit_reason.non_recoverable ||
+		 tdx->exit_reason.error)) {
+		/*
+		 * The only reason it gets EXIT_REASON_OTHER_SMI is there is an
+		 * #MSMI(Machine Check System Management Interrupt) with
+		 * exit_qualification bit 0 set in TD guest.
+		 * The #MSMI is delivered right after SEAMCALL returns,
+		 * and an #MC is delivered to host kernel after SMI handler
+		 * returns.
+		 *
+		 * The #MC right after SEAMCALL is fixed up and skipped in #MC
+		 * handler because it's an #MC happens in TD guest we cannot
+		 * handle it with host's context.
+		 *
+		 * Call KVM's machine check handler explicitly here.
+		 */
+		if (tdx->exit_reason.basic == EXIT_REASON_OTHER_SMI) {
+			unsigned long exit_qual;
+
+			exit_qual = tdexit_exit_qual(vcpu);
+			if (exit_qual & TD_EXIT_OTHER_SMI_IS_MSMI)
+				kvm_machine_check();
+		}
+	}
 }
 
 static int tdx_handle_exception(struct kvm_vcpu *vcpu)
@@ -1381,6 +1405,11 @@ int tdx_handle_exit(struct kvm_vcpu *vcpu, fastpath_t fastpath)
 			      exit_reason.full, exit_reason.basic,
 			      to_kvm_tdx(vcpu->kvm)->hkid,
 			      set_hkid_to_hpa(0, to_kvm_tdx(vcpu->kvm)->hkid));
+
+		/*
+		 * tdx_handle_exit_irqoff() handled EXIT_REASON_OTHER_SMI.  It
+		 * must be handled before enabling preemption because it's #MC.
+		 */
 		goto unhandled_exit;
 	}
 
@@ -1419,9 +1448,14 @@ int tdx_handle_exit(struct kvm_vcpu *vcpu, fastpath_t fastpath)
 		return tdx_handle_ept_misconfig(vcpu);
 	case EXIT_REASON_OTHER_SMI:
 		/*
-		 * If reach here, it's not a Machine Check System Management
-		 * Interrupt(MSMI).  #SMI is delivered and handled right after
-		 * SEAMRET, nothing needs to be done in KVM.
+		 * Unlike VMX, all the SMI in SEAM non-root mode (i.e. when
+		 * TD guest vcpu is running) will cause TD exit to TDX module,
+		 * then SEAMRET to KVM. Once it exits to KVM, SMI is delivered
+		 * and handled right away.
+		 *
+		 * - If it's an Machine Check System Management Interrupt
+		 *   (MSMI), it's handled above due to non_recoverable bit set.
+		 * - If it's not an MSMI, don't need to do anything here.
 		 */
 		return 1;
 	default:
arch/x86/kvm/vmx/tdx_arch.h +2 -0
index efc3c61c14ab..87ef22e9cd49 100644
@@ -42,6 +42,8 @@
 #define TDH_VP_WR			43
 #define TDH_SYS_LP_SHUTDOWN		44
 
+#define TD_EXIT_OTHER_SMI_IS_MSMI	BIT(1)
+
 /* TDX control structure (TDR/TDCS/TDVPS) field access codes */
 #define TDX_NON_ARCH			BIT_ULL(63)
 #define TDX_CLASS_SHIFT			56
Signature · 1 line
2.25.1
#587 lore raw

Re: [PATCH v19 103/130] KVM: TDX: Handle EXIT_REASON_OTHER_SMI with MSMI

Quoted 22 lines · On Mon, Feb 26, 2024 at 12:26:45AM -0800, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

When BIOS eMCA MCE-SMI morphing is enabled, the #MC is morphed to MSMI (Machine Check System Management Interrupt). Then the SMI causes TD exit with the read reason of EXIT_REASON_OTHER_SMI with MSMI bit set in the exit qualification to KVM instead of EXIT_REASON_EXCEPTION_NMI with MC exception.

Handle EXIT_REASON_OTHER_SMI with MSMI bit set in the exit qualification as MCE(Machine Check Exception) happened during TD guest running.

---
arch/x86/kvm/vmx/tdx.c | 40 ++++++++++++++++++++++++++++++++++---
arch/x86/kvm/vmx/tdx_arch.h | 2 ++

2 files changed, 39 insertions(+), 3 deletions(-)
arch/x86/kvm/vmx/tdx.c +0 -0
index bdd74682b474..117c2315f087 100644

arch/x86/kvm/vmx/tdx.c void tdx_handle_exit_irqoff(struct kvm_vcpu *vcpu) · line 920

Quoted 5 lines
arch/x86/kvm/vmx/tdx.c +2 -0
@@ -916,6 +916,30 @@ void tdx_handle_exit_irqoff(struct kvm_vcpu *vcpu)
 						     tdexit_intr_info(vcpu));
 	else if (exit_reason == EXIT_REASON_EXCEPTION_NMI)
 		vmx_handle_exception_irqoff(vcpu, tdexit_intr_info(vcpu));
+	else if (unlikely(tdx->exit_reason.non_recoverable ||
+		 tdx->exit_reason.error)) {

why not just:

	else if (tdx->exit_reason.basic == EXIT_REASON_OTHER_SMI) {

i.e., does EXIT_REASON_OTHER_SMI imply exit_reason.non_recoverable or exit_reason.error?

arch/x86/kvm/vmx/tdx.c void tdx_handle_exit_irqoff(struct kvm_vcpu *vcpu) · line 929

Quoted 9 lines
arch/x86/kvm/vmx/tdx.c +9 -0
+		/*
+		 * The only reason it gets EXIT_REASON_OTHER_SMI is there is an
+		 * #MSMI(Machine Check System Management Interrupt) with
+		 * exit_qualification bit 0 set in TD guest.
+		 * The #MSMI is delivered right after SEAMCALL returns,
+		 * and an #MC is delivered to host kernel after SMI handler
+		 * returns.
+		 *
+		 * The #MC right after SEAMCALL is fixed up and skipped in #MC

Looks fixing up and skipping #MC on the first instruction after TD-exit is missing in v19?

Quoted 18 lines
arch/x86/kvm/vmx/tdx.c +13 -0
+		 * handler because it's an #MC happens in TD guest we cannot
+		 * handle it with host's context.
+		 *
+		 * Call KVM's machine check handler explicitly here.
+		 */
+		if (tdx->exit_reason.basic == EXIT_REASON_OTHER_SMI) {
+			unsigned long exit_qual;
+
+			exit_qual = tdexit_exit_qual(vcpu);
+			if (exit_qual & TD_EXIT_OTHER_SMI_IS_MSMI)
 
+				kvm_machine_check();
+		}
+	}
 }
 

static int tdx_handle_exception(struct kvm_vcpu *vcpu)

arch/x86/kvm/vmx/tdx.c int tdx_handle_exit(struct kvm_vcpu *vcpu, fastpath_t fastpath) · line 1412

Quoted 8 lines
arch/x86/kvm/vmx/tdx.c +5 -0
@@ -1381,6 +1405,11 @@ int tdx_handle_exit(struct kvm_vcpu *vcpu, fastpath_t fastpath)
 			      exit_reason.full, exit_reason.basic,
 			      to_kvm_tdx(vcpu->kvm)->hkid,
 			      set_hkid_to_hpa(0, to_kvm_tdx(vcpu->kvm)->hkid));
+
+		/*
+		 * tdx_handle_exit_irqoff() handled EXIT_REASON_OTHER_SMI.  It
+		 * must be handled before enabling preemption because it's #MC.
+		 */

Then EXIT_REASON_OTHER_SMI is handled, why still go to unhandled_exit?

Quoted 8 lines
arch/x86/kvm/vmx/tdx.c +0 -1
 		goto unhandled_exit;
 	}
 
@@ -1419,9 +1448,14 @@ int tdx_handle_exit(struct kvm_vcpu *vcpu, fastpath_t fastpath)
 		return tdx_handle_ept_misconfig(vcpu);
 	case EXIT_REASON_OTHER_SMI:
 		/*
-		 * If reach here, it's not a Machine Check System Management

arch/x86/kvm/vmx/tdx.c int tdx_handle_exit(struct kvm_vcpu *vcpu, fastpath_t fastpath) · line 1458

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +8 -2
-		 * Interrupt(MSMI).  #SMI is delivered and handled right after
-		 * SEAMRET, nothing needs to be done in KVM.
+		 * Unlike VMX, all the SMI in SEAM non-root mode (i.e. when
+		 * TD guest vcpu is running) will cause TD exit to TDX module,
+		 * then SEAMRET to KVM. Once it exits to KVM, SMI is delivered
+		 * and handled right away.
+		 *
+		 * - If it's an Machine Check System Management Interrupt
+		 *   (MSMI), it's handled above due to non_recoverable bit set.
+		 * - If it's not an MSMI, don't need to do anything here.

This corrects a comment added in patch 100. Maybe we can just merge patch 100 into this one?

Quoted 20 lines
arch/x86/kvm/vmx/tdx.c +0 -0
 		 */
 		return 1;
 	default:
arch/x86/kvm/vmx/tdx_arch.h +2 -0
index efc3c61c14ab..87ef22e9cd49 100644
@@ -42,6 +42,8 @@
 #define TDH_VP_WR			43
 #define TDH_SYS_LP_SHUTDOWN		44
 
+#define TD_EXIT_OTHER_SMI_IS_MSMI	BIT(1)
+
 /* TDX control structure (TDR/TDCS/TDVPS) field access codes */
 #define TDX_NON_ARCH			BIT_ULL(63)
 #define TDX_CLASS_SHIFT			56
Signature · 1 line
2.25.1
#588 lore raw

Re: [PATCH v19 103/130] KVM: TDX: Handle EXIT_REASON_OTHER_SMI with MSMI

Quoted 30 lines · Chao Gao
Quoted 22 lines · On Mon, Feb 26, 2024 at 12:26:45AM -0800, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

When BIOS eMCA MCE-SMI morphing is enabled, the #MC is morphed to MSMI (Machine Check System Management Interrupt). Then the SMI causes TD exit with the read reason of EXIT_REASON_OTHER_SMI with MSMI bit set in the exit qualification to KVM instead of EXIT_REASON_EXCEPTION_NMI with MC exception.

Handle EXIT_REASON_OTHER_SMI with MSMI bit set in the exit qualification as MCE(Machine Check Exception) happened during TD guest running.

---
arch/x86/kvm/vmx/tdx.c | 40 ++++++++++++++++++++++++++++++++++---
arch/x86/kvm/vmx/tdx_arch.h | 2 ++

2 files changed, 39 insertions(+), 3 deletions(-)
arch/x86/kvm/vmx/tdx.c +0 -0
index bdd74682b474..117c2315f087 100644

arch/x86/kvm/vmx/tdx.c void tdx_handle_exit_irqoff(struct kvm_vcpu *vcpu) · line 920

Quoted 5 lines
arch/x86/kvm/vmx/tdx.c +2 -0
@@ -916,6 +916,30 @@ void tdx_handle_exit_irqoff(struct kvm_vcpu *vcpu)
 						     tdexit_intr_info(vcpu));
 	else if (exit_reason == EXIT_REASON_EXCEPTION_NMI)
 		vmx_handle_exception_irqoff(vcpu, tdexit_intr_info(vcpu));
+	else if (unlikely(tdx->exit_reason.non_recoverable ||
+		 tdx->exit_reason.error)) {

why not just:

Quoted 5 lines
	else if (tdx->exit_reason.basic == EXIT_REASON_OTHER_SMI) {

i.e., does EXIT_REASON_OTHER_SMI imply exit_reason.non_recoverable or exit_reason.error?

Yes, this should be refined.

Quoted 4 lines
Quoted 4 lines
arch/x86/kvm/vmx/tdx.c +4 -0
+		/*
+		 * The only reason it gets EXIT_REASON_OTHER_SMI is there is an
+		 * #MSMI(Machine Check System Management Interrupt) with
+		 * exit_qualification bit 0 set in TD guest.
Quoted 8 lines

arch/x86/kvm/vmx/tdx.c void tdx_handle_exit_irqoff(struct kvm_vcpu *vcpu) · line 929

Quoted 5 lines
arch/x86/kvm/vmx/tdx.c +5 -0
+		 * The #MSMI is delivered right after SEAMCALL returns,
+		 * and an #MC is delivered to host kernel after SMI handler
+		 * returns.
+		 *
+		 * The #MC right after SEAMCALL is fixed up and skipped in #MC

Looks fixing up and skipping #MC on the first instruction after TD-exit is missing in v19?

Right. We removed it as MSMI will provides if #MC happened in SEAM or not.

Quoted 17 lines
Quoted 17 lines
arch/x86/kvm/vmx/tdx.c +13 -0
+		 * handler because it's an #MC happens in TD guest we cannot
+		 * handle it with host's context.
+		 *
+		 * Call KVM's machine check handler explicitly here.
+		 */
+		if (tdx->exit_reason.basic == EXIT_REASON_OTHER_SMI) {
+			unsigned long exit_qual;
+
+			exit_qual = tdexit_exit_qual(vcpu);
+			if (exit_qual & TD_EXIT_OTHER_SMI_IS_MSMI)
 
+				kvm_machine_check();
+		}
+	}
 }
 
Quoted 12 lines

arch/x86/kvm/vmx/tdx.c int tdx_handle_exit(struct kvm_vcpu *vcpu, fastpath_t fastpath) · line 1412

Quoted 10 lines

static int tdx_handle_exception(struct kvm_vcpu *vcpu)

arch/x86/kvm/vmx/tdx.c +5 -0
@@ -1381,6 +1405,11 @@ int tdx_handle_exit(struct kvm_vcpu *vcpu, fastpath_t fastpath)
 			      exit_reason.full, exit_reason.basic,
 			      to_kvm_tdx(vcpu->kvm)->hkid,
 			      set_hkid_to_hpa(0, to_kvm_tdx(vcpu->kvm)->hkid));
+
+		/*
+		 * tdx_handle_exit_irqoff() handled EXIT_REASON_OTHER_SMI.  It
+		 * must be handled before enabling preemption because it's #MC.
+		 */

Then EXIT_REASON_OTHER_SMI is handled, why still go to unhandled_exit?

Let me update the comment.
exit_irqoff() doesn't return value to tell vcpu_run loop to continue or exit to user-space. As the guest is dead, we'd like to exit to the user-space.

Quoted 13 lines
Quoted 13 lines
arch/x86/kvm/vmx/tdx.c +3 -3
 		goto unhandled_exit;
 	}
 
@@ -1419,9 +1448,14 @@ int tdx_handle_exit(struct kvm_vcpu *vcpu, fastpath_t fastpath)
 		return tdx_handle_ept_misconfig(vcpu);
 	case EXIT_REASON_OTHER_SMI:
 		/*
-		 * If reach here, it's not a Machine Check System Management
-		 * Interrupt(MSMI).  #SMI is delivered and handled right after
-		 * SEAMRET, nothing needs to be done in KVM.
+		 * Unlike VMX, all the SMI in SEAM non-root mode (i.e. when
+		 * TD guest vcpu is running) will cause TD exit to TDX module,
+		 * then SEAMRET to KVM. Once it exits to KVM, SMI is delivered
Quoted 8 lines

arch/x86/kvm/vmx/tdx.c int tdx_handle_exit(struct kvm_vcpu *vcpu, fastpath_t fastpath) · line 1458

Quoted 5 lines
arch/x86/kvm/vmx/tdx.c +5 -0
+		 * and handled right away.
+		 *
+		 * - If it's an Machine Check System Management Interrupt
+		 *   (MSMI), it's handled above due to non_recoverable bit set.
+		 * - If it's not an MSMI, don't need to do anything here.

This corrects a comment added in patch 100. Maybe we can just merge patch 100 into this one?

Yes. Will do.

Quoted 21 lines
Quoted 20 lines
arch/x86/kvm/vmx/tdx.c +0 -0
 		 */
 		return 1;
 	default:
arch/x86/kvm/vmx/tdx_arch.h +2 -0
index efc3c61c14ab..87ef22e9cd49 100644
@@ -42,6 +42,8 @@
 #define TDH_VP_WR			43
 #define TDH_SYS_LP_SHUTDOWN		44
 
+#define TD_EXIT_OTHER_SMI_IS_MSMI	BIT(1)
+
 /* TDX control structure (TDR/TDCS/TDVPS) field access codes */
 #define TDX_NON_ARCH			BIT_ULL(63)
 #define TDX_CLASS_SHIFT			56
Signature · 1 line
2.25.1
Signature · 1 line
Isaku Yamahata <[email protected]>
#589 lore raw

Re: [PATCH v19 103/130] KVM: TDX: Handle EXIT_REASON_OTHER_SMI with MSMI

Quoted 51 lines · Isaku Yamahata
Quoted 29 lines · Chao Gao
Quoted 22 lines · On Mon, Feb 26, 2024 at 12:26:45AM -0800, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

When BIOS eMCA MCE-SMI morphing is enabled, the #MC is morphed to MSMI (Machine Check System Management Interrupt). Then the SMI causes TD exit with the read reason of EXIT_REASON_OTHER_SMI with MSMI bit set in the exit qualification to KVM instead of EXIT_REASON_EXCEPTION_NMI with MC exception.

Handle EXIT_REASON_OTHER_SMI with MSMI bit set in the exit qualification as MCE(Machine Check Exception) happened during TD guest running.

---
arch/x86/kvm/vmx/tdx.c | 40 ++++++++++++++++++++++++++++++++++---
arch/x86/kvm/vmx/tdx_arch.h | 2 ++

2 files changed, 39 insertions(+), 3 deletions(-)
arch/x86/kvm/vmx/tdx.c +0 -0
index bdd74682b474..117c2315f087 100644

arch/x86/kvm/vmx/tdx.c void tdx_handle_exit_irqoff(struct kvm_vcpu *vcpu) · line 920

Quoted 5 lines
arch/x86/kvm/vmx/tdx.c +2 -0
@@ -916,6 +916,30 @@ void tdx_handle_exit_irqoff(struct kvm_vcpu *vcpu)
 						     tdexit_intr_info(vcpu));
 	else if (exit_reason == EXIT_REASON_EXCEPTION_NMI)
 		vmx_handle_exception_irqoff(vcpu, tdexit_intr_info(vcpu));
+	else if (unlikely(tdx->exit_reason.non_recoverable ||
+		 tdx->exit_reason.error)) {

why not just:

Quoted 5 lines
	else if (tdx->exit_reason.basic == EXIT_REASON_OTHER_SMI) {

i.e., does EXIT_REASON_OTHER_SMI imply exit_reason.non_recoverable or exit_reason.error?

Yes, this should be refined.

Quoted 11 lines

arch/x86/kvm/vmx/tdx.c void tdx_handle_exit_irqoff(struct kvm_vcpu *vcpu) · line 929

Quoted 9 lines
arch/x86/kvm/vmx/tdx.c +9 -0
+		/*
+		 * The only reason it gets EXIT_REASON_OTHER_SMI is there is an
+		 * #MSMI(Machine Check System Management Interrupt) with
+		 * exit_qualification bit 0 set in TD guest.
+		 * The #MSMI is delivered right after SEAMCALL returns,
+		 * and an #MC is delivered to host kernel after SMI handler
+		 * returns.
+		 *
+		 * The #MC right after SEAMCALL is fixed up and skipped in #MC

Looks fixing up and skipping #MC on the first instruction after TD-exit is missing in v19?

Quoted 1 line

Right. We removed it as MSMI will provides if #MC happened in SEAM or not.

According to the patch of host #MC handler
https://lore.kernel.org/lkml/171265126376.10875.16864387954272613660.tip-bot2@tip-bot2/,
the #MC triggered by MSMI can be handled by kernel #MC handler. There is no need to call kvm_machine_check().

Does the following fixup make sense to you?
--------

KVM: TDX: Handle EXIT_REASON_OTHER_SMI

Handle "Other SMI" VM exit for TDX.

Unlike VMX, an SMI occurs in SEAM non-root mode cause VM exit to TDX module, then SEAMRET to KVM. Once it exits to KVM, SMI is delivered and handled by kernel handler right away.

Specifically, when BIOS eMCA MCE-SMI morphing is enabled, the #MC occurs in TDX guest is delivered as an Machine Check System Management Interrupt (MSMI) with the exit reason of EXIT_REASON_OTHER_SMI with MSMI (bit 0) set in the exit qualification.  On VM exit, TDX module checks whether the "Other SMI" is caused by a MSMI or not.  If so, TDX module makes TD as fatal, preventing further TD entries, and then completes the TD exit flow to KVM with the TDH.VP.ENTER outputs indicating TDX_NON_RECOVERABLE_TD. After TD exit, the MSMI is delivered and eventually handled by the kernel #MC handler[1].

So, to handle "Other SMI" VM exit:
- For non-MSMI case, KVM doesn't need to do anything, just continue TDX vCPU execution.
- For MSMI case, since the TDX guest is dead, follow other non-recoverable cases, exit to userspace.

[1] The patch supports handling MSMI signaled during SEAM operation. It's already in tip tree.
https://lore.kernel.org/lkml/171265126376.10875.16864387954272613660.tip-bot2@tip-bot2/

arch/x86/kvm/vmx/tdx.c +0 -0
index 4ee94bfb17e2..fd756d231204 100644
@@ -975,30 +975,6 @@ void tdx_handle_exit_irqoff(struct kvm_vcpu *vcpu)

tdexit_intr_info(vcpu));
        else if (exit_reason == EXIT_REASON_EXCEPTION_NMI) vmx_handle_exception_irqoff(vcpu, tdexit_intr_info(vcpu)); -       else if (unlikely(tdx->exit_reason.non_recoverable ||
-                tdx->exit_reason.error)) {
-               /*
-                * The only reason it gets EXIT_REASON_OTHER_SMI is there is an -                * #MSMI(Machine Check System Management Interrupt) with -                * exit_qualification bit 0 set in TD guest.
-                * The #MSMI is delivered right after SEAMCALL returns, -                * and an #MC is delivered to host kernel after SMI handler -                * returns.
-                *
-                * The #MC right after SEAMCALL is fixed up and skipped in #MC -                * handler because it's an #MC happens in TD guest we cannot -                * handle it with host's context.
-                *
-                * Call KVM's machine check handler explicitly here. -                */
-               if (tdx->exit_reason.basic == EXIT_REASON_OTHER_SMI) { -                       unsigned long exit_qual;
-
-                       exit_qual = tdexit_exit_qual(vcpu);
-                       if (exit_qual & TD_EXIT_OTHER_SMI_IS_MSMI) -                               kvm_machine_check();
-               }
-       }
 }

 static int tdx_handle_exception(struct kvm_vcpu *vcpu)
@@ -1923,10 +1899,6 @@ int tdx_handle_exit(struct kvm_vcpu *vcpu, fastpath_t fastpath) to_kvm_tdx(vcpu->kvm)->hkid, set_hkid_to_hpa(0, to_kvm_tdx(vcpu->kvm)->hkid));

-               /*
-                * tdx_handle_exit_irqoff() handled EXIT_REASON_OTHER_SMI.  It -                * must be handled before enabling preemption because it's #MC. -                */
                goto unhandled_exit;
        }

@@ -1970,14 +1942,14 @@ int tdx_handle_exit(struct kvm_vcpu *vcpu, fastpath_t fastpath) return tdx_handle_ept_misconfig(vcpu);
        case EXIT_REASON_OTHER_SMI:
                /*
-                * Unlike VMX, all the SMI in SEAM non-root mode (i.e. when -                * TD guest vcpu is running) will cause TD exit to TDX module, -                * then SEAMRET to KVM. Once it exits to KVM, SMI is delivered -                * and handled right away.
+                * Unlike VMX, SMI occurs in SEAM non-root mode (i.e. when +                * TD guest vCPU is running) will cause VM exit to TDX module, +                * then SEAMRET to KVM.  Once it exits to KVM, SMI is delivered +                * and handled by kernel handler right away.
                 *
-                * - If it's an Machine Check System Management Interrupt -                *   (MSMI), it's handled above due to non_recoverable bit set. -                * - If it's not an MSMI, don't need to do anything here. +                * - A MSMI will not reach here, it's handled as non_recoverable +                *   case above.
+                * - If it's not a MSMI, no need to do anything here.
                 */
                return 1;
        default:

arch/x86/kvm/vmx/tdx_arch.h +0 -0
index 2aecffe9f276..aa2fea7b2652 100644
@@ -41,8 +41,6 @@

 #define TDH_PHYMEM_PAGE_WBINVD         41
 #define TDH_VP_WR                      43

-#define TD_EXIT_OTHER_SMI_IS_MSMI      BIT_ULL(1)
-
 /* TDX control structure (TDR/TDCS/TDVPS) field access codes */ #define TDX_NON_ARCH                   BIT_ULL(63) #define TDX_CLASS_SHIFT                        56

#590 lore raw

Re: [PATCH v19 103/130] KVM: TDX: Handle EXIT_REASON_OTHER_SMI with MSMI

Quoted 11 lines · Binbin Wu
Quoted 11 lines
Quoted 11 lines

Review

Quoted 9 lines
(quoted diff) +9 -0
+		/*
+		 * The only reason it gets EXIT_REASON_OTHER_SMI is there is an
+		 * #MSMI(Machine Check System Management Interrupt) with
+		 * exit_qualification bit 0 set in TD guest.
+		 * The #MSMI is delivered right after SEAMCALL returns,
+		 * and an #MC is delivered to host kernel after SMI handler
+		 * returns.
+		 *
+		 * The #MC right after SEAMCALL is fixed up and skipped in #MC

Looks fixing up and skipping #MC on the first instruction after TD-exit is missing in v19?

Quoted 8 lines
Quoted 1 line

Right. We removed it as MSMI will provides if #MC happened in SEAM or not.

According to the patch of host #MC handler
https://lore.kernel.org/lkml/171265126376.10875.16864387954272613660.tip-bot2@tip-bot2/,
the #MC triggered by MSMI can be handled by kernel #MC handler. There is no need to call kvm_machine_check().

Does the following fixup make sense to you?

Yes. Now this patch becomes mostly none. So it makes more sense to squash this patch into [PATCH v19 100/130] KVM: TDX: handle EXIT_REASON_OTHER_SMI.

Quoted 127 lines

--------

KVM: TDX: Handle EXIT_REASON_OTHER_SMI

Handle "Other SMI" VM exit for TDX.

Unlike VMX, an SMI occurs in SEAM non-root mode cause VM exit to TDX module, then SEAMRET to KVM. Once it exits to KVM, SMI is delivered and handled by kernel handler right away.

Specifically, when BIOS eMCA MCE-SMI morphing is enabled, the #MC occurs in TDX guest is delivered as an Machine Check System Management Interrupt (MSMI) with the exit reason of EXIT_REASON_OTHER_SMI with MSMI (bit 0) set in the exit qualification.  On VM exit, TDX module checks whether the "Other SMI" is caused by a MSMI or not.  If so, TDX module makes TD as fatal, preventing further TD entries, and then completes the TD exit flow to KVM with the TDH.VP.ENTER outputs indicating TDX_NON_RECOVERABLE_TD. After TD exit, the MSMI is delivered and eventually handled by the kernel #MC handler[1].

So, to handle "Other SMI" VM exit:
- For non-MSMI case, KVM doesn't need to do anything, just continue TDX vCPU execution.
- For MSMI case, since the TDX guest is dead, follow other non-recoverable cases, exit to userspace.

[1] The patch supports handling MSMI signaled during SEAM operation. It's already in tip tree.
https://lore.kernel.org/lkml/171265126376.10875.16864387954272613660.tip-bot2@tip-bot2/

arch/x86/kvm/vmx/tdx.c +0 -4
index 4ee94bfb17e2..fd756d231204 100644
@@ -975,30 +975,6 @@ void tdx_handle_exit_irqoff(struct kvm_vcpu *vcpu)
 tdexit_intr_info(vcpu));
         else if (exit_reason == EXIT_REASON_EXCEPTION_NMI)
                 vmx_handle_exception_irqoff(vcpu, tdexit_intr_info(vcpu));
-       else if (unlikely(tdx->exit_reason.non_recoverable ||
-                tdx->exit_reason.error)) {
-               /*
-                * The only reason it gets EXIT_REASON_OTHER_SMI is there is

an
-                * #MSMI(Machine Check System Management Interrupt) with -                * exit_qualification bit 0 set in TD guest.
-                * The #MSMI is delivered right after SEAMCALL returns, -                * and an #MC is delivered to host kernel after SMI handler -                * returns.
-                *
-                * The #MC right after SEAMCALL is fixed up and skipped in #MC
-                * handler because it's an #MC happens in TD guest we cannot -                * handle it with host's context.
-                *
-                * Call KVM's machine check handler explicitly here. -                */
-               if (tdx->exit_reason.basic == EXIT_REASON_OTHER_SMI) { -                       unsigned long exit_qual;
-
-                       exit_qual = tdexit_exit_qual(vcpu);
-                       if (exit_qual & TD_EXIT_OTHER_SMI_IS_MSMI) -                               kvm_machine_check();
-               }
-       }
 }

 static int tdx_handle_exception(struct kvm_vcpu *vcpu)

arch/x86/kvm/vmx/tdx.c +0 -2
@@ -1923,10 +1899,6 @@ int tdx_handle_exit(struct kvm_vcpu *vcpu, fastpath_t
 fastpath)
                               to_kvm_tdx(vcpu->kvm)->hkid,
                               set_hkid_to_hpa(0,
 to_kvm_tdx(vcpu->kvm)->hkid));
 
-               /*
-                * tdx_handle_exit_irqoff() handled EXIT_REASON_OTHER_SMI. 

It
-                * must be handled before enabling preemption because it's #MC.
-                */
                goto unhandled_exit;
        }

arch/x86/kvm/vmx/tdx.c +0 -0
@@ -1970,14 +1942,14 @@ int tdx_handle_exit(struct kvm_vcpu *vcpu,
 fastpath_t fastpath)
                 return tdx_handle_ept_misconfig(vcpu);

        case EXIT_REASON_OTHER_SMI:
                /*
-                * Unlike VMX, all the SMI in SEAM non-root mode (i.e. when -                * TD guest vcpu is running) will cause TD exit to TDX module,
-                * then SEAMRET to KVM. Once it exits to KVM, SMI is delivered
-                * and handled right away.
+                * Unlike VMX, SMI occurs in SEAM non-root mode (i.e. when +                * TD guest vCPU is running) will cause VM exit to TDX module,
+                * then SEAMRET to KVM.  Once it exits to KVM, SMI is delivered
+                * and handled by kernel handler right away.
                 *
-                * - If it's an Machine Check System Management Interrupt -                *   (MSMI), it's handled above due to non_recoverable bit set.
-                * - If it's not an MSMI, don't need to do anything here. +                * - A MSMI will not reach here, it's handled as non_recoverable
+                *   case above.
+                * - If it's not a MSMI, no need to do anything here. */
                return 1;
        default:

arch/x86/kvm/vmx/tdx_arch.h +0 -2
index 2aecffe9f276..aa2fea7b2652 100644
@@ -41,8 +41,6 @@
  #define TDH_PHYMEM_PAGE_WBINVD         41
  #define TDH_VP_WR                      43
 
-#define TD_EXIT_OTHER_SMI_IS_MSMI      BIT_ULL(1)
-
  /* TDX control structure (TDR/TDCS/TDVPS) field access codes */
  #define TDX_NON_ARCH                   BIT_ULL(63)
  #define TDX_CLASS_SHIFT                        56
Signature · 1 line
Isaku Yamahata <[email protected]>
#591 lore raw

PATCH v19 104/130 KVM: TDX: Add a place holder for handler of TDX hypercalls (TDG.VP.VMCALL)

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

The TDX module specification defines TDG.VP.VMCALL API (TDVMCALL for short) for the guest TD to call hypercall to VMM. When the guest TD issues TDG.VP.VMCALL, the guest TD exits to VMM with a new exit reason of TDVMCALL. The arguments from the guest TD and returned values from the VMM are passed in the guest registers. The guest RCX registers indicates which registers are used. Define helper functions to access those registers as ABI.

Define the TDVMCALL exit reason, which is carved out from the VMX exit reason namespace as the TDVMCALL exit from TDX guest to TDX-SEAM is really just a VM-Exit. Add a place holder to handle TDVMCALL exit.

---
 arch/x86/include/uapi/asm/vmx.h |  4 ++-
 arch/x86/kvm/vmx/tdx.c          | 53 +++++++++++++++++++++++++++++++++
 arch/x86/kvm/vmx/tdx.h          | 13 ++++++++
 3 files changed, 69 insertions(+), 1 deletion(-)
arch/x86/include/uapi/asm/vmx.h +3 -1
index b3a30ef3efdd..f0f4a4cf84a7 100644
@@ -93,6 +93,7 @@
 #define EXIT_REASON_TPAUSE              68
 #define EXIT_REASON_BUS_LOCK            74
 #define EXIT_REASON_NOTIFY              75
+#define EXIT_REASON_TDCALL              77
 
 #define VMX_EXIT_REASONS \
 	{ EXIT_REASON_EXCEPTION_NMI,         "EXCEPTION_NMI" }, \
@@ -156,7 +157,8 @@
 	{ EXIT_REASON_UMWAIT,                "UMWAIT" }, \
 	{ EXIT_REASON_TPAUSE,                "TPAUSE" }, \
 	{ EXIT_REASON_BUS_LOCK,              "BUS_LOCK" }, \
-	{ EXIT_REASON_NOTIFY,                "NOTIFY" }
+	{ EXIT_REASON_NOTIFY,                "NOTIFY" }, \
+	{ EXIT_REASON_TDCALL,                "TDCALL" }
 
 #define VMX_EXIT_REASON_FLAGS \
 	{ VMX_EXIT_REASONS_FAILED_VMENTRY,	"FAILED_VMENTRY" }
arch/x86/kvm/vmx/tdx.c +53 -0

77 lines not on this page · raw message

arch/x86/kvm/vmx/tdx.h +13 -0
index eaffa7384725..4399d474764f 100644
@@ -83,6 +83,19 @@ struct vcpu_tdx {
 
 	struct list_head cpu_list;
 
+	union {
+		struct {
+			union {
+				struct {
+					u16 gpr_mask;
+					u16 xmm_mask;
+				};
+				u32 regs_mask;
+			};
+			u32 reserved;
+		};
+		u64 rcx;
+	} tdvmcall;
 	union tdx_exit_reason exit_reason;
 
 	bool initialized;
Signature · 1 line
2.25.1
#592 lore raw

Re: [PATCH v19 104/130] KVM: TDX: Add a place holder for handler of TDX hypercalls (TDG.VP.VMCALL)

arch/x86/kvm/vmx/tdx.c fastpath_t tdx_vcpu_run(struct kvm_vcpu *vcpu) · line 936

Quoted 9 lines

static inline bool is_td_vcpu_created(struct vcpu_tdx *tdx)

{
	return tdx->td_vcpu_created;
arch/x86/kvm/vmx/tdx.c +2 -0
@@ -897,6 +932,11 @@ fastpath_t tdx_vcpu_run(struct kvm_vcpu *vcpu)
 
 	tdx_complete_interrupts(vcpu);
 
+	if (tdx->exit_reason.basic == EXIT_REASON_TDCALL)
+		tdx->tdvmcall.rcx = vcpu->arch.regs[VCPU_REGS_RCX];

kvm_rcx_read()?

arch/x86/kvm/vmx/tdx.c fastpath_t tdx_vcpu_run(struct kvm_vcpu *vcpu) · line 938

Quoted 2 lines
arch/x86/kvm/vmx/tdx.c +2 -0
+	else
+		tdx->tdvmcall.rcx = 0;

RCX on TDVMCALL exit is supposed to be consumed by TDX module. I don't get why caching it is necessary. Can tdx->tdvmcall be simply dropped?

#593 lore raw

Re: [PATCH v19 104/130] KVM: TDX: Add a place holder for handler of TDX hypercalls (TDG.VP.VMCALL)

Quoted 13 lines · Chao Gao

arch/x86/kvm/vmx/tdx.c fastpath_t tdx_vcpu_run(struct kvm_vcpu *vcpu) · line 936

Quoted 9 lines

static inline bool is_td_vcpu_created(struct vcpu_tdx *tdx)

{
	return tdx->td_vcpu_created;
arch/x86/kvm/vmx/tdx.c +2 -0
@@ -897,6 +932,11 @@ fastpath_t tdx_vcpu_run(struct kvm_vcpu *vcpu)
 
 	tdx_complete_interrupts(vcpu);
 
+	if (tdx->exit_reason.basic == EXIT_REASON_TDCALL)
+		tdx->tdvmcall.rcx = vcpu->arch.regs[VCPU_REGS_RCX];

kvm_rcx_read()?

Quoted 5 lines

arch/x86/kvm/vmx/tdx.c fastpath_t tdx_vcpu_run(struct kvm_vcpu *vcpu) · line 938

Quoted 2 lines
arch/x86/kvm/vmx/tdx.c +2 -0
+	else
+		tdx->tdvmcall.rcx = 0;

RCX on TDVMCALL exit is supposed to be consumed by TDX module. I don't get why caching it is necessary. Can tdx->tdvmcall be simply dropped?

Now it's not used. Will drop tdvmcall.

It was originally used to remember a original register mask of TDVMCALL, and tdx_complete_vp_vmcall() used it as a valid value to copy back the output values. The current tdx_complete_vp_vmcall() uses kvm_rcx_read() because even if the user space changes rcx, it doesn't harm to KVM. KVM does what the user space tells.

Signature · 1 line
Isaku Yamahata <[email protected]>
#594 lore raw

PATCH v19 105/130 KVM: TDX: handle KVM hypercall with TDG.VP.VMCALL

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

The TDX Guest-Host communication interface (GHCI) specification defines
the ABI for the guest TD to issue hypercall. It reserves vendor specific
arguments for VMM specific use. Use it as KVM hypercall and handle it.

---
 arch/x86/kvm/vmx/tdx.c | 33 +++++++++++++++++++++++++++++++++
 1 file changed, 33 insertions(+)
arch/x86/kvm/vmx/tdx.c +33 -0
index 0be58cd428b3..c8eb47591105 100644
@@ -1008,8 +1008,41 @@ static int tdx_handle_triple_fault(struct kvm_vcpu *vcpu)
 	return 0;
 }
 
+static int tdx_emulate_vmcall(struct kvm_vcpu *vcpu)
+{
+	unsigned long nr, a0, a1, a2, a3, ret;
+
+	/*
+	 * ABI for KVM tdvmcall argument:
+	 * In Guest-Hypervisor Communication Interface(GHCI) specification,
+	 * Non-zero leaf number (R10 != 0) is defined to indicate
+	 * vendor-specific.  KVM uses this for KVM hypercall.  NOTE: KVM
+	 * hypercall number starts from one.  Zero isn't used for KVM hypercall
+	 * number.
+	 *
+	 * R10: KVM hypercall number
+	 * arguments: R11, R12, R13, R14.
+	 */
+	nr = kvm_r10_read(vcpu);
+	a0 = kvm_r11_read(vcpu);
+	a1 = kvm_r12_read(vcpu);
+	a2 = kvm_r13_read(vcpu);
+	a3 = kvm_r14_read(vcpu);
+
+	ret = __kvm_emulate_hypercall(vcpu, nr, a0, a1, a2, a3, true, 0);
+
+	tdvmcall_set_return_code(vcpu, ret);
+
+	if (nr == KVM_HC_MAP_GPA_RANGE && !ret)
+		return 0;
+	return 1;
+}
+
 static int handle_tdvmcall(struct kvm_vcpu *vcpu)
 {
+	if (tdvmcall_exit_type(vcpu))
+		return tdx_emulate_vmcall(vcpu);
+
 	switch (tdvmcall_leaf(vcpu)) {
 	default:
 		break;
Signature · 1 line
2.25.1
#595 lore raw

Re: [PATCH v19 105/130] KVM: TDX: handle KVM hypercall with TDG.VP.VMCALL

arch/x86/kvm/vmx/tdx.c static int tdx_handle_triple_fault(struct kvm_vcpu *vcpu) · line 1014

Quoted 4 lines
arch/x86/kvm/vmx/tdx.c +4 -0
+static int tdx_emulate_vmcall(struct kvm_vcpu *vcpu)
+{
+	unsigned long nr, a0, a1, a2, a3, ret;
+

do you need to emulate xen/hyper-v hypercalls here?

Nothing tells userspace that xen/hyper-v hypercalls are not supported and so userspace may expose related CPUID leafs to TD guests.

Quoted 13 lines
arch/x86/kvm/vmx/tdx.c +13 -0
+	/*
+	 * ABI for KVM tdvmcall argument:
+	 * In Guest-Hypervisor Communication Interface(GHCI) specification,
+	 * Non-zero leaf number (R10 != 0) is defined to indicate
+	 * vendor-specific.  KVM uses this for KVM hypercall.  NOTE: KVM
+	 * hypercall number starts from one.  Zero isn't used for KVM hypercall
+	 * number.
+	 *
+	 * R10: KVM hypercall number
+	 * arguments: R11, R12, R13, R14.
+	 */
+	nr = kvm_r10_read(vcpu);
+	a0 = kvm_r11_read(vcpu);

arch/x86/kvm/vmx/tdx.c static int tdx_handle_triple_fault(struct kvm_vcpu *vcpu) · line 1037

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +10 -0
+	a1 = kvm_r12_read(vcpu);
+	a2 = kvm_r13_read(vcpu);
+	a3 = kvm_r14_read(vcpu);
+
+	ret = __kvm_emulate_hypercall(vcpu, nr, a0, a1, a2, a3, true, 0);
+
+	tdvmcall_set_return_code(vcpu, ret);
+
+	if (nr == KVM_HC_MAP_GPA_RANGE && !ret)
+		return 0;

Can you add a comment to call out that KVM_HC_MAP_GPA_RANGE is redirected to the userspace?

Quoted 2 lines
arch/x86/kvm/vmx/tdx.c +2 -0
+	return 1;
+}
#596 lore raw

Re: [PATCH v19 105/130] KVM: TDX: handle KVM hypercall with TDG.VP.VMCALL

Quoted 6 lines · Chao Gao

arch/x86/kvm/vmx/tdx.c static int tdx_handle_triple_fault(struct kvm_vcpu *vcpu) · line 1014

Quoted 4 lines
arch/x86/kvm/vmx/tdx.c +4 -0
+static int tdx_emulate_vmcall(struct kvm_vcpu *vcpu)
+{
+	unsigned long nr, a0, a1, a2, a3, ret;
+

do you need to emulate xen/hyper-v hypercalls here?

No. kvm_emulate_hypercall() handles xen/hyper-v hypercalls, __kvm_emulate_hypercall() doesn't.

Quoted 21 lines

Nothing tells userspace that xen/hyper-v hypercalls are not supported and so userspace may expose related CPUID leafs to TD guests.

Quoted 18 lines
arch/x86/kvm/vmx/tdx.c +18 -0
+	/*
+	 * ABI for KVM tdvmcall argument:
+	 * In Guest-Hypervisor Communication Interface(GHCI) specification,
+	 * Non-zero leaf number (R10 != 0) is defined to indicate
+	 * vendor-specific.  KVM uses this for KVM hypercall.  NOTE: KVM
+	 * hypercall number starts from one.  Zero isn't used for KVM hypercall
+	 * number.
+	 *
+	 * R10: KVM hypercall number
+	 * arguments: R11, R12, R13, R14.
+	 */
+	nr = kvm_r10_read(vcpu);
+	a0 = kvm_r11_read(vcpu);
+	a1 = kvm_r12_read(vcpu);
+	a2 = kvm_r13_read(vcpu);
+	a3 = kvm_r14_read(vcpu);
+
+	ret = __kvm_emulate_hypercall(vcpu, nr, a0, a1, a2, a3, true, 0);
Quoted 8 lines

arch/x86/kvm/vmx/tdx.c static int tdx_handle_triple_fault(struct kvm_vcpu *vcpu) · line 1037

Quoted 5 lines
arch/x86/kvm/vmx/tdx.c +5 -0
+
+	tdvmcall_set_return_code(vcpu, ret);
+
+	if (nr == KVM_HC_MAP_GPA_RANGE && !ret)
+		return 0;

Can you add a comment to call out that KVM_HC_MAP_GPA_RANGE is redirected to the userspace?

Yes, this is confusing. We should refactor kvm_emulate_hypercall() more so that the caller shouldn't care about the return value like this. Will refactor it and update this patch.

Signature · 1 line
Isaku Yamahata <[email protected]>
#597 lore raw

Re: [PATCH v19 105/130] KVM: TDX: handle KVM hypercall with TDG.VP.VMCALL

Quoted 11 lines · Isaku Yamahata
Quoted 5 lines · Chao Gao

arch/x86/kvm/vmx/tdx.c static int tdx_handle_triple_fault(struct kvm_vcpu *vcpu) · line 1014

Quoted 4 lines
arch/x86/kvm/vmx/tdx.c +4 -0
+static int tdx_emulate_vmcall(struct kvm_vcpu *vcpu)
+{
+	unsigned long nr, a0, a1, a2, a3, ret;
+

do you need to emulate xen/hyper-v hypercalls here?

No. kvm_emulate_hypercall() handles xen/hyper-v hypercalls, __kvm_emulate_hypercall() doesn't.

So for TDX, kvm doesn't support xen/hyper-v, right?

Then, should KVM_CAP_XEN_HVM and KVM_CAP_HYPERV be filtered out for TDX?

Quoted 4 lines
Quoted 4 lines

Nothing tells userspace that xen/hyper-v hypercalls are not supported and so userspace may expose related CPUID leafs to TD guests.

#598 lore raw

Re: [PATCH v19 105/130] KVM: TDX: handle KVM hypercall with TDG.VP.VMCALL

Quoted 12 lines · Binbin Wu
Quoted 9 lines · Isaku Yamahata
Quoted 5 lines · Chao Gao

arch/x86/kvm/vmx/tdx.c static int tdx_handle_triple_fault(struct kvm_vcpu *vcpu) · line 1014

Quoted 4 lines
arch/x86/kvm/vmx/tdx.c +4 -0
+static int tdx_emulate_vmcall(struct kvm_vcpu *vcpu)
+{
+	unsigned long nr, a0, a1, a2, a3, ret;
+

do you need to emulate xen/hyper-v hypercalls here?

Quoted 5 lines
Quoted 2 lines

No. kvm_emulate_hypercall() handles xen/hyper-v hypercalls, __kvm_emulate_hypercall() doesn't.

So for TDX, kvm doesn't support xen/hyper-v, right?

Then, should KVM_CAP_XEN_HVM and KVM_CAP_HYPERV be filtered out for TDX?

That's right. We should update kvm_vm_ioctl_check_extension() and kvm_vcpu_ioctl_enable_cap(). I didn't pay attention to them.

Signature · 1 line
Isaku Yamahata <[email protected]>
#599 lore raw

Re: [PATCH v19 105/130] KVM: TDX: handle KVM hypercall with TDG.VP.VMCALL

Quoted 13 lines · Isaku Yamahata
Quoted 10 lines · Binbin Wu
Quoted 8 lines · Isaku Yamahata
Quoted 5 lines · Chao Gao

arch/x86/kvm/vmx/tdx.c static int tdx_handle_triple_fault(struct kvm_vcpu *vcpu) · line 1014

Quoted 4 lines
arch/x86/kvm/vmx/tdx.c +4 -0
+static int tdx_emulate_vmcall(struct kvm_vcpu *vcpu)
+{
+	unsigned long nr, a0, a1, a2, a3, ret;
+

do you need to emulate xen/hyper-v hypercalls here?

Quoted 7 lines
Quoted 5 lines
Quoted 2 lines

No. kvm_emulate_hypercall() handles xen/hyper-v hypercalls, __kvm_emulate_hypercall() doesn't.

So for TDX, kvm doesn't support xen/hyper-v, right?

Then, should KVM_CAP_XEN_HVM and KVM_CAP_HYPERV be filtered out for TDX?

That's right. We should update kvm_vm_ioctl_check_extension() and kvm_vcpu_ioctl_enable_cap(). I didn't pay attention to them.

Currently, QEMU checks the capabilities for Hyper-v/Xen via kvm_check_extension(), which is the global version.
Only modifications in KVM can't hide these capabilities. It needs userspace to use VM or vCPU version to check the capabilities for Hyper-v and Xen. Is it a change of ABI when the old global version is still workable, but userspace switches to use VM/vCPU version to check capabilities for Hyper-v and Xen? Are there objections if both QEMU and KVM are modified in order to hide Hyper-v/Xen capabilities for TDX?

#600 lore raw

Re: [PATCH v19 105/130] KVM: TDX: handle KVM hypercall with TDG.VP.VMCALL

Quoted 23 lines · Binbin Wu
Quoted 13 lines · Isaku Yamahata
Quoted 10 lines · Binbin Wu
Quoted 8 lines · Isaku Yamahata
Quoted 5 lines · Chao Gao

arch/x86/kvm/vmx/tdx.c static int tdx_handle_triple_fault(struct kvm_vcpu *vcpu) · line 1014

Quoted 4 lines
arch/x86/kvm/vmx/tdx.c +4 -0
+static int tdx_emulate_vmcall(struct kvm_vcpu *vcpu)
+{
+	unsigned long nr, a0, a1, a2, a3, ret;
+

do you need to emulate xen/hyper-v hypercalls here?

Quoted 7 lines
Quoted 5 lines
Quoted 2 lines

No. kvm_emulate_hypercall() handles xen/hyper-v hypercalls, __kvm_emulate_hypercall() doesn't.

So for TDX, kvm doesn't support xen/hyper-v, right?

Then, should KVM_CAP_XEN_HVM and KVM_CAP_HYPERV be filtered out for TDX?

That's right. We should update kvm_vm_ioctl_check_extension() and kvm_vcpu_ioctl_enable_cap(). I didn't pay attention to them.

Quoted 9 lines

Currently, QEMU checks the capabilities for Hyper-v/Xen via kvm_check_extension(), which is the global version.
Only modifications in KVM can't hide these capabilities. It needs userspace to use VM or vCPU version to check the capabilities for Hyper-v and Xen. Is it a change of ABI when the old global version is still workable, but userspace switches to use VM/vCPU version to check capabilities for Hyper-v and Xen?
Are there objections if both QEMU and KVM are modified in order to hide Hyper-v/Xen capabilities for TDX?

I think it's okay for KVM_X86_TDX_VM as long as we don't change the value for KVM_X86_DEFAULT_VM. Because vm_type KVM_X86_TDX_VM is different from the default and the document (Documentation/virt/kvm/api.rst), 4.4 KVM_CHECK_EXTENSION explicitly encourages VM version.

Based on their initialization different VMs may have different capabilities. It is thus encouraged to use the vm ioctl to query for capabilities (available with KVM_CAP_CHECK_EXTENSION_VM on the vm fd)

The change to qemu will be mostly trivial with the quick check.

Signature · 1 line
Isaku Yamahata <[email protected]>
#601 lore raw

PATCH v19 106/130 KVM: TDX: Add KVM Exit for TDX TDG.VP.VMCALL

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

Some of TDG.VP.VMCALL require device model, for example, qemu, to handle them on behalf of kvm kernel module. TDVMCALL_REPORT_FATAL_ERROR, TDVMCALL_MAP_GPA, TDVMCALL_SETUP_EVENT_NOTIFY_INTERRUPT, and TDVMCALL_GET_QUOTE requires user space VMM handling.

Introduce new kvm exit, KVM_EXIT_TDX, and functions to setup it. Device model should update R10 if necessary as return value.

---
v14 -> v15:
- updated struct kvm_tdx_exit with union
- export constants for reg bitmask

---
 arch/x86/kvm/vmx/tdx.c   | 83 ++++++++++++++++++++++++++++++++++++-
 include/uapi/linux/kvm.h | 89 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 170 insertions(+), 2 deletions(-)
arch/x86/kvm/vmx/tdx.c +81 -2
index c8eb47591105..72dbe2ff9062 100644
@@ -1038,6 +1038,78 @@ static int tdx_emulate_vmcall(struct kvm_vcpu *vcpu)
 	return 1;
 }
 
+static int tdx_complete_vp_vmcall(struct kvm_vcpu *vcpu)
+{
+	struct kvm_tdx_vmcall *tdx_vmcall = &vcpu->run->tdx.u.vmcall;
+	__u64 reg_mask = kvm_rcx_read(vcpu);
+
+#define COPY_REG(MASK, REG)							\
+	do {									\
+		if (reg_mask & TDX_VMCALL_REG_MASK_ ## MASK)			\
+			kvm_## REG ## _write(vcpu, tdx_vmcall->out_ ## REG);	\
+	} while (0)
+
+
+	COPY_REG(R10, r10);
+	COPY_REG(R11, r11);
+	COPY_REG(R12, r12);
+	COPY_REG(R13, r13);
+	COPY_REG(R14, r14);
+	COPY_REG(R15, r15);
+	COPY_REG(RBX, rbx);
+	COPY_REG(RDI, rdi);
+	COPY_REG(RSI, rsi);
+	COPY_REG(R8, r8);
+	COPY_REG(R9, r9);
+	COPY_REG(RDX, rdx);
+
+#undef COPY_REG
+
+	return 1;
+}
+
+static int tdx_vp_vmcall_to_user(struct kvm_vcpu *vcpu)
+{
+	struct kvm_tdx_vmcall *tdx_vmcall = &vcpu->run->tdx.u.vmcall;
+	__u64 reg_mask;
+
+	vcpu->arch.complete_userspace_io = tdx_complete_vp_vmcall;
+	memset(tdx_vmcall, 0, sizeof(*tdx_vmcall));
+
+	vcpu->run->exit_reason = KVM_EXIT_TDX;
+	vcpu->run->tdx.type = KVM_EXIT_TDX_VMCALL;
+
+	reg_mask = kvm_rcx_read(vcpu);
+	tdx_vmcall->reg_mask = reg_mask;
+
+#define COPY_REG(MASK, REG)							\
+	do {									\
+		if (reg_mask & TDX_VMCALL_REG_MASK_ ## MASK) {			\
+			tdx_vmcall->in_ ## REG = kvm_ ## REG ## _read(vcpu);	\
+			tdx_vmcall->out_ ## REG = tdx_vmcall->in_ ## REG;	\
+		}								\
+	} while (0)
+
+
+	COPY_REG(R10, r10);
+	COPY_REG(R11, r11);
+	COPY_REG(R12, r12);
+	COPY_REG(R13, r13);
+	COPY_REG(R14, r14);
+	COPY_REG(R15, r15);
+	COPY_REG(RBX, rbx);
+	COPY_REG(RDI, rdi);
+	COPY_REG(RSI, rsi);
+	COPY_REG(R8, r8);
+	COPY_REG(R9, r9);
+	COPY_REG(RDX, rdx);
+
+#undef COPY_REG
+
+	/* notify userspace to handle the request */
+	return 0;
+}
+
 static int handle_tdvmcall(struct kvm_vcpu *vcpu)
 {
 	if (tdvmcall_exit_type(vcpu))
@@ -1048,8 +1120,15 @@ static int handle_tdvmcall(struct kvm_vcpu *vcpu)
 		break;
 	}
 
-	tdvmcall_set_return_code(vcpu, TDVMCALL_INVALID_OPERAND);
-	return 1;
+	/*
+	 * Unknown VMCALL.  Toss the request to the user space VMM, e.g. qemu,
+	 * as it may know how to handle.
+	 *
+	 * Those VMCALLs require user space VMM:
+	 * TDVMCALL_REPORT_FATAL_ERROR, TDVMCALL_MAP_GPA,
+	 * TDVMCALL_SETUP_EVENT_NOTIFY_INTERRUPT, and TDVMCALL_GET_QUOTE.
+	 */
+	return tdx_vp_vmcall_to_user(vcpu);
 }
 
 void tdx_load_mmu_pgd(struct kvm_vcpu *vcpu, hpa_t root_hpa, int pgd_level)
include/uapi/linux/kvm.h +89 -0

107 lines not on this page · raw message

Signature · 1 line
2.25.1
#602 lore raw

Re: [PATCH v19 106/130] KVM: TDX: Add KVM Exit for TDX TDG.VP.VMCALL

Quoted 30 lines · On Mon, Feb 26, 2024 at 12:26:48AM -0800, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

Some of TDG.VP.VMCALL require device model, for example, qemu, to handle them on behalf of kvm kernel module. TDVMCALL_REPORT_FATAL_ERROR, TDVMCALL_MAP_GPA, TDVMCALL_SETUP_EVENT_NOTIFY_INTERRUPT, and TDVMCALL_GET_QUOTE requires user space VMM handling.

Introduce new kvm exit, KVM_EXIT_TDX, and functions to setup it. Device model should update R10 if necessary as return value.

---
v14 -> v15:
- updated struct kvm_tdx_exit with union
- export constants for reg bitmask

---
arch/x86/kvm/vmx/tdx.c | 83 ++++++++++++++++++++++++++++++++++++-
include/uapi/linux/kvm.h | 89 ++++++++++++++++++++++++++++++++++++++++

2 files changed, 170 insertions(+), 2 deletions(-)
arch/x86/kvm/vmx/tdx.c +0 -0
index c8eb47591105..72dbe2ff9062 100644
@@ -1038,6 +1038,78 @@ static int tdx_emulate_vmcall(struct kvm_vcpu *vcpu)
 	return 1;
 }
 

arch/x86/kvm/vmx/tdx.c static int tdx_emulate_vmcall(struct kvm_vcpu *vcpu) · line 1050

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +10 -0
+static int tdx_complete_vp_vmcall(struct kvm_vcpu *vcpu)
+{
+	struct kvm_tdx_vmcall *tdx_vmcall = &vcpu->run->tdx.u.vmcall;
+	__u64 reg_mask = kvm_rcx_read(vcpu);
+
+#define COPY_REG(MASK, REG)							\
+	do {									\
+		if (reg_mask & TDX_VMCALL_REG_MASK_ ## MASK)			\
+			kvm_## REG ## _write(vcpu, tdx_vmcall->out_ ## REG);	\
+	} while (0)

Why XMMs are not copied?

Looks you assume the guest won't use XMMs for TDVMCALL. But I think the ABI (KVM_EXIT_TDX) should be general, i.e., can support all kinds of (future) TDVMCALLs.

Quoted 19 lines
arch/x86/kvm/vmx/tdx.c +19 -0
+
+
+	COPY_REG(R10, r10);
+	COPY_REG(R11, r11);
+	COPY_REG(R12, r12);
+	COPY_REG(R13, r13);
+	COPY_REG(R14, r14);
+	COPY_REG(R15, r15);
+	COPY_REG(RBX, rbx);
+	COPY_REG(RDI, rdi);
+	COPY_REG(RSI, rsi);
+	COPY_REG(R8, r8);
+	COPY_REG(R9, r9);
+	COPY_REG(RDX, rdx);
+
+#undef COPY_REG
+
+	return 1;
+}
#603 lore raw

Re: [PATCH v19 106/130] KVM: TDX: Add KVM Exit for TDX TDG.VP.VMCALL

In reply to [email protected]
Quoted 9 lines · On Mon, Feb 26, 2024, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

Some of TDG.VP.VMCALL require device model, for example, qemu, to handle them on behalf of kvm kernel module. TDVMCALL_REPORT_FATAL_ERROR, TDVMCALL_MAP_GPA, TDVMCALL_SETUP_EVENT_NOTIFY_INTERRUPT, and TDVMCALL_GET_QUOTE requires user space VMM handling.

Introduce new kvm exit, KVM_EXIT_TDX, and functions to setup it. Device model should update R10 if necessary as return value.

Hard NAK.

KVM needs its own ABI, under no circumstance should KVM inherit ABI directly from the GHCI. Even worse, this doesn't even sanity check the "unknown" VMCALLs, KVM just blindly punts *everything* to userspace. And even worse than that, KVM already has at least one user exit that overlaps, TDVMCALL_MAP_GPA => KVM_HC_MAP_GPA_RANGE.

If the userspace VMM wants to run an end-around on KVM and directly communicate with the guest, e.g. via a synthetic device (a la virtio), that's totally fine, because *KVM* is not definining any unique ABI, KVM is purely providing the transport, e.g. emulated MMIO or PIO (and maybe not even that). IIRC, this option even came up in the context of GET_QUOTE.

But explicit exiting to userspace with KVM_EXIT_TDX is very different. KVM is creating a contract with userspace that says "for TDX VMCALLs [a-z], KVM will exit to userspace with values [a-z]". *Every* new VMCALL that's added to the GHCI will become KVM ABI, e.g. if Intel ships a TDX module that adds a new VMALL, then KVM will forward the exit to userspace, and userspace can then start relying on that behavior.

And punting all register state, decoding, etc. to userspace creates a crap ABI. KVM effectively did this for SEV and SEV-ES by copying the PSP ABI verbatim into KVM ioctls(), and it's a gross, ugly mess.

Each VMCALL that KVM wants to forward needs a dedicated KVM_EXIT_<reason> and associated struct in the exit union. Yes, it's slightly more work now, but it's one time pain. Whereas copying all registers is endless misery for everyone involved, e.g. *every* userspace VMM needs to decipher the registers, do sanity checking, etc. And *every* end user needs to do the same when a debugging inevitable failures.

This also solves Chao's comment about XMM registers. Except for emualting Hyper-V hypercalls, which have very explicit handling, KVM does NOT support using XMM registers in hypercalls.

Quoted 230 lines

---
v14 -> v15:
- updated struct kvm_tdx_exit with union
- export constants for reg bitmask

---
 arch/x86/kvm/vmx/tdx.c   | 83 ++++++++++++++++++++++++++++++++++++-
 include/uapi/linux/kvm.h | 89 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 170 insertions(+), 2 deletions(-)
arch/x86/kvm/vmx/tdx.c +81 -2
index c8eb47591105..72dbe2ff9062 100644
@@ -1038,6 +1038,78 @@ static int tdx_emulate_vmcall(struct kvm_vcpu *vcpu)
 	return 1;
 }
 
+static int tdx_complete_vp_vmcall(struct kvm_vcpu *vcpu)
+{
+	struct kvm_tdx_vmcall *tdx_vmcall = &vcpu->run->tdx.u.vmcall;
+	__u64 reg_mask = kvm_rcx_read(vcpu);
+
+#define COPY_REG(MASK, REG)							\
+	do {									\
+		if (reg_mask & TDX_VMCALL_REG_MASK_ ## MASK)			\
+			kvm_## REG ## _write(vcpu, tdx_vmcall->out_ ## REG);	\
+	} while (0)
+
+
+	COPY_REG(R10, r10);
+	COPY_REG(R11, r11);
+	COPY_REG(R12, r12);
+	COPY_REG(R13, r13);
+	COPY_REG(R14, r14);
+	COPY_REG(R15, r15);
+	COPY_REG(RBX, rbx);
+	COPY_REG(RDI, rdi);
+	COPY_REG(RSI, rsi);
+	COPY_REG(R8, r8);
+	COPY_REG(R9, r9);
+	COPY_REG(RDX, rdx);
+
+#undef COPY_REG
+
+	return 1;
+}
+
+static int tdx_vp_vmcall_to_user(struct kvm_vcpu *vcpu)
+{
+	struct kvm_tdx_vmcall *tdx_vmcall = &vcpu->run->tdx.u.vmcall;
+	__u64 reg_mask;
+
+	vcpu->arch.complete_userspace_io = tdx_complete_vp_vmcall;
+	memset(tdx_vmcall, 0, sizeof(*tdx_vmcall));
+
+	vcpu->run->exit_reason = KVM_EXIT_TDX;
+	vcpu->run->tdx.type = KVM_EXIT_TDX_VMCALL;
+
+	reg_mask = kvm_rcx_read(vcpu);
+	tdx_vmcall->reg_mask = reg_mask;
+
+#define COPY_REG(MASK, REG)							\
+	do {									\
+		if (reg_mask & TDX_VMCALL_REG_MASK_ ## MASK) {			\
+			tdx_vmcall->in_ ## REG = kvm_ ## REG ## _read(vcpu);	\
+			tdx_vmcall->out_ ## REG = tdx_vmcall->in_ ## REG;	\
+		}								\
+	} while (0)
+
+
+	COPY_REG(R10, r10);
+	COPY_REG(R11, r11);
+	COPY_REG(R12, r12);
+	COPY_REG(R13, r13);
+	COPY_REG(R14, r14);
+	COPY_REG(R15, r15);
+	COPY_REG(RBX, rbx);
+	COPY_REG(RDI, rdi);
+	COPY_REG(RSI, rsi);
+	COPY_REG(R8, r8);
+	COPY_REG(R9, r9);
+	COPY_REG(RDX, rdx);
+
+#undef COPY_REG
+
+	/* notify userspace to handle the request */
+	return 0;
+}
+
 static int handle_tdvmcall(struct kvm_vcpu *vcpu)
 {
 	if (tdvmcall_exit_type(vcpu))
@@ -1048,8 +1120,15 @@ static int handle_tdvmcall(struct kvm_vcpu *vcpu)
 		break;
 	}
 
-	tdvmcall_set_return_code(vcpu, TDVMCALL_INVALID_OPERAND);
-	return 1;
+	/*
+	 * Unknown VMCALL.  Toss the request to the user space VMM, e.g. qemu,
+	 * as it may know how to handle.
+	 *
+	 * Those VMCALLs require user space VMM:
+	 * TDVMCALL_REPORT_FATAL_ERROR, TDVMCALL_MAP_GPA,
+	 * TDVMCALL_SETUP_EVENT_NOTIFY_INTERRUPT, and TDVMCALL_GET_QUOTE.
+	 */
+	return tdx_vp_vmcall_to_user(vcpu);
 }
 
 void tdx_load_mmu_pgd(struct kvm_vcpu *vcpu, hpa_t root_hpa, int pgd_level)
include/uapi/linux/kvm.h +89 -0
index 5e2b28934aa9..a7aa804ef021 100644
@@ -167,6 +167,92 @@ struct kvm_xen_exit {
 	} u;
 };
 
+/* masks for reg_mask to indicate which registers are passed. */
+#define TDX_VMCALL_REG_MASK_RBX	BIT_ULL(2)
+#define TDX_VMCALL_REG_MASK_RDX	BIT_ULL(3)
+#define TDX_VMCALL_REG_MASK_RSI	BIT_ULL(6)
+#define TDX_VMCALL_REG_MASK_RDI	BIT_ULL(7)
+#define TDX_VMCALL_REG_MASK_R8	BIT_ULL(8)
+#define TDX_VMCALL_REG_MASK_R9	BIT_ULL(9)
+#define TDX_VMCALL_REG_MASK_R10	BIT_ULL(10)
+#define TDX_VMCALL_REG_MASK_R11	BIT_ULL(11)
+#define TDX_VMCALL_REG_MASK_R12	BIT_ULL(12)
+#define TDX_VMCALL_REG_MASK_R13	BIT_ULL(13)
+#define TDX_VMCALL_REG_MASK_R14	BIT_ULL(14)
+#define TDX_VMCALL_REG_MASK_R15	BIT_ULL(15)
+
+struct kvm_tdx_exit {
+#define KVM_EXIT_TDX_VMCALL	1
+	__u32 type;
+	__u32 pad;
+
+	union {
+		struct kvm_tdx_vmcall {
+			/*
+			 * RAX(bit 0), RCX(bit 1) and RSP(bit 4) are reserved.
+			 * RAX(bit 0): TDG.VP.VMCALL status code.
+			 * RCX(bit 1): bitmap for used registers.
+			 * RSP(bit 4): the caller stack.
+			 */
+			union {
+				__u64 in_rcx;
+				__u64 reg_mask;
+			};
+
+			/*
+			 * Guest-Host-Communication Interface for TDX spec
+			 * defines the ABI for TDG.VP.VMCALL.
+			 */
+			/* Input parameters: guest -> VMM */
+			union {
+				__u64 in_r10;
+				__u64 type;
+			};
+			union {
+				__u64 in_r11;
+				__u64 subfunction;
+			};
+			/*
+			 * Subfunction specific.
+			 * Registers are used in this order to pass input
+			 * arguments.  r12=arg0, r13=arg1, etc.
+			 */
+			__u64 in_r12;
+			__u64 in_r13;
+			__u64 in_r14;
+			__u64 in_r15;
+			__u64 in_rbx;
+			__u64 in_rdi;
+			__u64 in_rsi;
+			__u64 in_r8;
+			__u64 in_r9;
+			__u64 in_rdx;
+
+			/* Output parameters: VMM -> guest */
+			union {
+				__u64 out_r10;
+				__u64 status_code;
+			};
+			/*
+			 * Subfunction specific.
+			 * Registers are used in this order to output return
+			 * values.  r11=ret0, r12=ret1, etc.
+			 */
+			__u64 out_r11;
+			__u64 out_r12;
+			__u64 out_r13;
+			__u64 out_r14;
+			__u64 out_r15;
+			__u64 out_rbx;
+			__u64 out_rdi;
+			__u64 out_rsi;
+			__u64 out_r8;
+			__u64 out_r9;
+			__u64 out_rdx;
+		} vmcall;
+	} u;
+};
+
 #define KVM_S390_GET_SKEYS_NONE   1
 #define KVM_S390_SKEYS_MAX        1048576
 
@@ -210,6 +296,7 @@ struct kvm_xen_exit {
 #define KVM_EXIT_NOTIFY           37
 #define KVM_EXIT_LOONGARCH_IOCSR  38
 #define KVM_EXIT_MEMORY_FAULT     39
+#define KVM_EXIT_TDX              40
 
 /* For KVM_EXIT_INTERNAL_ERROR */
 /* Emulate instruction failed. */
@@ -470,6 +557,8 @@ struct kvm_run {
 			__u64 gpa;
 			__u64 size;
 		} memory_fault;
+		/* KVM_EXIT_TDX_VMCALL */
+		struct kvm_tdx_exit tdx;
 		/* Fix the size of the union. */
 		char padding[256];
 	};
Signature · 1 line
2.25.1
#604 lore raw

Re: [PATCH v19 106/130] KVM: TDX: Add KVM Exit for TDX TDG.VP.VMCALL

Quoted 42 lines · Sean Christopherson
Quoted 9 lines · On Mon, Feb 26, 2024, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

Some of TDG.VP.VMCALL require device model, for example, qemu, to handle them on behalf of kvm kernel module. TDVMCALL_REPORT_FATAL_ERROR, TDVMCALL_MAP_GPA, TDVMCALL_SETUP_EVENT_NOTIFY_INTERRUPT, and TDVMCALL_GET_QUOTE requires user space VMM handling.

Introduce new kvm exit, KVM_EXIT_TDX, and functions to setup it. Device model should update R10 if necessary as return value.

Hard NAK.

KVM needs its own ABI, under no circumstance should KVM inherit ABI directly from the GHCI. Even worse, this doesn't even sanity check the "unknown" VMCALLs, KVM just blindly punts *everything* to userspace. And even worse than that, KVM already has at least one user exit that overlaps, TDVMCALL_MAP_GPA => KVM_HC_MAP_GPA_RANGE.

If the userspace VMM wants to run an end-around on KVM and directly communicate with the guest, e.g. via a synthetic device (a la virtio), that's totally fine, because *KVM* is not definining any unique ABI, KVM is purely providing the transport, e.g. emulated MMIO or PIO (and maybe not even that). IIRC, this option even came up in the context of GET_QUOTE.

But explicit exiting to userspace with KVM_EXIT_TDX is very different. KVM is creating a contract with userspace that says "for TDX VMCALLs [a-z], KVM will exit to userspace with values [a-z]". *Every* new VMCALL that's added to the GHCI will become KVM ABI, e.g. if Intel ships a TDX module that adds a new VMALL, then KVM will forward the exit to userspace, and userspace can then start relying on that behavior.

And punting all register state, decoding, etc. to userspace creates a crap ABI. KVM effectively did this for SEV and SEV-ES by copying the PSP ABI verbatim into KVM ioctls(), and it's a gross, ugly mess.

Each VMCALL that KVM wants to forward needs a dedicated KVM_EXIT_<reason> and associated struct in the exit union. Yes, it's slightly more work now, but it's one time pain. Whereas copying all registers is endless misery for everyone involved, e.g. *every* userspace VMM needs to decipher the registers, do sanity checking, etc. And *every* end user needs to do the same when a debugging inevitable failures.

Quoted 3 lines

This also solves Chao's comment about XMM registers. Except for emualting Hyper-V hypercalls, which have very explicit handling, KVM does NOT support using XMM registers in hypercalls.

Sure. I will introduce the followings.

KVM_EXIT_TDX_GET_QUOTE
Request a quote.

KVM_EXIT_TDX_SETUP_EVENT_NOTIFY_INTERRUPT
Guest tells which interrupt vector the VMM uses to notify the guest. The use case if GetQuote. It is async request. The user-space VMM uses this interrupts to notify the guest on the completion. Or guest polls it.

KVM_EXIT_TDX_REPORT_FATAL_ERROR
Guest panicked. This conveys extra 64 bytes in registers. Probably this should be converted to KVM_EXIT_SYSTEM_EVENT and KVM_SYSTEM_EVENT_CRASH.

MapGPA is converted to KVM_HC_MAP_GPA_RANGE.

Signature · 1 line
Isaku Yamahata <[email protected]>
#605 lore raw

PATCH v19 107/130 KVM: TDX: Handle TDX PV CPUID hypercall

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

Wire up TDX PV CPUID hypercall to the KVM backend function.

---
 arch/x86/kvm/vmx/tdx.c | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)
arch/x86/kvm/vmx/tdx.c +22 -0
index 72dbe2ff9062..eb68d6c148b6 100644
@@ -1110,12 +1110,34 @@ static int tdx_vp_vmcall_to_user(struct kvm_vcpu *vcpu)
 	return 0;
 }
 
+static int tdx_emulate_cpuid(struct kvm_vcpu *vcpu)
+{
+	u32 eax, ebx, ecx, edx;
+
+	/* EAX and ECX for cpuid is stored in R12 and R13. */
+	eax = tdvmcall_a0_read(vcpu);
+	ecx = tdvmcall_a1_read(vcpu);
+
+	kvm_cpuid(vcpu, &eax, &ebx, &ecx, &edx, false);
+
+	tdvmcall_a0_write(vcpu, eax);
+	tdvmcall_a1_write(vcpu, ebx);
+	tdvmcall_a2_write(vcpu, ecx);
+	tdvmcall_a3_write(vcpu, edx);
+
+	tdvmcall_set_return_code(vcpu, TDVMCALL_SUCCESS);
+
+	return 1;
+}
+
 static int handle_tdvmcall(struct kvm_vcpu *vcpu)
 {
 	if (tdvmcall_exit_type(vcpu))
 		return tdx_emulate_vmcall(vcpu);
 
 	switch (tdvmcall_leaf(vcpu)) {
+	case EXIT_REASON_CPUID:
+		return tdx_emulate_cpuid(vcpu);
 	default:
 		break;
 	}
Signature · 1 line
2.25.1
#606 lore raw

PATCH v19 108/130 KVM: TDX: Handle TDX PV HLT hypercall

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

Wire up TDX PV HLT hypercall to the KVM backend function.

---
v19:
- move tdvps_state_non_arch_check() to this patch

v18:
- drop buggy_hlt_workaround and use TDH.VP.RD(TD_VCPU_STATE_DETAILS)

---
 arch/x86/kvm/vmx/tdx.c | 26 +++++++++++++++++++++++++-
 arch/x86/kvm/vmx/tdx.h |  4 ++++
 2 files changed, 29 insertions(+), 1 deletion(-)
arch/x86/kvm/vmx/tdx.c +25 -1
index eb68d6c148b6..a2caf2ae838c 100644
@@ -688,7 +688,18 @@ void tdx_vcpu_load(struct kvm_vcpu *vcpu, int cpu)
 
 bool tdx_protected_apic_has_interrupt(struct kvm_vcpu *vcpu)
 {
-	return pi_has_pending_interrupt(vcpu);
+	bool ret = pi_has_pending_interrupt(vcpu);
+	union tdx_vcpu_state_details details;
+	struct vcpu_tdx *tdx = to_tdx(vcpu);
+
+	if (ret || vcpu->arch.mp_state != KVM_MP_STATE_HALTED)
+		return true;
+
+	if (tdx->interrupt_disabled_hlt)
+		return false;
+
+	details.full = td_state_non_arch_read64(tdx, TD_VCPU_STATE_DETAILS_NON_ARCH);
+	return !!details.vmxip;
 }
 
 void tdx_prepare_switch_to_guest(struct kvm_vcpu *vcpu)
@@ -1130,6 +1141,17 @@ static int tdx_emulate_cpuid(struct kvm_vcpu *vcpu)
 	return 1;
 }
 
+static int tdx_emulate_hlt(struct kvm_vcpu *vcpu)
+{
+	struct vcpu_tdx *tdx = to_tdx(vcpu);
+
+	/* See tdx_protected_apic_has_interrupt() to avoid heavy seamcall */
+	tdx->interrupt_disabled_hlt = tdvmcall_a0_read(vcpu);
+
+	tdvmcall_set_return_code(vcpu, TDVMCALL_SUCCESS);
+	return kvm_emulate_halt_noskip(vcpu);
+}
+
 static int handle_tdvmcall(struct kvm_vcpu *vcpu)
 {
 	if (tdvmcall_exit_type(vcpu))
@@ -1138,6 +1160,8 @@ static int handle_tdvmcall(struct kvm_vcpu *vcpu)
 	switch (tdvmcall_leaf(vcpu)) {
 	case EXIT_REASON_CPUID:
 		return tdx_emulate_cpuid(vcpu);
+	case EXIT_REASON_HLT:
+		return tdx_emulate_hlt(vcpu);
 	default:
 		break;
 	}
arch/x86/kvm/vmx/tdx.h +4 -0
index 4399d474764f..11c74c34555f 100644
@@ -104,6 +104,8 @@ struct vcpu_tdx {
 	bool host_state_need_restore;
 	u64 msr_host_kernel_gs_base;
 
+	bool interrupt_disabled_hlt;
+
 	/*
 	 * Dummy to make pmu_intel not corrupt memory.
 	 * TODO: Support PMU for TDX.  Future work.
@@ -166,6 +168,7 @@ static __always_inline void tdvps_vmcs_check(u32 field, u8 bits)
 }
 
 static __always_inline void tdvps_management_check(u64 field, u8 bits) {}
+static __always_inline void tdvps_state_non_arch_check(u64 field, u8 bits) {}
 
 #define TDX_BUILD_TDVPS_ACCESSORS(bits, uclass, lclass)				\
 static __always_inline u##bits td_##lclass##_read##bits(struct vcpu_tdx *tdx,	\
@@ -226,6 +229,7 @@ TDX_BUILD_TDVPS_ACCESSORS(32, VMCS, vmcs);
 TDX_BUILD_TDVPS_ACCESSORS(64, VMCS, vmcs);
 
 TDX_BUILD_TDVPS_ACCESSORS(8, MANAGEMENT, management);
+TDX_BUILD_TDVPS_ACCESSORS(64, STATE_NON_ARCH, state_non_arch);
 
 static __always_inline u64 td_tdcs_exec_read64(struct kvm_tdx *kvm_tdx, u32 field)
 {
Signature · 1 line
2.25.1
#607 lore raw

Re: [PATCH v19 108/130] KVM: TDX: Handle TDX PV HLT hypercall

Quoted 23 lines · On Mon, Feb 26, 2024 at 12:26:50AM -0800, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

Wire up TDX PV HLT hypercall to the KVM backend function.

---
v19:
- move tdvps_state_non_arch_check() to this patch

v18:
- drop buggy_hlt_workaround and use TDH.VP.RD(TD_VCPU_STATE_DETAILS)

---
arch/x86/kvm/vmx/tdx.c | 26 +++++++++++++++++++++++++- arch/x86/kvm/vmx/tdx.h | 4 ++++

2 files changed, 29 insertions(+), 1 deletion(-)
arch/x86/kvm/vmx/tdx.c +0 -0
index eb68d6c148b6..a2caf2ae838c 100644

arch/x86/kvm/vmx/tdx.c void tdx_vcpu_load(struct kvm_vcpu *vcpu, int cpu) · line 691

Quoted 5 lines
arch/x86/kvm/vmx/tdx.c +1 -1
@@ -688,7 +688,18 @@ void tdx_vcpu_load(struct kvm_vcpu *vcpu, int cpu)
 
 bool tdx_protected_apic_has_interrupt(struct kvm_vcpu *vcpu)
 {
-	return pi_has_pending_interrupt(vcpu);
+	bool ret = pi_has_pending_interrupt(vcpu);

Maybe

	bool has_pending_interrupt = pi_has_pending_interrupt(vcpu);

"ret" isn't a good name. or even call pi_has_pending_interrupt() directly in the if statement below.

arch/x86/kvm/vmx/tdx.c void tdx_vcpu_load(struct kvm_vcpu *vcpu, int cpu) · line 696

Quoted 5 lines
arch/x86/kvm/vmx/tdx.c +5 -0
+	union tdx_vcpu_state_details details;
+	struct vcpu_tdx *tdx = to_tdx(vcpu);
+
+	if (ret || vcpu->arch.mp_state != KVM_MP_STATE_HALTED)
+		return true;

Question: why mp_state matters here?

arch/x86/kvm/vmx/tdx.c void tdx_vcpu_load(struct kvm_vcpu *vcpu, int cpu) · line 699

Quoted 3 lines
arch/x86/kvm/vmx/tdx.c +3 -0
+
+	if (tdx->interrupt_disabled_hlt)
+		return false;

Shouldn't we move this into vt_interrupt_allowed()? VMX calls the function to check if interrupt is disabled. KVM can clear tdx->interrupt_disabled_hlt on every TD-enter and set it only on TD-exit due to the guest making a TDVMCALL(hlt) w/ interrupt disabled.

Quoted 66 lines
arch/x86/kvm/vmx/tdx.c +16 -0
+
+	details.full = td_state_non_arch_read64(tdx, TD_VCPU_STATE_DETAILS_NON_ARCH);
+	return !!details.vmxip;
 }
 
 void tdx_prepare_switch_to_guest(struct kvm_vcpu *vcpu)
@@ -1130,6 +1141,17 @@ static int tdx_emulate_cpuid(struct kvm_vcpu *vcpu)
 	return 1;
 }
 
+static int tdx_emulate_hlt(struct kvm_vcpu *vcpu)
+{
+	struct vcpu_tdx *tdx = to_tdx(vcpu);
+
+	/* See tdx_protected_apic_has_interrupt() to avoid heavy seamcall */
+	tdx->interrupt_disabled_hlt = tdvmcall_a0_read(vcpu);
+
+	tdvmcall_set_return_code(vcpu, TDVMCALL_SUCCESS);
+	return kvm_emulate_halt_noskip(vcpu);
+}
+
 static int handle_tdvmcall(struct kvm_vcpu *vcpu)
 {
 	if (tdvmcall_exit_type(vcpu))
@@ -1138,6 +1160,8 @@ static int handle_tdvmcall(struct kvm_vcpu *vcpu)
 	switch (tdvmcall_leaf(vcpu)) {
 	case EXIT_REASON_CPUID:
 		return tdx_emulate_cpuid(vcpu);
+	case EXIT_REASON_HLT:
+		return tdx_emulate_hlt(vcpu);
 	default:
 		break;
 	}
arch/x86/kvm/vmx/tdx.h +4 -0
index 4399d474764f..11c74c34555f 100644
@@ -104,6 +104,8 @@ struct vcpu_tdx {
 	bool host_state_need_restore;
 	u64 msr_host_kernel_gs_base;
 
+	bool interrupt_disabled_hlt;
+
 	/*
 	 * Dummy to make pmu_intel not corrupt memory.
 	 * TODO: Support PMU for TDX.  Future work.
@@ -166,6 +168,7 @@ static __always_inline void tdvps_vmcs_check(u32 field, u8 bits)
 }
 
 static __always_inline void tdvps_management_check(u64 field, u8 bits) {}
+static __always_inline void tdvps_state_non_arch_check(u64 field, u8 bits) {}
 
 #define TDX_BUILD_TDVPS_ACCESSORS(bits, uclass, lclass)				\
 static __always_inline u##bits td_##lclass##_read##bits(struct vcpu_tdx *tdx,	\
@@ -226,6 +229,7 @@ TDX_BUILD_TDVPS_ACCESSORS(32, VMCS, vmcs);
 TDX_BUILD_TDVPS_ACCESSORS(64, VMCS, vmcs);
 
 TDX_BUILD_TDVPS_ACCESSORS(8, MANAGEMENT, management);
+TDX_BUILD_TDVPS_ACCESSORS(64, STATE_NON_ARCH, state_non_arch);
 
 static __always_inline u64 td_tdcs_exec_read64(struct kvm_tdx *kvm_tdx, u32 field)
 {
Signature · 1 line
2.25.1
#608 lore raw

Re: [PATCH v19 108/130] KVM: TDX: Handle TDX PV HLT hypercall

Quoted 31 lines · Chao Gao
Quoted 23 lines · On Mon, Feb 26, 2024 at 12:26:50AM -0800, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

Wire up TDX PV HLT hypercall to the KVM backend function.

---
v19:
- move tdvps_state_non_arch_check() to this patch

v18:
- drop buggy_hlt_workaround and use TDH.VP.RD(TD_VCPU_STATE_DETAILS)

---
arch/x86/kvm/vmx/tdx.c | 26 +++++++++++++++++++++++++- arch/x86/kvm/vmx/tdx.h | 4 ++++

2 files changed, 29 insertions(+), 1 deletion(-)
arch/x86/kvm/vmx/tdx.c +0 -0
index eb68d6c148b6..a2caf2ae838c 100644

arch/x86/kvm/vmx/tdx.c void tdx_vcpu_load(struct kvm_vcpu *vcpu, int cpu) · line 691

Quoted 5 lines
arch/x86/kvm/vmx/tdx.c +1 -1
@@ -688,7 +688,18 @@ void tdx_vcpu_load(struct kvm_vcpu *vcpu, int cpu)
 
 bool tdx_protected_apic_has_interrupt(struct kvm_vcpu *vcpu)
 {
-	return pi_has_pending_interrupt(vcpu);
+	bool ret = pi_has_pending_interrupt(vcpu);

Maybe

Quoted 4 lines
	bool has_pending_interrupt = pi_has_pending_interrupt(vcpu);

"ret" isn't a good name. or even call pi_has_pending_interrupt() directly in the if statement below.

Ya, or split the if-statement into multiple chucks, with comments explaining what each non-intuitive chunk is doing. The pi_has_pending_interrupt(vcpu) check is self-explanatory, the halted thing, not so much. They are terminal statements, there's zero reason to pre-check the PID.

E.g.

	/*
	 * Comment explaining why KVM needs to assume a non-halted vCPU has a
	 * pending interrupt (KVM can't see RFLAGS.IF).
	 */
	if (vcpu->arch.mp_state != KVM_MP_STATE_HALTED)
		return true;

	if (pi_has_pending_interrupt(vcpu))
		return;
Quoted 7 lines

arch/x86/kvm/vmx/tdx.c void tdx_vcpu_load(struct kvm_vcpu *vcpu, int cpu) · line 696

Quoted 5 lines
arch/x86/kvm/vmx/tdx.c +5 -0
+	union tdx_vcpu_state_details details;
+	struct vcpu_tdx *tdx = to_tdx(vcpu);
+
+	if (ret || vcpu->arch.mp_state != KVM_MP_STATE_HALTED)
+		return true;

Question: why mp_state matters here?

Quoted 8 lines

arch/x86/kvm/vmx/tdx.c void tdx_vcpu_load(struct kvm_vcpu *vcpu, int cpu) · line 699

Quoted 3 lines
arch/x86/kvm/vmx/tdx.c +3 -0
+
+	if (tdx->interrupt_disabled_hlt)
+		return false;

Shouldn't we move this into vt_interrupt_allowed()? VMX calls the function to check if interrupt is disabled. KVM can clear tdx->interrupt_disabled_hlt on every TD-enter and set it only on TD-exit due to the guest making a TDVMCALL(hlt) w/ interrupt disabled.

I'm pretty sure interrupt_disabled_hlt shouldn't exist, should "a0", a.k.a. r12, be preserved at this point?

	/* Another comment explaning magic code. */
	if (to_vmx(vcpu)->exit_reason.basic == EXIT_REASON_HLT &&
	    tdvmcall_a0_read(vcpu))
		return false;

Actually, can't this all be:

	if (to_vmx(vcpu)->exit_reason.basic != EXIT_REASON_HLT)
		return true;

	if (!tdvmcall_a0_read(vcpu))
		return false;

	if (pi_has_pending_interrupt(vcpu))
		return true;

	return tdx_has_pending_virtual_interrupt(vcpu);
#609 lore raw

Re: [PATCH v19 108/130] KVM: TDX: Handle TDX PV HLT hypercall

Quoted 44 lines · Sean Christopherson
Quoted 31 lines · Chao Gao
Quoted 23 lines · On Mon, Feb 26, 2024 at 12:26:50AM -0800, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

Wire up TDX PV HLT hypercall to the KVM backend function.

---
v19:
- move tdvps_state_non_arch_check() to this patch

v18:
- drop buggy_hlt_workaround and use TDH.VP.RD(TD_VCPU_STATE_DETAILS)

---
arch/x86/kvm/vmx/tdx.c | 26 +++++++++++++++++++++++++- arch/x86/kvm/vmx/tdx.h | 4 ++++

2 files changed, 29 insertions(+), 1 deletion(-)
arch/x86/kvm/vmx/tdx.c +0 -0
index eb68d6c148b6..a2caf2ae838c 100644

arch/x86/kvm/vmx/tdx.c void tdx_vcpu_load(struct kvm_vcpu *vcpu, int cpu) · line 691

Quoted 5 lines
arch/x86/kvm/vmx/tdx.c +1 -1
@@ -688,7 +688,18 @@ void tdx_vcpu_load(struct kvm_vcpu *vcpu, int cpu)
 
 bool tdx_protected_apic_has_interrupt(struct kvm_vcpu *vcpu)
 {
-	return pi_has_pending_interrupt(vcpu);
+	bool ret = pi_has_pending_interrupt(vcpu);

Maybe

Quoted 4 lines
	bool has_pending_interrupt = pi_has_pending_interrupt(vcpu);

"ret" isn't a good name. or even call pi_has_pending_interrupt() directly in the if statement below.

Ya, or split the if-statement into multiple chucks, with comments explaining what each non-intuitive chunk is doing. The pi_has_pending_interrupt(vcpu) check is self-explanatory, the halted thing, not so much. They are terminal statements, there's zero reason to pre-check the PID.

E.g.

Quoted 23 lines
	/*
	 * Comment explaining why KVM needs to assume a non-halted vCPU has a
	 * pending interrupt (KVM can't see RFLAGS.IF).
	 */
	if (vcpu->arch.mp_state != KVM_MP_STATE_HALTED)
		return true;

	if (pi_has_pending_interrupt(vcpu))
		return;
Quoted 13 lines

arch/x86/kvm/vmx/tdx.c void tdx_vcpu_load(struct kvm_vcpu *vcpu, int cpu) · line 696

Quoted 5 lines
arch/x86/kvm/vmx/tdx.c +5 -0
+	union tdx_vcpu_state_details details;
+	struct vcpu_tdx *tdx = to_tdx(vcpu);
+
+	if (ret || vcpu->arch.mp_state != KVM_MP_STATE_HALTED)
+		return true;

Question: why mp_state matters here?

arch/x86/kvm/vmx/tdx.c void tdx_vcpu_load(struct kvm_vcpu *vcpu, int cpu) · line 699

Quoted 3 lines
arch/x86/kvm/vmx/tdx.c +3 -0
+
+	if (tdx->interrupt_disabled_hlt)
+		return false;

Shouldn't we move this into vt_interrupt_allowed()? VMX calls the function to check if interrupt is disabled.

Chao, are you suggesting to implement tdx_interrupt_allowed() as "EXIT_REASON_HLT && a0" instead of "return true"?
I don't think it makes sense because it's rare case and we can't avoid spurious wakeup for TDX case.

Quoted 15 lines
Quoted 3 lines

KVM can clear tdx->interrupt_disabled_hlt on
every TD-enter and set it only on TD-exit due to the guest making a TDVMCALL(hlt) w/ interrupt disabled.

I'm pretty sure interrupt_disabled_hlt shouldn't exist, should "a0", a.k.a. r12, be preserved at this point?

	/* Another comment explaning magic code. */
	if (to_vmx(vcpu)->exit_reason.basic == EXIT_REASON_HLT &&
	    tdvmcall_a0_read(vcpu))
		return false;

Actually, can't this all be:

Quoted 11 lines
	if (to_vmx(vcpu)->exit_reason.basic != EXIT_REASON_HLT)
		return true;

	if (!tdvmcall_a0_read(vcpu))
		return false;

	if (pi_has_pending_interrupt(vcpu))
		return true;

	return tdx_has_pending_virtual_interrupt(vcpu);

Thanks for the suggestion. This is much cleaner. Will update the function.

Signature · 1 line
Isaku Yamahata <[email protected]>
#610 lore raw

Re: [PATCH v19 108/130] KVM: TDX: Handle TDX PV HLT hypercall

Quoted 14 lines
Quoted 7 lines
Quoted 7 lines

arch/x86/kvm/vmx/tdx.c void tdx_vcpu_load(struct kvm_vcpu *vcpu, int cpu) · line 696

Quoted 5 lines
arch/x86/kvm/vmx/tdx.c +5 -0
+	union tdx_vcpu_state_details details;
+	struct vcpu_tdx *tdx = to_tdx(vcpu);
+
+	if (ret || vcpu->arch.mp_state != KVM_MP_STATE_HALTED)
+		return true;

Question: why mp_state matters here?

Quoted 6 lines
Quoted 6 lines

arch/x86/kvm/vmx/tdx.c void tdx_vcpu_load(struct kvm_vcpu *vcpu, int cpu) · line 699

Quoted 3 lines
arch/x86/kvm/vmx/tdx.c +3 -0
+
+	if (tdx->interrupt_disabled_hlt)
+		return false;

Shouldn't we move this into vt_interrupt_allowed()? VMX calls the function to check if interrupt is disabled.

Quoted 4 lines

Chao, are you suggesting to implement tdx_interrupt_allowed() as "EXIT_REASON_HLT && a0" instead of "return true"?
I don't think it makes sense because it's rare case and we can't avoid spurious wakeup for TDX case.

Yes. KVM differeniates "interrupt allowed" from "has interrupt", e.g.,

static inline bool kvm_vcpu_has_events(struct kvm_vcpu *vcpu)
...

	if (kvm_arch_interrupt_allowed(vcpu) &&
	    (kvm_cpu_has_interrupt(vcpu) ||
	    kvm_guest_apic_has_interrupt(vcpu)))
		return true;

I think tdx_protected_apic_has_interrupt() mixes them together, which isn't good.

Probably it is a minor thing; if no one else thinks it is better to move the "interrupt allowed" check to tdx_interrupt_allowed(), I am also fine with not doing that.

#611 lore raw

Re: [PATCH v19 108/130] KVM: TDX: Handle TDX PV HLT hypercall

Quoted 24 lines · Chao Gao
Quoted 14 lines
Quoted 7 lines
Quoted 7 lines

arch/x86/kvm/vmx/tdx.c void tdx_vcpu_load(struct kvm_vcpu *vcpu, int cpu) · line 696

Quoted 5 lines
arch/x86/kvm/vmx/tdx.c +5 -0
+	union tdx_vcpu_state_details details;
+	struct vcpu_tdx *tdx = to_tdx(vcpu);
+
+	if (ret || vcpu->arch.mp_state != KVM_MP_STATE_HALTED)
+		return true;

Question: why mp_state matters here?

Quoted 6 lines
Quoted 6 lines

arch/x86/kvm/vmx/tdx.c void tdx_vcpu_load(struct kvm_vcpu *vcpu, int cpu) · line 699

Quoted 3 lines
arch/x86/kvm/vmx/tdx.c +3 -0
+
+	if (tdx->interrupt_disabled_hlt)
+		return false;

Shouldn't we move this into vt_interrupt_allowed()? VMX calls the function to check if interrupt is disabled.

Quoted 4 lines

Chao, are you suggesting to implement tdx_interrupt_allowed() as "EXIT_REASON_HLT && a0" instead of "return true"?
I don't think it makes sense because it's rare case and we can't avoid spurious wakeup for TDX case.

Yes. KVM differeniates "interrupt allowed" from "has interrupt", e.g.,

static inline bool kvm_vcpu_has_events(struct kvm_vcpu *vcpu)
...

Quoted 8 lines
	if (kvm_arch_interrupt_allowed(vcpu) &&
	    (kvm_cpu_has_interrupt(vcpu) ||
	    kvm_guest_apic_has_interrupt(vcpu)))
		return true;

I think tdx_protected_apic_has_interrupt() mixes them together, which isn't good.

Your point is code clarity. Ok, we can code in that way. I don't expect any performance difference.

Signature · 1 line
Isaku Yamahata <[email protected]>
#612 lore raw

PATCH v19 109/130 KVM: TDX: Handle TDX PV port io hypercall

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

Wire up TDX PV port IO hypercall to the KVM backend function.

---
v18:
- Fix out case to set R10 and R11 correctly when user space handled port out.

---
 arch/x86/kvm/vmx/tdx.c | 67 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 67 insertions(+)
arch/x86/kvm/vmx/tdx.c +67 -0
index a2caf2ae838c..55fc6cc6c816 100644
@@ -1152,6 +1152,71 @@ static int tdx_emulate_hlt(struct kvm_vcpu *vcpu)
 	return kvm_emulate_halt_noskip(vcpu);
 }
 
+static int tdx_complete_pio_out(struct kvm_vcpu *vcpu)
+{
+	tdvmcall_set_return_code(vcpu, TDVMCALL_SUCCESS);
+	tdvmcall_set_return_val(vcpu, 0);
+	return 1;
+}
+
+static int tdx_complete_pio_in(struct kvm_vcpu *vcpu)
+{
+	struct x86_emulate_ctxt *ctxt = vcpu->arch.emulate_ctxt;
+	unsigned long val = 0;
+	int ret;
+
+	WARN_ON_ONCE(vcpu->arch.pio.count != 1);
+
+	ret = ctxt->ops->pio_in_emulated(ctxt, vcpu->arch.pio.size,
+					 vcpu->arch.pio.port, &val, 1);
+	WARN_ON_ONCE(!ret);
+
+	tdvmcall_set_return_code(vcpu, TDVMCALL_SUCCESS);
+	tdvmcall_set_return_val(vcpu, val);
+
+	return 1;
+}
+
+static int tdx_emulate_io(struct kvm_vcpu *vcpu)
+{
+	struct x86_emulate_ctxt *ctxt = vcpu->arch.emulate_ctxt;
+	unsigned long val = 0;
+	unsigned int port;
+	int size, ret;
+	bool write;
+
+	++vcpu->stat.io_exits;
+
+	size = tdvmcall_a0_read(vcpu);
+	write = tdvmcall_a1_read(vcpu);
+	port = tdvmcall_a2_read(vcpu);
+
+	if (size != 1 && size != 2 && size != 4) {
+		tdvmcall_set_return_code(vcpu, TDVMCALL_INVALID_OPERAND);
+		return 1;
+	}
+
+	if (write) {
+		val = tdvmcall_a3_read(vcpu);
+		ret = ctxt->ops->pio_out_emulated(ctxt, size, port, &val, 1);
+
+		/* No need for a complete_userspace_io callback. */
+		vcpu->arch.pio.count = 0;
+	} else
+		ret = ctxt->ops->pio_in_emulated(ctxt, size, port, &val, 1);
+
+	if (ret)
+		tdvmcall_set_return_val(vcpu, val);
+	else {
+		if (write)
+			vcpu->arch.complete_userspace_io = tdx_complete_pio_out;
+		else
+			vcpu->arch.complete_userspace_io = tdx_complete_pio_in;
+	}
+
+	return ret;
+}
+
 static int handle_tdvmcall(struct kvm_vcpu *vcpu)
 {
 	if (tdvmcall_exit_type(vcpu))
@@ -1162,6 +1227,8 @@ static int handle_tdvmcall(struct kvm_vcpu *vcpu)
 		return tdx_emulate_cpuid(vcpu);
 	case EXIT_REASON_HLT:
 		return tdx_emulate_hlt(vcpu);
+	case EXIT_REASON_IO_INSTRUCTION:
+		return tdx_emulate_io(vcpu);
 	default:
 		break;
 	}
Signature · 1 line
2.25.1
#613 lore raw

Re: [PATCH v19 109/130] KVM: TDX: Handle TDX PV port io hypercall

Quoted 61 lines · On 2/26/2024 4:26 PM, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

Wire up TDX PV port IO hypercall to the KVM backend function.

---
v18:
- Fix out case to set R10 and R11 correctly when user space handled port out.

---
  arch/x86/kvm/vmx/tdx.c | 67 ++++++++++++++++++++++++++++++++++++++++++
  1 file changed, 67 insertions(+)
arch/x86/kvm/vmx/tdx.c +39 -0
index a2caf2ae838c..55fc6cc6c816 100644
@@ -1152,6 +1152,71 @@ static int tdx_emulate_hlt(struct kvm_vcpu *vcpu)
  	return kvm_emulate_halt_noskip(vcpu);
  }
 
+static int tdx_complete_pio_out(struct kvm_vcpu *vcpu)
+{
+	tdvmcall_set_return_code(vcpu, TDVMCALL_SUCCESS);
+	tdvmcall_set_return_val(vcpu, 0);
+	return 1;
+}
+
+static int tdx_complete_pio_in(struct kvm_vcpu *vcpu)
+{
+	struct x86_emulate_ctxt *ctxt = vcpu->arch.emulate_ctxt;
+	unsigned long val = 0;
+	int ret;
+
+	WARN_ON_ONCE(vcpu->arch.pio.count != 1);
+
+	ret = ctxt->ops->pio_in_emulated(ctxt, vcpu->arch.pio.size,
+					 vcpu->arch.pio.port, &val, 1);
+	WARN_ON_ONCE(!ret);
+
+	tdvmcall_set_return_code(vcpu, TDVMCALL_SUCCESS);
+	tdvmcall_set_return_val(vcpu, val);
+
+	return 1;
+}
+
+static int tdx_emulate_io(struct kvm_vcpu *vcpu)
+{
+	struct x86_emulate_ctxt *ctxt = vcpu->arch.emulate_ctxt;
+	unsigned long val = 0;
+	unsigned int port;
+	int size, ret;
+	bool write;
+
+	++vcpu->stat.io_exits;
+
+	size = tdvmcall_a0_read(vcpu);
+	write = tdvmcall_a1_read(vcpu);
+	port = tdvmcall_a2_read(vcpu);
+

arch/x86/kvm/vmx/tdx.c static int tdx_emulate_hlt(struct kvm_vcpu *vcpu) · line 1203

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +10 -0
+	if (size != 1 && size != 2 && size != 4) {
+		tdvmcall_set_return_code(vcpu, TDVMCALL_INVALID_OPERAND);
+		return 1;
+	}
+
+	if (write) {
+		val = tdvmcall_a3_read(vcpu);
+		ret = ctxt->ops->pio_out_emulated(ctxt, size, port, &val, 1);
+
+		/* No need for a complete_userspace_io callback. */

I am confused about the comment.

The code below sets the complete_userspace_io callback for write case, i.e. tdx_complete_pio_out().

Quoted 28 lines
arch/x86/kvm/vmx/tdx.c +18 -0
+		vcpu->arch.pio.count = 0;
+	} else
+		ret = ctxt->ops->pio_in_emulated(ctxt, size, port, &val, 1);
+
+	if (ret)
+		tdvmcall_set_return_val(vcpu, val);
+	else {
+		if (write)
+			vcpu->arch.complete_userspace_io = tdx_complete_pio_out;
+		else
+			vcpu->arch.complete_userspace_io = tdx_complete_pio_in;
+	}
+
+	return ret;
+}
+
  static int handle_tdvmcall(struct kvm_vcpu *vcpu)
  {
  	if (tdvmcall_exit_type(vcpu))
@@ -1162,6 +1227,8 @@ static int handle_tdvmcall(struct kvm_vcpu *vcpu)
  		return tdx_emulate_cpuid(vcpu);
  	case EXIT_REASON_HLT:
  		return tdx_emulate_hlt(vcpu);
+	case EXIT_REASON_IO_INSTRUCTION:
+		return tdx_emulate_io(vcpu);
  	default:
  		break;
  	}
#614 lore raw

Re: [PATCH v19 109/130] KVM: TDX: Handle TDX PV port io hypercall

Quoted 75 lines · Binbin Wu
Quoted 60 lines · On 2/26/2024 4:26 PM, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

Wire up TDX PV port IO hypercall to the KVM backend function.

---
v18:
- Fix out case to set R10 and R11 correctly when user space handled port out.

---
  arch/x86/kvm/vmx/tdx.c | 67 ++++++++++++++++++++++++++++++++++++++++++
  1 file changed, 67 insertions(+)
arch/x86/kvm/vmx/tdx.c +39 -0
index a2caf2ae838c..55fc6cc6c816 100644
@@ -1152,6 +1152,71 @@ static int tdx_emulate_hlt(struct kvm_vcpu *vcpu)
  	return kvm_emulate_halt_noskip(vcpu);
  }
+static int tdx_complete_pio_out(struct kvm_vcpu *vcpu)
+{
+	tdvmcall_set_return_code(vcpu, TDVMCALL_SUCCESS);
+	tdvmcall_set_return_val(vcpu, 0);
+	return 1;
+}
+
+static int tdx_complete_pio_in(struct kvm_vcpu *vcpu)
+{
+	struct x86_emulate_ctxt *ctxt = vcpu->arch.emulate_ctxt;
+	unsigned long val = 0;
+	int ret;
+
+	WARN_ON_ONCE(vcpu->arch.pio.count != 1);
+
+	ret = ctxt->ops->pio_in_emulated(ctxt, vcpu->arch.pio.size,
+					 vcpu->arch.pio.port, &val, 1);
+	WARN_ON_ONCE(!ret);
+
+	tdvmcall_set_return_code(vcpu, TDVMCALL_SUCCESS);
+	tdvmcall_set_return_val(vcpu, val);
+
+	return 1;
+}
+
+static int tdx_emulate_io(struct kvm_vcpu *vcpu)
+{
+	struct x86_emulate_ctxt *ctxt = vcpu->arch.emulate_ctxt;
+	unsigned long val = 0;
+	unsigned int port;
+	int size, ret;
+	bool write;
+
+	++vcpu->stat.io_exits;
+
+	size = tdvmcall_a0_read(vcpu);
+	write = tdvmcall_a1_read(vcpu);
+	port = tdvmcall_a2_read(vcpu);
+

arch/x86/kvm/vmx/tdx.c static int tdx_emulate_hlt(struct kvm_vcpu *vcpu) · line 1202

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +10 -0
+	if (size != 1 && size != 2 && size != 4) {
+		tdvmcall_set_return_code(vcpu, TDVMCALL_INVALID_OPERAND);
+		return 1;
+	}
+
+	if (write) {
+		val = tdvmcall_a3_read(vcpu);
+		ret = ctxt->ops->pio_out_emulated(ctxt, size, port, &val, 1);
+
+		/* No need for a complete_userspace_io callback. */

I am confused about the comment.

Quoted 2 lines

The code below sets the complete_userspace_io callback for write case, i.e. tdx_complete_pio_out().

You're correct. This comment is stale and should be removed it.

Signature · 1 line
Isaku Yamahata <[email protected]>
#615 lore raw

Re: [PATCH v19 109/130] KVM: TDX: Handle TDX PV port io hypercall

Quoted 77 lines · Isaku Yamahata
Quoted 74 lines · Binbin Wu
Quoted 60 lines · On 2/26/2024 4:26 PM, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

Wire up TDX PV port IO hypercall to the KVM backend function.

---
v18:
- Fix out case to set R10 and R11 correctly when user space handled port out.

---
   arch/x86/kvm/vmx/tdx.c | 67 ++++++++++++++++++++++++++++++++++++++++++
   1 file changed, 67 insertions(+)
arch/x86/kvm/vmx/tdx.c +39 -0
index a2caf2ae838c..55fc6cc6c816 100644
@@ -1152,6 +1152,71 @@ static int tdx_emulate_hlt(struct kvm_vcpu *vcpu)
   	return kvm_emulate_halt_noskip(vcpu);
   }
+static int tdx_complete_pio_out(struct kvm_vcpu *vcpu)
+{
+	tdvmcall_set_return_code(vcpu, TDVMCALL_SUCCESS);
+	tdvmcall_set_return_val(vcpu, 0);
+	return 1;
+}
+
+static int tdx_complete_pio_in(struct kvm_vcpu *vcpu)
+{
+	struct x86_emulate_ctxt *ctxt = vcpu->arch.emulate_ctxt;
+	unsigned long val = 0;
+	int ret;
+
+	WARN_ON_ONCE(vcpu->arch.pio.count != 1);
+
+	ret = ctxt->ops->pio_in_emulated(ctxt, vcpu->arch.pio.size,
+					 vcpu->arch.pio.port, &val, 1);
+	WARN_ON_ONCE(!ret);
+
+	tdvmcall_set_return_code(vcpu, TDVMCALL_SUCCESS);
+	tdvmcall_set_return_val(vcpu, val);
+
+	return 1;
+}
+
+static int tdx_emulate_io(struct kvm_vcpu *vcpu)
+{
+	struct x86_emulate_ctxt *ctxt = vcpu->arch.emulate_ctxt;
+	unsigned long val = 0;
+	unsigned int port;
+	int size, ret;
+	bool write;
+
+	++vcpu->stat.io_exits;
+
+	size = tdvmcall_a0_read(vcpu);
+	write = tdvmcall_a1_read(vcpu);
+	port = tdvmcall_a2_read(vcpu);
+

arch/x86/kvm/vmx/tdx.c static int tdx_emulate_hlt(struct kvm_vcpu *vcpu) · line 1202

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +10 -0
+	if (size != 1 && size != 2 && size != 4) {
+		tdvmcall_set_return_code(vcpu, TDVMCALL_INVALID_OPERAND);
+		return 1;
+	}
+
+	if (write) {
+		val = tdvmcall_a3_read(vcpu);
+		ret = ctxt->ops->pio_out_emulated(ctxt, size, port, &val, 1);
+
+		/* No need for a complete_userspace_io callback. */

I am confused about the comment.

Quoted 3 lines
Quoted 2 lines

The code below sets the complete_userspace_io callback for write case, i.e. tdx_complete_pio_out().

You're correct. This comment is stale and should be removed it.

Also, since the tdx_complete_pio_out() is installed as complete_userspace_io callback for write, it's more reasonable to move the reset of pio.count into tdx_complete_pio_out(). How about the following fixup:

arch/x86/kvm/vmx/tdx.c +0 -0
index 9ead46cb75ab..b43bb8ccddb9 100644
@@ -1115,6 +1115,7 @@ static int tdx_emulate_hlt(struct kvm_vcpu *vcpu)
 

 static int tdx_complete_pio_out(struct kvm_vcpu *vcpu)
 {
+       vcpu->arch.pio.count = 0;
        tdvmcall_set_return_code(vcpu, TDVMCALL_SUCCESS); tdvmcall_set_return_val(vcpu, 0);
        return 1;
@@ -1159,15 +1160,13 @@ static int tdx_emulate_io(struct kvm_vcpu *vcpu) if (write) {
                val = tdvmcall_a3_read(vcpu);
                ret = ctxt->ops->pio_out_emulated(ctxt, size, port, &val, 1); -
-               /* No need for a complete_userspace_io callback. */ -               vcpu->arch.pio.count = 0;
-       } else
+       } else {
                ret = ctxt->ops->pio_in_emulated(ctxt, size, port, &val, 1); +       }

-       if (ret)
+       if (ret) {
                tdvmcall_set_return_val(vcpu, val);
-       else {
+       } else {
                if (write)
                        vcpu->arch.complete_userspace_io = tdx_complete_pio_out; else

#616 lore raw

Re: [PATCH v19 109/130] KVM: TDX: Handle TDX PV port io hypercall

Quoted 80 lines · Binbin Wu
Quoted 77 lines · Isaku Yamahata
Quoted 74 lines · Binbin Wu
Quoted 60 lines · On 2/26/2024 4:26 PM, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

Wire up TDX PV port IO hypercall to the KVM backend function.

---
v18:
- Fix out case to set R10 and R11 correctly when user space handled port out.

---
   arch/x86/kvm/vmx/tdx.c | 67 ++++++++++++++++++++++++++++++++++++++++++
   1 file changed, 67 insertions(+)
arch/x86/kvm/vmx/tdx.c +39 -0
index a2caf2ae838c..55fc6cc6c816 100644
@@ -1152,6 +1152,71 @@ static int tdx_emulate_hlt(struct kvm_vcpu *vcpu)
   	return kvm_emulate_halt_noskip(vcpu);
   }
+static int tdx_complete_pio_out(struct kvm_vcpu *vcpu)
+{
+	tdvmcall_set_return_code(vcpu, TDVMCALL_SUCCESS);
+	tdvmcall_set_return_val(vcpu, 0);
+	return 1;
+}
+
+static int tdx_complete_pio_in(struct kvm_vcpu *vcpu)
+{
+	struct x86_emulate_ctxt *ctxt = vcpu->arch.emulate_ctxt;
+	unsigned long val = 0;
+	int ret;
+
+	WARN_ON_ONCE(vcpu->arch.pio.count != 1);
+
+	ret = ctxt->ops->pio_in_emulated(ctxt, vcpu->arch.pio.size,
+					 vcpu->arch.pio.port, &val, 1);
+	WARN_ON_ONCE(!ret);
+
+	tdvmcall_set_return_code(vcpu, TDVMCALL_SUCCESS);
+	tdvmcall_set_return_val(vcpu, val);
+
+	return 1;
+}
+
+static int tdx_emulate_io(struct kvm_vcpu *vcpu)
+{
+	struct x86_emulate_ctxt *ctxt = vcpu->arch.emulate_ctxt;
+	unsigned long val = 0;
+	unsigned int port;
+	int size, ret;
+	bool write;
+
+	++vcpu->stat.io_exits;
+
+	size = tdvmcall_a0_read(vcpu);
+	write = tdvmcall_a1_read(vcpu);
+	port = tdvmcall_a2_read(vcpu);
+

arch/x86/kvm/vmx/tdx.c static int tdx_emulate_hlt(struct kvm_vcpu *vcpu) · line 1202

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +10 -0
+	if (size != 1 && size != 2 && size != 4) {
+		tdvmcall_set_return_code(vcpu, TDVMCALL_INVALID_OPERAND);
+		return 1;
+	}
+
+	if (write) {
+		val = tdvmcall_a3_read(vcpu);
+		ret = ctxt->ops->pio_out_emulated(ctxt, size, port, &val, 1);
+
+		/* No need for a complete_userspace_io callback. */

I am confused about the comment.

Quoted 7 lines
Quoted 3 lines
Quoted 2 lines

The code below sets the complete_userspace_io callback for write case, i.e. tdx_complete_pio_out().

You're correct. This comment is stale and should be removed it.

Also, since the tdx_complete_pio_out() is installed as complete_userspace_io callback for write, it's more reasonable to move the reset of pio.count into tdx_complete_pio_out().
How about the following fixup:

It makes sense. It matches better with other complete callbacks for tdx_complete_pio_out() to clear pio.count to 0.

Quoted 36 lines
arch/x86/kvm/vmx/tdx.c +5 -6
index 9ead46cb75ab..b43bb8ccddb9 100644
@@ -1115,6 +1115,7 @@ static int tdx_emulate_hlt(struct kvm_vcpu *vcpu)
 
  static int tdx_complete_pio_out(struct kvm_vcpu *vcpu)
  {
+       vcpu->arch.pio.count = 0;
         tdvmcall_set_return_code(vcpu, TDVMCALL_SUCCESS);
         tdvmcall_set_return_val(vcpu, 0);
         return 1;
@@ -1159,15 +1160,13 @@ static int tdx_emulate_io(struct kvm_vcpu *vcpu)
         if (write) {
                 val = tdvmcall_a3_read(vcpu);
                 ret = ctxt->ops->pio_out_emulated(ctxt, size, port, &val,
 1);
-
-               /* No need for a complete_userspace_io callback. */
-               vcpu->arch.pio.count = 0;
-       } else
+       } else {
                 ret = ctxt->ops->pio_in_emulated(ctxt, size, port, &val, 1);
+       }
 
-       if (ret)
+       if (ret) {
                 tdvmcall_set_return_val(vcpu, val);
-       else {
+       } else {
                 if (write)

                        vcpu->arch.complete_userspace_io = tdx_complete_pio_out;
                else

Signature · 1 line
Isaku Yamahata <[email protected]>
#617 lore raw

PATCH v19 110/130 KVM: TDX: Handle TDX PV MMIO hypercall

In reply to [email protected]

From: Sean Christopherson <[email protected]>

Export kvm_io_bus_read and kvm_mmio tracepoint and wire up TDX PV MMIO hypercall to the KVM backend functions.

kvm_io_bus_read/write() searches KVM device emulated in kernel of the given MMIO address and emulates the MMIO. As TDX PV MMIO also needs it, export kvm_io_bus_read(). kvm_io_bus_write() is already exported. TDX PV MMIO emulates some of MMIO itself. To add trace point consistently with x86 kvm, export kvm_mmio tracepoint.

---
 arch/x86/kvm/vmx/tdx.c | 114 +++++++++++++++++++++++++++++++++++++++++
 arch/x86/kvm/x86.c     |   1 +
 virt/kvm/kvm_main.c    |   2 +
 3 files changed, 117 insertions(+)
arch/x86/kvm/vmx/tdx.c +114 -0
index 55fc6cc6c816..389bb95d2af0 100644
@@ -1217,6 +1217,118 @@ static int tdx_emulate_io(struct kvm_vcpu *vcpu)
 	return ret;
 }
 
+static int tdx_complete_mmio(struct kvm_vcpu *vcpu)
+{
+	unsigned long val = 0;
+	gpa_t gpa;
+	int size;
+
+	KVM_BUG_ON(vcpu->mmio_needed != 1, vcpu->kvm);
+	vcpu->mmio_needed = 0;
+
+	if (!vcpu->mmio_is_write) {
+		gpa = vcpu->mmio_fragments[0].gpa;
+		size = vcpu->mmio_fragments[0].len;
+
+		memcpy(&val, vcpu->run->mmio.data, size);
+		tdvmcall_set_return_val(vcpu, val);
+		trace_kvm_mmio(KVM_TRACE_MMIO_READ, size, gpa, &val);
+	}
+	return 1;
+}
+
+static inline int tdx_mmio_write(struct kvm_vcpu *vcpu, gpa_t gpa, int size,
+				 unsigned long val)
+{
+	if (kvm_iodevice_write(vcpu, &vcpu->arch.apic->dev, gpa, size, &val) &&
+	    kvm_io_bus_write(vcpu, KVM_MMIO_BUS, gpa, size, &val))
+		return -EOPNOTSUPP;
+
+	trace_kvm_mmio(KVM_TRACE_MMIO_WRITE, size, gpa, &val);
+	return 0;
+}
+
+static inline int tdx_mmio_read(struct kvm_vcpu *vcpu, gpa_t gpa, int size)
+{
+	unsigned long val;
+
+	if (kvm_iodevice_read(vcpu, &vcpu->arch.apic->dev, gpa, size, &val) &&
+	    kvm_io_bus_read(vcpu, KVM_MMIO_BUS, gpa, size, &val))
+		return -EOPNOTSUPP;
+
+	tdvmcall_set_return_val(vcpu, val);
+	trace_kvm_mmio(KVM_TRACE_MMIO_READ, size, gpa, &val);
+	return 0;
+}
+
+static int tdx_emulate_mmio(struct kvm_vcpu *vcpu)
+{
+	struct kvm_memory_slot *slot;
+	int size, write, r;
+	unsigned long val;
+	gpa_t gpa;
+
+	KVM_BUG_ON(vcpu->mmio_needed, vcpu->kvm);
+
+	size = tdvmcall_a0_read(vcpu);
+	write = tdvmcall_a1_read(vcpu);
+	gpa = tdvmcall_a2_read(vcpu);
+	val = write ? tdvmcall_a3_read(vcpu) : 0;
+
+	if (size != 1 && size != 2 && size != 4 && size != 8)
+		goto error;
+	if (write != 0 && write != 1)
+		goto error;
+
+	/* Strip the shared bit, allow MMIO with and without it set. */
+	gpa = gpa & ~gfn_to_gpa(kvm_gfn_shared_mask(vcpu->kvm));
+
+	if (size > 8u || ((gpa + size - 1) ^ gpa) & PAGE_MASK)
+		goto error;
+
+	slot = kvm_vcpu_gfn_to_memslot(vcpu, gpa_to_gfn(gpa));
+	if (slot && !(slot->flags & KVM_MEMSLOT_INVALID))
+		goto error;
+
+	if (!kvm_io_bus_write(vcpu, KVM_FAST_MMIO_BUS, gpa, 0, NULL)) {
+		trace_kvm_fast_mmio(gpa);
+		return 1;
+	}
+
+	if (write)
+		r = tdx_mmio_write(vcpu, gpa, size, val);
+	else
+		r = tdx_mmio_read(vcpu, gpa, size);
+	if (!r) {
+		/* Kernel completed device emulation. */
+		tdvmcall_set_return_code(vcpu, TDVMCALL_SUCCESS);
+		return 1;
+	}
+
+	/* Request the device emulation to userspace device model. */
+	vcpu->mmio_needed = 1;
+	vcpu->mmio_is_write = write;
+	vcpu->arch.complete_userspace_io = tdx_complete_mmio;
+
+	vcpu->run->mmio.phys_addr = gpa;
+	vcpu->run->mmio.len = size;
+	vcpu->run->mmio.is_write = write;
+	vcpu->run->exit_reason = KVM_EXIT_MMIO;
+
+	if (write) {
+		memcpy(vcpu->run->mmio.data, &val, size);
+	} else {
+		vcpu->mmio_fragments[0].gpa = gpa;
+		vcpu->mmio_fragments[0].len = size;
+		trace_kvm_mmio(KVM_TRACE_MMIO_READ_UNSATISFIED, size, gpa, NULL);
+	}
+	return 0;
+
+error:
+	tdvmcall_set_return_code(vcpu, TDVMCALL_INVALID_OPERAND);
+	return 1;
+}
+
 static int handle_tdvmcall(struct kvm_vcpu *vcpu)
 {
 	if (tdvmcall_exit_type(vcpu))
@@ -1229,6 +1341,8 @@ static int handle_tdvmcall(struct kvm_vcpu *vcpu)
 		return tdx_emulate_hlt(vcpu);
 	case EXIT_REASON_IO_INSTRUCTION:
 		return tdx_emulate_io(vcpu);
+	case EXIT_REASON_EPT_VIOLATION:
+		return tdx_emulate_mmio(vcpu);
 	default:
 		break;
 	}
arch/x86/kvm/x86.c +1 -0
index 03950368d8db..d5b18cad9dcd 100644
@@ -13975,6 +13975,7 @@ EXPORT_SYMBOL_GPL(kvm_sev_es_string_io);
 
 EXPORT_TRACEPOINT_SYMBOL_GPL(kvm_entry);
 EXPORT_TRACEPOINT_SYMBOL_GPL(kvm_exit);
+EXPORT_TRACEPOINT_SYMBOL_GPL(kvm_mmio);
 EXPORT_TRACEPOINT_SYMBOL_GPL(kvm_fast_mmio);
 EXPORT_TRACEPOINT_SYMBOL_GPL(kvm_inj_virq);
 EXPORT_TRACEPOINT_SYMBOL_GPL(kvm_page_fault);
virt/kvm/kvm_main.c +2 -0
index e27c22449d85..bc14e1f2610c 100644
@@ -2689,6 +2689,7 @@ struct kvm_memory_slot *kvm_vcpu_gfn_to_memslot(struct kvm_vcpu *vcpu, gfn_t gfn
 
 	return NULL;
 }
+EXPORT_SYMBOL_GPL(kvm_vcpu_gfn_to_memslot);
 
 bool kvm_is_visible_gfn(struct kvm *kvm, gfn_t gfn)
 {
@@ -5992,6 +5993,7 @@ int kvm_io_bus_read(struct kvm_vcpu *vcpu, enum kvm_bus bus_idx, gpa_t addr,
 	r = __kvm_io_bus_read(vcpu, bus, &range, val);
 	return r < 0 ? r : 0;
 }
+EXPORT_SYMBOL_GPL(kvm_io_bus_read);
 
 int kvm_io_bus_register_dev(struct kvm *kvm, enum kvm_bus bus_idx, gpa_t addr,
 			    int len, struct kvm_io_device *dev)
Signature · 1 line
2.25.1
#618 lore raw

Re: [PATCH v19 110/130] KVM: TDX: Handle TDX PV MMIO hypercall

Quoted 82 lines · On 2/26/2024 4:26 PM, [email protected] wrote:

From: Sean Christopherson <[email protected]>

Export kvm_io_bus_read and kvm_mmio tracepoint and wire up TDX PV MMIO hypercall to the KVM backend functions.

kvm_io_bus_read/write() searches KVM device emulated in kernel of the given MMIO address and emulates the MMIO. As TDX PV MMIO also needs it, export kvm_io_bus_read(). kvm_io_bus_write() is already exported. TDX PV MMIO emulates some of MMIO itself. To add trace point consistently with x86 kvm, export kvm_mmio tracepoint.

---
  arch/x86/kvm/vmx/tdx.c | 114 +++++++++++++++++++++++++++++++++++++++++
  arch/x86/kvm/x86.c     |   1 +
  virt/kvm/kvm_main.c    |   2 +
  3 files changed, 117 insertions(+)
arch/x86/kvm/vmx/tdx.c +54 -0
index 55fc6cc6c816..389bb95d2af0 100644
@@ -1217,6 +1217,118 @@ static int tdx_emulate_io(struct kvm_vcpu *vcpu)
  	return ret;
  }
 
+static int tdx_complete_mmio(struct kvm_vcpu *vcpu)
+{
+	unsigned long val = 0;
+	gpa_t gpa;
+	int size;
+
+	KVM_BUG_ON(vcpu->mmio_needed != 1, vcpu->kvm);
+	vcpu->mmio_needed = 0;
+
+	if (!vcpu->mmio_is_write) {
+		gpa = vcpu->mmio_fragments[0].gpa;
+		size = vcpu->mmio_fragments[0].len;
+
+		memcpy(&val, vcpu->run->mmio.data, size);
+		tdvmcall_set_return_val(vcpu, val);
+		trace_kvm_mmio(KVM_TRACE_MMIO_READ, size, gpa, &val);
+	}
+	return 1;
+}
+
+static inline int tdx_mmio_write(struct kvm_vcpu *vcpu, gpa_t gpa, int size,
+				 unsigned long val)
+{
+	if (kvm_iodevice_write(vcpu, &vcpu->arch.apic->dev, gpa, size, &val) &&
+	    kvm_io_bus_write(vcpu, KVM_MMIO_BUS, gpa, size, &val))
+		return -EOPNOTSUPP;
+
+	trace_kvm_mmio(KVM_TRACE_MMIO_WRITE, size, gpa, &val);
+	return 0;
+}
+
+static inline int tdx_mmio_read(struct kvm_vcpu *vcpu, gpa_t gpa, int size)
+{
+	unsigned long val;
+
+	if (kvm_iodevice_read(vcpu, &vcpu->arch.apic->dev, gpa, size, &val) &&
+	    kvm_io_bus_read(vcpu, KVM_MMIO_BUS, gpa, size, &val))
+		return -EOPNOTSUPP;
+
+	tdvmcall_set_return_val(vcpu, val);
+	trace_kvm_mmio(KVM_TRACE_MMIO_READ, size, gpa, &val);
+	return 0;
+}
+
+static int tdx_emulate_mmio(struct kvm_vcpu *vcpu)
+{
+	struct kvm_memory_slot *slot;
+	int size, write, r;
+	unsigned long val;
+	gpa_t gpa;
+
+	KVM_BUG_ON(vcpu->mmio_needed, vcpu->kvm);
+
+	size = tdvmcall_a0_read(vcpu);

arch/x86/kvm/vmx/tdx.c static int tdx_emulate_io(struct kvm_vcpu *vcpu) · line 1283

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +10 -0
+	write = tdvmcall_a1_read(vcpu);
+	gpa = tdvmcall_a2_read(vcpu);
+	val = write ? tdvmcall_a3_read(vcpu) : 0;
+
+	if (size != 1 && size != 2 && size != 4 && size != 8)
+		goto error;
+	if (write != 0 && write != 1)
+		goto error;
+
+	/* Strip the shared bit, allow MMIO with and without it set. */

Based on the discussion https://lore.kernel.org/all/[email protected]/
Do we still allow the MMIO without shared bit?

arch/x86/kvm/vmx/tdx.c static int tdx_emulate_io(struct kvm_vcpu *vcpu) · line 1286

Quoted 3 lines
arch/x86/kvm/vmx/tdx.c +3 -0
+	gpa = gpa & ~gfn_to_gpa(kvm_gfn_shared_mask(vcpu->kvm));
+
+	if (size > 8u || ((gpa + size - 1) ^ gpa) & PAGE_MASK)

"size > 8u" can be removed, since based on the check of size above, it can't be greater than 8.

arch/x86/kvm/vmx/tdx.c static int tdx_emulate_io(struct kvm_vcpu *vcpu) · line 1293

Quoted 7 lines
arch/x86/kvm/vmx/tdx.c +7 -0
+		goto error;
+
+	slot = kvm_vcpu_gfn_to_memslot(vcpu, gpa_to_gfn(gpa));
+	if (slot && !(slot->flags & KVM_MEMSLOT_INVALID))
+		goto error;
+
+	if (!kvm_io_bus_write(vcpu, KVM_FAST_MMIO_BUS, gpa, 0, NULL)) {

Should this be checked for write first?

I check the handle_ept_misconfig() in VMX, it doesn't check write first neither.

Functionally, it should be OK since guest will not read the address range of fast mmio. So the read case will be filtered out by ioeventfd_write().
But it has take a long way to get to ioeventfd_write().
Isn't it more efficient to check write first?

Quoted 82 lines
arch/x86/kvm/vmx/tdx.c +40 -0
+		trace_kvm_fast_mmio(gpa);
+		return 1;
+	}
+
+	if (write)
+		r = tdx_mmio_write(vcpu, gpa, size, val);
+	else
+		r = tdx_mmio_read(vcpu, gpa, size);
+	if (!r) {
+		/* Kernel completed device emulation. */
+		tdvmcall_set_return_code(vcpu, TDVMCALL_SUCCESS);
+		return 1;
+	}
+
+	/* Request the device emulation to userspace device model. */
+	vcpu->mmio_needed = 1;
+	vcpu->mmio_is_write = write;
+	vcpu->arch.complete_userspace_io = tdx_complete_mmio;
+
+	vcpu->run->mmio.phys_addr = gpa;
+	vcpu->run->mmio.len = size;
+	vcpu->run->mmio.is_write = write;
+	vcpu->run->exit_reason = KVM_EXIT_MMIO;
+
+	if (write) {
+		memcpy(vcpu->run->mmio.data, &val, size);
+	} else {
+		vcpu->mmio_fragments[0].gpa = gpa;
+		vcpu->mmio_fragments[0].len = size;
+		trace_kvm_mmio(KVM_TRACE_MMIO_READ_UNSATISFIED, size, gpa, NULL);
+	}
+	return 0;
+
+error:
+	tdvmcall_set_return_code(vcpu, TDVMCALL_INVALID_OPERAND);
+	return 1;
+}
+
  static int handle_tdvmcall(struct kvm_vcpu *vcpu)
  {
  	if (tdvmcall_exit_type(vcpu))
@@ -1229,6 +1341,8 @@ static int handle_tdvmcall(struct kvm_vcpu *vcpu)
  		return tdx_emulate_hlt(vcpu);
  	case EXIT_REASON_IO_INSTRUCTION:
  		return tdx_emulate_io(vcpu);
+	case EXIT_REASON_EPT_VIOLATION:
+		return tdx_emulate_mmio(vcpu);
  	default:
  		break;
  	}
arch/x86/kvm/x86.c +1 -0
index 03950368d8db..d5b18cad9dcd 100644
@@ -13975,6 +13975,7 @@ EXPORT_SYMBOL_GPL(kvm_sev_es_string_io);
 
  EXPORT_TRACEPOINT_SYMBOL_GPL(kvm_entry);
  EXPORT_TRACEPOINT_SYMBOL_GPL(kvm_exit);
+EXPORT_TRACEPOINT_SYMBOL_GPL(kvm_mmio);
  EXPORT_TRACEPOINT_SYMBOL_GPL(kvm_fast_mmio);
  EXPORT_TRACEPOINT_SYMBOL_GPL(kvm_inj_virq);
  EXPORT_TRACEPOINT_SYMBOL_GPL(kvm_page_fault);
virt/kvm/kvm_main.c +2 -0
index e27c22449d85..bc14e1f2610c 100644
@@ -2689,6 +2689,7 @@ struct kvm_memory_slot *kvm_vcpu_gfn_to_memslot(struct kvm_vcpu *vcpu, gfn_t gfn
 
  	return NULL;
  }
+EXPORT_SYMBOL_GPL(kvm_vcpu_gfn_to_memslot);
 
  bool kvm_is_visible_gfn(struct kvm *kvm, gfn_t gfn)
  {
@@ -5992,6 +5993,7 @@ int kvm_io_bus_read(struct kvm_vcpu *vcpu, enum kvm_bus bus_idx, gpa_t addr,
  	r = __kvm_io_bus_read(vcpu, bus, &range, val);
  	return r < 0 ? r : 0;
  }
+EXPORT_SYMBOL_GPL(kvm_io_bus_read);
 
  int kvm_io_bus_register_dev(struct kvm *kvm, enum kvm_bus bus_idx, gpa_t addr,
  			    int len, struct kvm_io_device *dev)
#619 lore raw

Re: [PATCH v19 110/130] KVM: TDX: Handle TDX PV MMIO hypercall

Quoted 40 lines · Binbin Wu
Quoted 12 lines
arch/x86/kvm/vmx/tdx.c +11 -0
+
+static int tdx_emulate_mmio(struct kvm_vcpu *vcpu)
+{
+    struct kvm_memory_slot *slot;
+    int size, write, r;
+    unsigned long val;
+    gpa_t gpa;
+
+    KVM_BUG_ON(vcpu->mmio_needed, vcpu->kvm);
+
+    size = tdvmcall_a0_read(vcpu);

arch/x86/kvm/vmx/tdx.c static int tdx_emulate_io(struct kvm_vcpu *vcpu) · line 1283

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +10 -0
+    write = tdvmcall_a1_read(vcpu);
+    gpa = tdvmcall_a2_read(vcpu);
+    val = write ? tdvmcall_a3_read(vcpu) : 0;
+
+    if (size != 1 && size != 2 && size != 4 && size != 8)
+        goto error;
+    if (write != 0 && write != 1)
+        goto error;
+
+    /* Strip the shared bit, allow MMIO with and without it set. */

Based on the discussion https://lore.kernel.org/all/[email protected]/
Do we still allow the MMIO without shared bit?

Quoted 3 lines

+    gpa = gpa & ~gfn_to_gpa(kvm_gfn_shared_mask(vcpu->kvm)); +
+    if (size > 8u || ((gpa + size - 1) ^ gpa) & PAGE_MASK)

"size > 8u" can be removed, since based on the check of size above, it can't be greater than 8.

arch/x86/kvm/vmx/tdx.c static int tdx_emulate_io(struct kvm_vcpu *vcpu) · line 1293

Quoted 7 lines

+        goto error;

arch/x86/kvm/vmx/tdx.c +6 -0
+
+    slot = kvm_vcpu_gfn_to_memslot(vcpu, gpa_to_gfn(gpa));
+    if (slot && !(slot->flags & KVM_MEMSLOT_INVALID))
+        goto error;
+
+    if (!kvm_io_bus_write(vcpu, KVM_FAST_MMIO_BUS, gpa, 0, NULL)) {

Should this be checked for write first?

Quoted 6 lines

I check the handle_ept_misconfig() in VMX, it doesn't check write first neither.

Functionally, it should be OK since guest will not read the address range of fast mmio. So the read case will be filtered out by ioeventfd_write().
But it has take a long way to get to ioeventfd_write().
Isn't it more efficient to check write first?

I got the reason why in handle_ept_misconfig(), it tries to do fast mmio write without checking. It was intended to make fast mmio faster.
And for ept misconfig case, it's not easy to get the info of read/write.

But in this patch, we have already have read/write info, so maybe we can add the check for write before fast mmio?

Quoted 85 lines
Quoted 83 lines

+        trace_kvm_fast_mmio(gpa);
+        return 1;
+    }

arch/x86/kvm/vmx/tdx.c +27 -0
+
+    if (write)
+        r = tdx_mmio_write(vcpu, gpa, size, val);
+    else
+        r = tdx_mmio_read(vcpu, gpa, size);
+    if (!r) {
+        /* Kernel completed device emulation. */
+        tdvmcall_set_return_code(vcpu, TDVMCALL_SUCCESS);
+        return 1;
+    }
+
+    /* Request the device emulation to userspace device model. */
+    vcpu->mmio_needed = 1;
+    vcpu->mmio_is_write = write;
+    vcpu->arch.complete_userspace_io = tdx_complete_mmio;
+
+    vcpu->run->mmio.phys_addr = gpa;
+    vcpu->run->mmio.len = size;
+    vcpu->run->mmio.is_write = write;
+    vcpu->run->exit_reason = KVM_EXIT_MMIO;
+
+    if (write) {
+        memcpy(vcpu->run->mmio.data, &val, size);
+    } else {
+        vcpu->mmio_fragments[0].gpa = gpa;
+        vcpu->mmio_fragments[0].len = size;
+        trace_kvm_mmio(KVM_TRACE_MMIO_READ_UNSATISFIED, size, gpa, 

NULL);
+    }
+    return 0;

arch/x86/kvm/vmx/tdx.c +6 -0
+
+error:
+    tdvmcall_set_return_code(vcpu, TDVMCALL_INVALID_OPERAND);
+    return 1;
+}
+

  static int handle_tdvmcall(struct kvm_vcpu *vcpu) {
      if (tdvmcall_exit_type(vcpu))

arch/x86/kvm/vmx/tdx.c +0 -0
@@ -1229,6 +1341,8 @@ static int handle_tdvmcall(struct kvm_vcpu *vcpu)
           return tdx_emulate_hlt(vcpu);

      case EXIT_REASON_IO_INSTRUCTION:
          return tdx_emulate_io(vcpu);
+    case EXIT_REASON_EPT_VIOLATION:
+        return tdx_emulate_mmio(vcpu);
      default:
          break;
      }

arch/x86/kvm/x86.c +1 -0
index 03950368d8db..d5b18cad9dcd 100644
@@ -13975,6 +13975,7 @@ EXPORT_SYMBOL_GPL(kvm_sev_es_string_io);
     EXPORT_TRACEPOINT_SYMBOL_GPL(kvm_entry);
   EXPORT_TRACEPOINT_SYMBOL_GPL(kvm_exit);
+EXPORT_TRACEPOINT_SYMBOL_GPL(kvm_mmio);
   EXPORT_TRACEPOINT_SYMBOL_GPL(kvm_fast_mmio);
   EXPORT_TRACEPOINT_SYMBOL_GPL(kvm_inj_virq);
   EXPORT_TRACEPOINT_SYMBOL_GPL(kvm_page_fault);
virt/kvm/kvm_main.c +2 -0
index e27c22449d85..bc14e1f2610c 100644
@@ -2689,6 +2689,7 @@ struct kvm_memory_slot 
 *kvm_vcpu_gfn_to_memslot(struct kvm_vcpu *vcpu, gfn_t gfn
         return NULL;
   }
+EXPORT_SYMBOL_GPL(kvm_vcpu_gfn_to_memslot);
     bool kvm_is_visible_gfn(struct kvm *kvm, gfn_t gfn)
   {
@@ -5992,6 +5993,7 @@ int kvm_io_bus_read(struct kvm_vcpu *vcpu, enum 
 kvm_bus bus_idx, gpa_t addr,
       r = __kvm_io_bus_read(vcpu, bus, &range, val);
       return r < 0 ? r : 0;
   }
+EXPORT_SYMBOL_GPL(kvm_io_bus_read);
     int kvm_io_bus_register_dev(struct kvm *kvm, enum kvm_bus bus_idx, gpa_t addr,
                   int len, struct kvm_io_device *dev)
#620 lore raw

Re: [PATCH v19 110/130] KVM: TDX: Handle TDX PV MMIO hypercall

Quoted 20 lines · Binbin Wu
Quoted 17 lines · Binbin Wu
Quoted 17 lines
arch/x86/kvm/vmx/tdx.c +16 -0
+
+static int tdx_emulate_mmio(struct kvm_vcpu *vcpu)
+{
+    struct kvm_memory_slot *slot;
+    int size, write, r;
+    unsigned long val;
+    gpa_t gpa;
+
+    KVM_BUG_ON(vcpu->mmio_needed, vcpu->kvm);
+
+    size = tdvmcall_a0_read(vcpu);
+    write = tdvmcall_a1_read(vcpu);
+    gpa = tdvmcall_a2_read(vcpu);
+    val = write ? tdvmcall_a3_read(vcpu) : 0;
+
+    if (size != 1 && size != 2 && size != 4 && size != 8)
Quoted 8 lines
Quoted 8 lines
Quoted 5 lines

+        goto error;
+    if (write != 0 && write != 1)
+        goto error;
+
+    /* Strip the shared bit, allow MMIO with and without it set. */

Based on the discussion
https://lore.kernel.org/all/[email protected]/
Do we still allow the MMIO without shared bit?

That's independent. The part is how to work around guest accesses the MMIO region with private GPA. This part is, the guest issues TDG.VP.VMCALL<MMMIO> and KVM masks out the shared bit to make it friendly to the user space VMM.

Quoted 5 lines
Quoted 5 lines
Quoted 3 lines

+    gpa = gpa & ~gfn_to_gpa(kvm_gfn_shared_mask(vcpu->kvm)); +
+    if (size > 8u || ((gpa + size - 1) ^ gpa) & PAGE_MASK)

"size > 8u" can be removed, since based on the check of size above, it can't be greater than 8.

Yes, will remove the check.

Quoted 18 lines
Quoted 9 lines

arch/x86/kvm/vmx/tdx.c static int tdx_emulate_io(struct kvm_vcpu *vcpu) · line 1293

Quoted 7 lines

+        goto error;

arch/x86/kvm/vmx/tdx.c +6 -0
+
+    slot = kvm_vcpu_gfn_to_memslot(vcpu, gpa_to_gfn(gpa));
+    if (slot && !(slot->flags & KVM_MEMSLOT_INVALID))
+        goto error;
+
+    if (!kvm_io_bus_write(vcpu, KVM_FAST_MMIO_BUS, gpa, 0, NULL)) {

Should this be checked for write first?

Quoted 8 lines

I check the handle_ept_misconfig() in VMX, it doesn't check write first neither.

Functionally, it should be OK since guest will not read the address range of fast mmio.
So the read case will be filtered out by ioeventfd_write(). But it has take a long way to get to ioeventfd_write(). Isn't it more efficient to check write first?

Quoted 7 lines

I got the reason why in handle_ept_misconfig(), it tries to do fast mmio write without checking.
It was intended to make fast mmio faster.
And for ept misconfig case, it's not easy to get the info of read/write.

But in this patch, we have already have read/write info, so maybe we can add the check for write before fast mmio?

Yes, let's add it.

Signature · 1 line
Isaku Yamahata <[email protected]>
#621 lore raw

Re: [PATCH v19 110/130] KVM: TDX: Handle TDX PV MMIO hypercall

Quoted 29 lines · Isaku Yamahata
Quoted 18 lines · Binbin Wu
Quoted 16 lines · Binbin Wu
Quoted 16 lines
arch/x86/kvm/vmx/tdx.c +16 -0
+
+static int tdx_emulate_mmio(struct kvm_vcpu *vcpu)
+{
+    struct kvm_memory_slot *slot;
+    int size, write, r;
+    unsigned long val;
+    gpa_t gpa;
+
+    KVM_BUG_ON(vcpu->mmio_needed, vcpu->kvm);
+
+    size = tdvmcall_a0_read(vcpu);
+    write = tdvmcall_a1_read(vcpu);
+    gpa = tdvmcall_a2_read(vcpu);
+    val = write ? tdvmcall_a3_read(vcpu) : 0;
+
+    if (size != 1 && size != 2 && size != 4 && size != 8)
Quoted 8 lines
Quoted 8 lines
Quoted 5 lines

+        goto error;
+    if (write != 0 && write != 1)
+        goto error;
+
+    /* Strip the shared bit, allow MMIO with and without it set. */

Based on the discussion
https://lore.kernel.org/all/[email protected]/
Do we still allow the MMIO without shared bit?

Quoted 4 lines

That's independent. The part is how to work around guest accesses the MMIO region with private GPA. This part is, the guest issues TDG.VP.VMCALL<MMMIO> and KVM masks out the shared bit to make it friendly to the user space VMM.

It's similar.
The tdvmcall from the guest for mmio can also be private GPA, which is not reasonable, right? According to the comment, kvm doens't care about if the TD guest issue the tdvmcall with private GPA or shared GPA.

#622 lore raw

Re: [PATCH v19 110/130] KVM: TDX: Handle TDX PV MMIO hypercall

Quoted 36 lines · Binbin Wu
Quoted 29 lines · Isaku Yamahata
Quoted 18 lines · Binbin Wu
Quoted 16 lines · Binbin Wu
Quoted 16 lines
(quoted diff) +16 -0
+
+static int tdx_emulate_mmio(struct kvm_vcpu *vcpu)
+{
+    struct kvm_memory_slot *slot;
+    int size, write, r;
+    unsigned long val;
+    gpa_t gpa;
+
+    KVM_BUG_ON(vcpu->mmio_needed, vcpu->kvm);
+
+    size = tdvmcall_a0_read(vcpu);
+    write = tdvmcall_a1_read(vcpu);
+    gpa = tdvmcall_a2_read(vcpu);
+    val = write ? tdvmcall_a3_read(vcpu) : 0;
+
+    if (size != 1 && size != 2 && size != 4 && size != 8)
Quoted 8 lines
Quoted 8 lines
Quoted 5 lines

+        goto error;
+    if (write != 0 && write != 1)
+        goto error;
+
+    /* Strip the shared bit, allow MMIO with and without it set. */

Based on the discussion
https://lore.kernel.org/all/[email protected]/
Do we still allow the MMIO without shared bit?

Quoted 4 lines

That's independent. The part is how to work around guest accesses the MMIO region with private GPA. This part is, the guest issues TDG.VP.VMCALL<MMMIO> and KVM masks out the shared bit to make it friendly to the user space VMM.

Quoted 5 lines

It's similar.
The tdvmcall from the guest for mmio can also be private GPA, which is not reasonable, right?
According to the comment, kvm doens't care about if the TD guest issue the tdvmcall with private GPA or shared GPA.

I checked the GHCI spec. It clearly states this hypercall is for shared GPA. We should return error for private GPA.

This TDG.VP.VMCALL is used to help request the VMM perform emulated-MMIO-access operation. The VMM may emulate MMIO space in shared-GPA space. The VMM can induce a #VE on these shared-GPA accesses by mapping shared GPAs with the suppress-VE bit cleared in the EPT Entries corresponding to these mappings

So we'll have something as follows. Compile only tested.

arch/x86/kvm/vmx/tdx.c +19 -9
index 3bf0d6e3cd21..0f696f3fbd86 100644
@@ -1281,24 +1281,34 @@ static int tdx_emulate_mmio(struct kvm_vcpu *vcpu)
        if (write != 0 && write != 1)
                goto error;
 
-       /* Strip the shared bit, allow MMIO with and without it set. */
+       /*
+        * MMIO with TDG.VP.VMCALL<MMIO> allows only shared GPA because
+        * private GPA is for device assignment.
+        */
+       if (kvm_is_private_gpa(gpa))
+               goto error;
+
+       /*
+        * Strip the shared bit because device emulator is assigned to GPA
+        * without shared bit.  We'd like the existing code untouched.
+        */
        gpa = gpa & ~gfn_to_gpa(kvm_gfn_shared_mask(vcpu->kvm));
 
-       if (size > 8u || ((gpa + size - 1) ^ gpa) & PAGE_MASK)
+       /* Disallow MMIO crossing page boundary for simplicity. */
+       if (((gpa + size - 1) ^ gpa) & PAGE_MASK)
                goto error;
 
        slot = kvm_vcpu_gfn_to_memslot(vcpu, gpa_to_gfn(gpa));
        if (slot && !(slot->flags & KVM_MEMSLOT_INVALID))
                goto error;
 
-       if (!kvm_io_bus_write(vcpu, KVM_FAST_MMIO_BUS, gpa, 0, NULL)) {
-               trace_kvm_fast_mmio(gpa);
-               return 1;
-       }
-
-       if (write)
+       if (write) {
+               if (!kvm_io_bus_write(vcpu, KVM_FAST_MMIO_BUS, gpa, 0, NULL)) {
+                       trace_kvm_fast_mmio(gpa);
+                       return 1;
+               }
                r = tdx_mmio_write(vcpu, gpa, size, val);
-       else
+       } else
                r = tdx_mmio_read(vcpu, gpa, size);
        if (!r) {
                /* Kernel completed device emulation. */
Signature · 1 line
Isaku Yamahata <[email protected]>
#623 lore raw

Re: [PATCH v19 110/130] KVM: TDX: Handle TDX PV MMIO hypercall

In reply to [email protected]
Quoted 26 lines · On 2/26/2024 4:26 PM, [email protected] wrote:

From: Sean Christopherson <[email protected]>

Export kvm_io_bus_read and kvm_mmio tracepoint and wire up TDX PV MMIO hypercall to the KVM backend functions.

kvm_io_bus_read/write() searches KVM device emulated in kernel of the given MMIO address and emulates the MMIO. As TDX PV MMIO also needs it, export kvm_io_bus_read(). kvm_io_bus_write() is already exported. TDX PV MMIO emulates some of MMIO itself. To add trace point consistently with x86 kvm, export kvm_mmio tracepoint.

---
  arch/x86/kvm/vmx/tdx.c | 114 +++++++++++++++++++++++++++++++++++++++++
  arch/x86/kvm/x86.c     |   1 +
  virt/kvm/kvm_main.c    |   2 +
  3 files changed, 117 insertions(+)
arch/x86/kvm/vmx/tdx.c +0 -0
index 55fc6cc6c816..389bb95d2af0 100644
@@ -1217,6 +1217,118 @@ static int tdx_emulate_io(struct kvm_vcpu *vcpu)
  	return ret;

arch/x86/kvm/vmx/tdx.c static int tdx_emulate_io(struct kvm_vcpu *vcpu) · line 1227

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +8 -0
  }
 
+static int tdx_complete_mmio(struct kvm_vcpu *vcpu)
+{
+	unsigned long val = 0;
+	gpa_t gpa;
+	int size;
+
+	KVM_BUG_ON(vcpu->mmio_needed != 1, vcpu->kvm);
+	vcpu->mmio_needed = 0;

mmio_needed is used by instruction emulator to setup the complete callback. Since TDX handle MMIO in a PV way, mmio_needed is not needed here.

arch/x86/kvm/vmx/tdx.c static int tdx_emulate_io(struct kvm_vcpu *vcpu) · line 1229

Quoted 2 lines
arch/x86/kvm/vmx/tdx.c +2 -0
+
+	if (!vcpu->mmio_is_write) {

It's also needed by instruction emulator, we can use vcpu->run->mmio.is_write instead.

arch/x86/kvm/vmx/tdx.c static int tdx_emulate_io(struct kvm_vcpu *vcpu) · line 1231

Quoted 2 lines
arch/x86/kvm/vmx/tdx.c +2 -0
+		gpa = vcpu->mmio_fragments[0].gpa;
+		size = vcpu->mmio_fragments[0].len;

Since MMIO cross page boundary is not allowed according to the input checks from TDVMCALL, these mmio_fragments[] is not needed. Just use vcpu->run->mmio.phys_addr and vcpu->run->mmio.len?

arch/x86/kvm/vmx/tdx.c static int tdx_emulate_io(struct kvm_vcpu *vcpu) · line 1236

Quoted 5 lines
arch/x86/kvm/vmx/tdx.c +5 -0
+
+		memcpy(&val, vcpu->run->mmio.data, size);
+		tdvmcall_set_return_val(vcpu, val);
+		trace_kvm_mmio(KVM_TRACE_MMIO_READ, size, gpa, &val);
+	}

Tracepoint for KVM_TRACE_MMIO_WRITE is missing when it is handled in userspace.

Also, the return code is only set when the emulation is done in kernel, but not set when it's handled in userspace.

arch/x86/kvm/vmx/tdx.c static int tdx_emulate_io(struct kvm_vcpu *vcpu) · line 1238

Quoted 2 lines
arch/x86/kvm/vmx/tdx.c +2 -0
+	return 1;
+}

How about the fixup as following:

@@ -1173,19 +1173,18 @@ static int tdx_emulate_io(struct kvm_vcpu *vcpu) static int tdx_complete_mmio(struct kvm_vcpu *vcpu) { unsigned long val = 0; - gpa_t gpa; - int size; - - vcpu->mmio_needed = 0; - - if (!vcpu->mmio_is_write) { - gpa = vcpu->mmio_fragments[0].gpa; - size = vcpu->mmio_fragments[0].len; + gpa_t gpa = vcpu->run->mmio.phys_addr; + int size = vcpu->run->mmio.len; + if (vcpu->run->mmio.is_write) { + trace_kvm_mmio(KVM_TRACE_MMIO_WRITE, size, gpa, &val); + } else { memcpy(&val, vcpu->run->mmio.data, size); tdvmcall_set_return_val(vcpu, val); trace_kvm_mmio(KVM_TRACE_MMIO_READ, size, gpa, &val); } + + tdvmcall_set_return_code(vcpu, TDVMCALL_SUCCESS); return 1; }

Quoted 34 lines
arch/x86/kvm/vmx/tdx.c +34 -0
+
+static inline int tdx_mmio_write(struct kvm_vcpu *vcpu, gpa_t gpa, int size,
+				 unsigned long val)
+{
+	if (kvm_iodevice_write(vcpu, &vcpu->arch.apic->dev, gpa, size, &val) &&
+	    kvm_io_bus_write(vcpu, KVM_MMIO_BUS, gpa, size, &val))
+		return -EOPNOTSUPP;
+
+	trace_kvm_mmio(KVM_TRACE_MMIO_WRITE, size, gpa, &val);
+	return 0;
+}
+
+static inline int tdx_mmio_read(struct kvm_vcpu *vcpu, gpa_t gpa, int size)
+{
+	unsigned long val;
+
+	if (kvm_iodevice_read(vcpu, &vcpu->arch.apic->dev, gpa, size, &val) &&
+	    kvm_io_bus_read(vcpu, KVM_MMIO_BUS, gpa, size, &val))
+		return -EOPNOTSUPP;
+
+	tdvmcall_set_return_val(vcpu, val);
+	trace_kvm_mmio(KVM_TRACE_MMIO_READ, size, gpa, &val);
+	return 0;
+}
+
+static int tdx_emulate_mmio(struct kvm_vcpu *vcpu)
+{
+	struct kvm_memory_slot *slot;
+	int size, write, r;
+	unsigned long val;
+	gpa_t gpa;
+
+	KVM_BUG_ON(vcpu->mmio_needed, vcpu->kvm);
+

[...]

arch/x86/kvm/vmx/tdx.c static int tdx_emulate_io(struct kvm_vcpu *vcpu) · line 1310

Quoted 4 lines
arch/x86/kvm/vmx/tdx.c +4 -0
+
+	/* Request the device emulation to userspace device model. */
+	vcpu->mmio_needed = 1;
+	vcpu->mmio_is_write = write;

Then they can be dropped.

arch/x86/kvm/vmx/tdx.c static int tdx_emulate_io(struct kvm_vcpu *vcpu) · line 1322

Quoted 12 lines
arch/x86/kvm/vmx/tdx.c +12 -0
+	vcpu->arch.complete_userspace_io = tdx_complete_mmio;
+
+	vcpu->run->mmio.phys_addr = gpa;
+	vcpu->run->mmio.len = size;
+	vcpu->run->mmio.is_write = write;
+	vcpu->run->exit_reason = KVM_EXIT_MMIO;
+
+	if (write) {
+		memcpy(vcpu->run->mmio.data, &val, size);
+	} else {
+		vcpu->mmio_fragments[0].gpa = gpa;
+		vcpu->mmio_fragments[0].len = size;

These two lines can be dropped as well.

arch/x86/kvm/vmx/tdx.c static int tdx_emulate_io(struct kvm_vcpu *vcpu) · line 1331

Quoted 9 lines
arch/x86/kvm/vmx/tdx.c +9 -0
+		trace_kvm_mmio(KVM_TRACE_MMIO_READ_UNSATISFIED, size, gpa, NULL);
+	}
+	return 0;
+
+error:
+	tdvmcall_set_return_code(vcpu, TDVMCALL_INVALID_OPERAND);
+	return 1;
+}
+

- /* Request the device emulation to userspace device model. */ - vcpu->mmio_needed = 1; - vcpu->mmio_is_write = write; vcpu->arch.complete_userspace_io = tdx_complete_mmio; vcpu->run->mmio.phys_addr = gpa; @@ -1265,13 +1272,11 @@ static int tdx_emulate_mmio(struct kvm_vcpu *vcpu) vcpu->run->mmio.is_write = write; vcpu->run->exit_reason = KVM_EXIT_MMIO; - if (write) { + if (write) memcpy(vcpu->run->mmio.data, &val, size); - } else { - vcpu->mmio_fragments[0].gpa = gpa; - vcpu->mmio_fragments[0].len = size; + else trace_kvm_mmio(KVM_TRACE_MMIO_READ_UNSATISFIED, size, gpa, NULL); - } + return 0;

#624 lore raw

Re: [PATCH v19 110/130] KVM: TDX: Handle TDX PV MMIO hypercall

Quoted 67 lines · Binbin Wu
Quoted 25 lines · On 2/26/2024 4:26 PM, [email protected] wrote:

From: Sean Christopherson <[email protected]>

Export kvm_io_bus_read and kvm_mmio tracepoint and wire up TDX PV MMIO hypercall to the KVM backend functions.

kvm_io_bus_read/write() searches KVM device emulated in kernel of the given MMIO address and emulates the MMIO.  As TDX PV MMIO also needs it, export kvm_io_bus_read().  kvm_io_bus_write() is already exported.  TDX PV MMIO emulates some of MMIO itself.  To add trace point consistently with x86 kvm, export kvm_mmio tracepoint.

---
  arch/x86/kvm/vmx/tdx.c | 114 +++++++++++++++++++++++++++++++++++++++++ arch/x86/kvm/x86.c     |   1 +
  virt/kvm/kvm_main.c    |   2 +

  3 files changed, 117 insertions(+)
arch/x86/kvm/vmx/tdx.c +0 -0
index 55fc6cc6c816..389bb95d2af0 100644

arch/x86/kvm/vmx/tdx.c static int tdx_emulate_io(struct kvm_vcpu *vcpu) · line 1226

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +7 -0
@@ -1217,6 +1217,118 @@ static int tdx_emulate_io(struct kvm_vcpu *vcpu)
       return ret;
   }
   +static int tdx_complete_mmio(struct kvm_vcpu *vcpu)
+{
+    unsigned long val = 0;
+    gpa_t gpa;
+    int size;
+
+    KVM_BUG_ON(vcpu->mmio_needed != 1, vcpu->kvm);
+    vcpu->mmio_needed = 0;

mmio_needed is used by instruction emulator to setup the complete callback. Since TDX handle MMIO in a PV way, mmio_needed is not needed here.

arch/x86/kvm/vmx/tdx.c static int tdx_emulate_io(struct kvm_vcpu *vcpu) · line 1228

Quoted 2 lines
arch/x86/kvm/vmx/tdx.c +2 -0
+
+    if (!vcpu->mmio_is_write) {

It's also needed by instruction emulator, we can use vcpu->run->mmio.is_write instead.

arch/x86/kvm/vmx/tdx.c static int tdx_emulate_io(struct kvm_vcpu *vcpu) · line 1231

Quoted 2 lines
arch/x86/kvm/vmx/tdx.c +2 -0
+        gpa = vcpu->mmio_fragments[0].gpa;
+        size = vcpu->mmio_fragments[0].len;

Since MMIO cross page boundary is not allowed according to the input checks from TDVMCALL, these mmio_fragments[] is not needed. Just use vcpu->run->mmio.phys_addr and vcpu->run->mmio.len?

arch/x86/kvm/vmx/tdx.c static int tdx_emulate_io(struct kvm_vcpu *vcpu) · line 1236

Quoted 5 lines
arch/x86/kvm/vmx/tdx.c +5 -0
+
+        memcpy(&val, vcpu->run->mmio.data, size);
+        tdvmcall_set_return_val(vcpu, val);
+        trace_kvm_mmio(KVM_TRACE_MMIO_READ, size, gpa, &val);
+    }

Tracepoint for KVM_TRACE_MMIO_WRITE is missing when it is handled in userspace.

Also, the return code is only set when the emulation is done in kernel, but not set when it's handled in userspace.

arch/x86/kvm/vmx/tdx.c static int tdx_emulate_io(struct kvm_vcpu *vcpu) · line 1237

Quoted 2 lines
arch/x86/kvm/vmx/tdx.c +2 -0
+    return 1;
+}

How about the fixup as following:

Review static int tdx_emulate_io(struct kvm_vcpu · line 1174

Quoted 2 lines
(quoted diff) +0 -0
@@ -1173,19 +1173,18 @@ static int tdx_emulate_io(struct kvm_vcpu 
 *vcpu) static int tdx_complete_mmio(struct kvm_vcpu *vcpu) { unsigned long val = 0; - gpa_t gpa; - int size; - - vcpu->mmio_needed = 0; - - if (!vcpu->mmio_is_write) { - gpa = vcpu->mmio_fragments[0].gpa; - size = vcpu->mmio_fragments[0].len; + gpa_t gpa = vcpu->run->mmio.phys_addr; + int size = vcpu->run->mmio.len; + if (vcpu->run->mmio.is_write) { + trace_kvm_mmio(KVM_TRACE_MMIO_WRITE, size, gpa, &val); + } else { memcpy(&val, vcpu->run->mmio.data, size); tdvmcall_set_return_val(vcpu, val); trace_kvm_mmio(KVM_TRACE_MMIO_READ, size, gpa, &val); } + + tdvmcall_set_return_code(vcpu, TDVMCALL_SUCCESS); return 1; }
 

Sorry for the mess.

@@ -1173,19 +1173,18 @@ static int tdx_emulate_io(struct kvm_vcpu *vcpu) static int tdx_complete_mmio(struct kvm_vcpu *vcpu)
 {
        unsigned long val = 0;
-       gpa_t gpa;
-       int size;
-
-       vcpu->mmio_needed = 0;
-
-       if (!vcpu->mmio_is_write) {
-               gpa = vcpu->mmio_fragments[0].gpa;
-               size = vcpu->mmio_fragments[0].len;
+       gpa_t gpa = vcpu->run->mmio.phys_addr;
+       int size = vcpu->run->mmio.len;

+       if (vcpu->run->mmio.is_write) {
+               trace_kvm_mmio(KVM_TRACE_MMIO_WRITE, size, gpa, &val); +       } else {
                memcpy(&val, vcpu->run->mmio.data, size); tdvmcall_set_return_val(vcpu, val); trace_kvm_mmio(KVM_TRACE_MMIO_READ, size, gpa, &val); }
+
+       tdvmcall_set_return_code(vcpu, TDVMCALL_SUCCESS); return 1;
 }

Quoted 62 lines
Quoted 40 lines
arch/x86/kvm/vmx/tdx.c +5 -0
+
+static inline int tdx_mmio_write(struct kvm_vcpu *vcpu, gpa_t gpa, 
 int size,
+                 unsigned long val)
+{
+    if (kvm_iodevice_write(vcpu, &vcpu->arch.apic->dev, gpa, size, 

&val) &&
+        kvm_io_bus_write(vcpu, KVM_MMIO_BUS, gpa, size, &val)) +        return -EOPNOTSUPP;

arch/x86/kvm/vmx/tdx.c +6 -0
+
+    trace_kvm_mmio(KVM_TRACE_MMIO_WRITE, size, gpa, &val);
+    return 0;
+}
+
+static inline int tdx_mmio_read(struct kvm_vcpu *vcpu, gpa_t gpa, 

int size)

arch/x86/kvm/vmx/tdx.c +4 -0
+{
+    unsigned long val;
+
+    if (kvm_iodevice_read(vcpu, &vcpu->arch.apic->dev, gpa, size, 

&val) &&
+        kvm_io_bus_read(vcpu, KVM_MMIO_BUS, gpa, size, &val)) +        return -EOPNOTSUPP;

arch/x86/kvm/vmx/tdx.c +15 -0
+
+    tdvmcall_set_return_val(vcpu, val);
+    trace_kvm_mmio(KVM_TRACE_MMIO_READ, size, gpa, &val);
+    return 0;
+}
+
+static int tdx_emulate_mmio(struct kvm_vcpu *vcpu)
+{
+    struct kvm_memory_slot *slot;
+    int size, write, r;
+    unsigned long val;
+    gpa_t gpa;
+
+    KVM_BUG_ON(vcpu->mmio_needed, vcpu->kvm);
+

[...]

Quoted 4 lines

+
+    /* Request the device emulation to userspace device model. */ +    vcpu->mmio_needed = 1;
+    vcpu->mmio_is_write = write;

Then they can be dropped.

arch/x86/kvm/vmx/tdx.c static int tdx_emulate_io(struct kvm_vcpu *vcpu) · line 1322

Quoted 12 lines

+    vcpu->arch.complete_userspace_io = tdx_complete_mmio;

arch/x86/kvm/vmx/tdx.c +11 -0
+
+    vcpu->run->mmio.phys_addr = gpa;
+    vcpu->run->mmio.len = size;
+    vcpu->run->mmio.is_write = write;
+    vcpu->run->exit_reason = KVM_EXIT_MMIO;
+
+    if (write) {
+        memcpy(vcpu->run->mmio.data, &val, size);
+    } else {
+        vcpu->mmio_fragments[0].gpa = gpa;
+        vcpu->mmio_fragments[0].len = size;

These two lines can be dropped as well.

Quoted 15 lines

arch/x86/kvm/vmx/tdx.c static int tdx_emulate_io(struct kvm_vcpu *vcpu) · line 1331

Quoted 9 lines

+ trace_kvm_mmio(KVM_TRACE_MMIO_READ_UNSATISFIED, size, gpa, NULL); +    }
+    return 0;

arch/x86/kvm/vmx/tdx.c +6 -0
+
+error:
+    tdvmcall_set_return_code(vcpu, TDVMCALL_INVALID_OPERAND);
+    return 1;
+}
+

- /* Request the device emulation to userspace device model. */ -
vcpu->mmio_needed = 1; - vcpu->mmio_is_write = write; vcpu->arch.complete_userspace_io = tdx_complete_mmio; vcpu->run->mmio.phys_addr = gpa; @@ -1265,13 +1272,11 @@ static int tdx_emulate_mmio(struct kvm_vcpu *vcpu) vcpu->run->mmio.is_write = write; vcpu->run->exit_reason = KVM_EXIT_MMIO; - if (write) { + if (write) memcpy(vcpu->run->mmio.data, &val, size); - } else { - vcpu->mmio_fragments[0].gpa = gpa; - vcpu->mmio_fragments[0].len = size; + else trace_kvm_mmio(KVM_TRACE_MMIO_READ_UNSATISFIED, size, gpa, NULL); - } + return 0;

-       /* Request the device emulation to userspace device model. */ -       vcpu->mmio_needed = 1;
-       vcpu->mmio_is_write = write; vcpu->arch.complete_userspace_io = tdx_complete_mmio;

        vcpu->run->mmio.phys_addr = gpa;
@@ -1265,13 +1272,11 @@ static int tdx_emulate_mmio(struct kvm_vcpu *vcpu) vcpu->run->mmio.is_write = write; vcpu->run->exit_reason = KVM_EXIT_MMIO;

-       if (write) {
+       if (write)
                memcpy(vcpu->run->mmio.data, &val, size);
-       } else {
-               vcpu->mmio_fragments[0].gpa = gpa;
-               vcpu->mmio_fragments[0].len = size;
+       else
                trace_kvm_mmio(KVM_TRACE_MMIO_READ_UNSATISFIED, size, gpa, NULL); -       }
+
        return 0;

#625 lore raw

Re: [PATCH v19 110/130] KVM: TDX: Handle TDX PV MMIO hypercall

In reply to Binbin Wu
Quoted 6 lines · Binbin Wu
Quoted 2 lines

+               gpa = vcpu->mmio_fragments[0].gpa; +               size = vcpu->mmio_fragments[0].len;

Since MMIO cross page boundary is not allowed according to the input checks from TDVMCALL, these mmio_fragments[] is not needed. Just use vcpu->run->mmio.phys_addr and vcpu->run->mmio.len?

Can we add a comment or something to that check, on why KVM doesn't handle it? Is it documented somewhere in the TDX ABI that it is not expected to be supported?

#626 lore raw

Re: [PATCH v19 110/130] KVM: TDX: Handle TDX PV MMIO hypercall

Quoted 9 lines · Rick P
Quoted 5 lines · Binbin Wu
Quoted 2 lines

+               gpa = vcpu->mmio_fragments[0].gpa; +               size = vcpu->mmio_fragments[0].len;

Since MMIO cross page boundary is not allowed according to the input checks from TDVMCALL, these mmio_fragments[] is not needed. Just use vcpu->run->mmio.phys_addr and vcpu->run->mmio.len?

Can we add a comment or something to that check, on why KVM doesn't handle it? Is it documented somewhere in the TDX ABI that it is not expected to be supported?

TDX GHCI doesn't have such restriction.

According to the reply from Isaku in the below link, I think current restriction is due to software implementation for simplicity.
https://lore.kernel.org/kvm/[email protected]/
+       /* Disallow MMIO crossing page boundary for simplicity. */
+       if (((gpa + size - 1) ^ gpa) & PAGE_MASK)
                goto error;

According to https://lore.kernel.org/all/165550567214.4207.3700499203810719676.tip-bot2@tip-bot2/,
for Linux as TDX guest, it rejects EPT violation #VEs that split pages based on the reason "MMIO accesses are supposed to be naturally aligned and therefore never cross page boundaries" to handle the load_unaligned_zeropad() case.

I am not sure "MMIO accesses are supposed to be naturally aligned" is true for all other OS as TDX guest, though.

Any suggestion?

#627 lore raw

Re: [PATCH v19 110/130] KVM: TDX: Handle TDX PV MMIO hypercall

Quoted 21 lines · Binbin Wu
Quoted 9 lines · Rick P
Quoted 5 lines · Binbin Wu
Quoted 2 lines

+               gpa = vcpu->mmio_fragments[0].gpa; +               size = vcpu->mmio_fragments[0].len;

Since MMIO cross page boundary is not allowed according to the input checks from TDVMCALL, these mmio_fragments[] is not needed. Just use vcpu->run->mmio.phys_addr and vcpu->run->mmio.len?

Can we add a comment or something to that check, on why KVM doesn't handle it? Is it documented somewhere in the TDX ABI that it is not expected to be supported?

TDX GHCI doesn't have such restriction.

According to the reply from Isaku in the below link, I think current restriction is due to software implementation for simplicity.
https://lore.kernel.org/kvm/[email protected]/

+       /* Disallow MMIO crossing page boundary for simplicity. */ +       if (((gpa + size - 1) ^ gpa) & PAGE_MASK)
                goto error;

Quoted 8 lines

According to https://lore.kernel.org/all/165550567214.4207.3700499203810719676.tip-bot2@tip-bot2/,
for Linux as TDX guest, it rejects EPT violation #VEs that split pages based on the reason "MMIO accesses are supposed to be naturally aligned and therefore never cross page boundaries" to handle the load_unaligned_zeropad() case.

I am not sure "MMIO accesses are supposed to be naturally aligned" is true for all other OS as TDX guest, though.

Any suggestion?

Had some discussion with Gao, Chao.

For TDX PV MMIO hypercall, it has got the GPA already.
I.e, we don't need to worry about case of "contiguous in virtual memory, but not be contiguous in physical memory".

Also, the size of the PV MMIO access is limited to 1, 2, 4, 8 bytes. No need to be split.

So, for TDX, there is no need to use vcpu->mmio_fragments[] even if the MMIO access crosses page boundary. The check for "Disallow MMIO crossing page boundary" can be removed and no extra fragments handling needed.

#628 lore raw

Re: [PATCH v19 110/130] KVM: TDX: Handle TDX PV MMIO hypercall

Quoted 34 lines · Binbin Wu
Quoted 31 lines · Binbin Wu
Quoted 10 lines · Rick P
Quoted 5 lines · Binbin Wu
Quoted 2 lines

+               gpa = vcpu->mmio_fragments[0].gpa; +               size = vcpu->mmio_fragments[0].len;

Since MMIO cross page boundary is not allowed according to the input checks from TDVMCALL, these mmio_fragments[] is not needed. Just use vcpu->run->mmio.phys_addr and vcpu->run->mmio.len?

Can we add a comment or something to that check, on why KVM doesn't handle it?
Is it documented somewhere in the TDX ABI that it is not expected to be supported?

TDX GHCI doesn't have such restriction.

According to the reply from Isaku in the below link, I think current restriction is due to software implementation for simplicity.
https://lore.kernel.org/kvm/[email protected]/

+       /* Disallow MMIO crossing page boundary for simplicity. */ +       if (((gpa + size - 1) ^ gpa) & PAGE_MASK)
                goto error;

According to
https://lore.kernel.org/all/165550567214.4207.3700499203810719676.tip-bot2@tip-bot2/
,
for Linux as TDX guest, it rejects EPT violation #VEs that split pages based on the reason "MMIO accesses are supposed to be naturally aligned and therefore never cross page boundaries" to handle the load_unaligned_zeropad() case.

Quoted 4 lines
Quoted 4 lines

I am not sure "MMIO accesses are supposed to be naturally aligned" is true for all other OS as TDX guest, though.

Any suggestion?

It doesn't seem likely a guest will rely on such MMIO to cause an error, so should be safe to wait until its needed. Let's leave a comment that it is not expected to be needed by guests, so just return an error for simplicity.

Quoted 7 lines
Quoted 2 lines

Had some discussion with Gao, Chao.

For TDX PV MMIO hypercall, it has got the GPA already.
I.e, we don't need to worry about case of "contiguous in virtual memory, but not be contiguous in physical memory".

Right, the guest should have to split it into two calls I guess. If the MMIO is physically contiguous anyway though, a guest could try still to make a request that spans two page. (assuming there is not some other HW restriction for that kind of access)

Quoted 10 lines

Also, the size of the PV MMIO access is limited to 1, 2, 4, 8 bytes. No need to be split.

So, for TDX, there is no need to use vcpu->mmio_fragments[] even if the MMIO access crosses page boundary.
The check for "Disallow MMIO crossing page boundary" can be removed and no extra fragments handling needed.

Ok, thanks.

#629 lore raw

Re: [PATCH v19 110/130] KVM: TDX: Handle TDX PV MMIO hypercall

In reply to Binbin Wu
Quoted 32 lines · Binbin Wu
Quoted 29 lines · On 2/26/2024 4:26 PM, [email protected] wrote:

From: Sean Christopherson <[email protected]>

Export kvm_io_bus_read and kvm_mmio tracepoint and wire up TDX PV MMIO hypercall to the KVM backend functions.

kvm_io_bus_read/write() searches KVM device emulated in kernel of the given MMIO address and emulates the MMIO. As TDX PV MMIO also needs it, export kvm_io_bus_read(). kvm_io_bus_write() is already exported. TDX PV MMIO emulates some of MMIO itself. To add trace point consistently with x86 kvm, export kvm_mmio tracepoint.

---
  arch/x86/kvm/vmx/tdx.c | 114 +++++++++++++++++++++++++++++++++++++++++
  arch/x86/kvm/x86.c     |   1 +
  virt/kvm/kvm_main.c    |   2 +
  3 files changed, 117 insertions(+)
arch/x86/kvm/vmx/tdx.c +2 -0
index 55fc6cc6c816..389bb95d2af0 100644
@@ -1217,6 +1217,118 @@ static int tdx_emulate_io(struct kvm_vcpu *vcpu)
  	return ret;
  }
+static int tdx_complete_mmio(struct kvm_vcpu *vcpu)
+{
Quoted 8 lines

arch/x86/kvm/vmx/tdx.c static int tdx_emulate_io(struct kvm_vcpu *vcpu) · line 1227

Quoted 6 lines
arch/x86/kvm/vmx/tdx.c +6 -0
+	unsigned long val = 0;
+	gpa_t gpa;
+	int size;
+
+	KVM_BUG_ON(vcpu->mmio_needed != 1, vcpu->kvm);
+	vcpu->mmio_needed = 0;

mmio_needed is used by instruction emulator to setup the complete callback. Since TDX handle MMIO in a PV way, mmio_needed is not needed here.

Ok, we don't need to update mmio_needed.

Quoted 4 lines

arch/x86/kvm/vmx/tdx.c static int tdx_emulate_io(struct kvm_vcpu *vcpu) · line 1228

Quoted 2 lines
arch/x86/kvm/vmx/tdx.c +2 -0
+
+	if (!vcpu->mmio_is_write) {

It's also needed by instruction emulator, we can use vcpu->run->mmio.is_write instead.

No because vcpu->run->mmio is shared with user space. KVM need to stash it independently.

Quoted 7 lines

arch/x86/kvm/vmx/tdx.c static int tdx_emulate_io(struct kvm_vcpu *vcpu) · line 1231

Quoted 3 lines
arch/x86/kvm/vmx/tdx.c +2 -0
+		gpa = vcpu->mmio_fragments[0].gpa;
+		size = vcpu->mmio_fragments[0].len;

Since MMIO cross page boundary is not allowed according to the input checks from TDVMCALL, these mmio_fragments[] is not needed.
Just use vcpu->run->mmio.phys_addr and vcpu->run->mmio.len?

ditto.

Quoted 8 lines

arch/x86/kvm/vmx/tdx.c static int tdx_emulate_io(struct kvm_vcpu *vcpu) · line 1236

Quoted 5 lines
arch/x86/kvm/vmx/tdx.c +5 -0
+
+		memcpy(&val, vcpu->run->mmio.data, size);
+		tdvmcall_set_return_val(vcpu, val);
+		trace_kvm_mmio(KVM_TRACE_MMIO_READ, size, gpa, &val);
+	}

Tracepoint for KVM_TRACE_MMIO_WRITE is missing when it is handled in userspace.

tdx_mmio_write() has it before existing to the user space. It matches with how write_mmio() behaves in x86.c.

Hmm, to match with other code, we should remove trace_kvm_mmio(KVM_TRACE_MMIO_READ) and keep KVM_TRACE_MMIO_READ_UNSATISFIED in tdx_emulate_mmio(). That's how read_prepare() and read_exit_mmio() behaves.

For MMIO read
- When kernel can handle the MMIO, KVM_TRACE_MMIO_READ with data.
- When exiting to the user space, KVM_TRACE_MMIO_READ_UNSATISFIED before the exit. No trace after the user space handled the MMIO.

For MMIO write
- KVM_TRACE_MMIO_WRITE before handling it.

Quoted 56 lines

Also, the return code is only set when the emulation is done in kernel, but not set when it's handled in userspace.

arch/x86/kvm/vmx/tdx.c static int tdx_emulate_io(struct kvm_vcpu *vcpu) · line 1237

Quoted 2 lines
arch/x86/kvm/vmx/tdx.c +2 -0
+	return 1;
+}

How about the fixup as following:

(quoted diff) +0 -1
@@ -1173,19 +1173,18 @@ static int tdx_emulate_io(struct kvm_vcpu *vcpu)
 static int tdx_complete_mmio(struct kvm_vcpu *vcpu) { unsigned long val = 0;
- gpa_t gpa; - int size; - - vcpu->mmio_needed = 0; - - if

(!vcpu->mmio_is_write) { - gpa = vcpu->mmio_fragments[0].gpa; - size = vcpu->mmio_fragments[0].len; + gpa_t gpa = vcpu->run->mmio.phys_addr; + int size = vcpu->run->mmio.len; + if (vcpu->run->mmio.is_write) { + trace_kvm_mmio(KVM_TRACE_MMIO_WRITE, size, gpa, &val); + } else { memcpy(&val, vcpu->run->mmio.data, size); tdvmcall_set_return_val(vcpu, val); trace_kvm_mmio(KVM_TRACE_MMIO_READ, size, gpa, &val); } + + tdvmcall_set_return_code(vcpu, TDVMCALL_SUCCESS); return 1; }

Quoted 34 lines
arch/x86/kvm/vmx/tdx.c +34 -0
+
+static inline int tdx_mmio_write(struct kvm_vcpu *vcpu, gpa_t gpa, int size,
+				 unsigned long val)
+{
+	if (kvm_iodevice_write(vcpu, &vcpu->arch.apic->dev, gpa, size, &val) &&
+	    kvm_io_bus_write(vcpu, KVM_MMIO_BUS, gpa, size, &val))
+		return -EOPNOTSUPP;
+
+	trace_kvm_mmio(KVM_TRACE_MMIO_WRITE, size, gpa, &val);
+	return 0;
+}
+
+static inline int tdx_mmio_read(struct kvm_vcpu *vcpu, gpa_t gpa, int size)
+{
+	unsigned long val;
+
+	if (kvm_iodevice_read(vcpu, &vcpu->arch.apic->dev, gpa, size, &val) &&
+	    kvm_io_bus_read(vcpu, KVM_MMIO_BUS, gpa, size, &val))
+		return -EOPNOTSUPP;
+
+	tdvmcall_set_return_val(vcpu, val);
+	trace_kvm_mmio(KVM_TRACE_MMIO_READ, size, gpa, &val);
+	return 0;
+}
+
+static int tdx_emulate_mmio(struct kvm_vcpu *vcpu)
+{
+	struct kvm_memory_slot *slot;
+	int size, write, r;
+	unsigned long val;
+	gpa_t gpa;
+
+	KVM_BUG_ON(vcpu->mmio_needed, vcpu->kvm);
+

[...]

Quoted 5 lines

arch/x86/kvm/vmx/tdx.c static int tdx_emulate_io(struct kvm_vcpu *vcpu) · line 1310

Quoted 4 lines
arch/x86/kvm/vmx/tdx.c +4 -0
+
+	/* Request the device emulation to userspace device model. */
+	vcpu->mmio_needed = 1;
+	vcpu->mmio_is_write = write;

Then they can be dropped.

We may drop mmio_needed. mmio_is_write is needed as above.

Quoted 5 lines
Quoted 5 lines
arch/x86/kvm/vmx/tdx.c +5 -0
+	vcpu->arch.complete_userspace_io = tdx_complete_mmio;
+
+	vcpu->run->mmio.phys_addr = gpa;
+	vcpu->run->mmio.len = size;
+	vcpu->run->mmio.is_write = write;
Quoted 8 lines

arch/x86/kvm/vmx/tdx.c static int tdx_emulate_io(struct kvm_vcpu *vcpu) · line 1322

Quoted 7 lines
arch/x86/kvm/vmx/tdx.c +7 -0
+	vcpu->run->exit_reason = KVM_EXIT_MMIO;
+
+	if (write) {
+		memcpy(vcpu->run->mmio.data, &val, size);
+	} else {
+		vcpu->mmio_fragments[0].gpa = gpa;
+		vcpu->mmio_fragments[0].len = size;

These two lines can be dropped as well.

ditto.

Signature · 1 line
Isaku Yamahata <[email protected]>
#630 lore raw

Re: [PATCH v19 110/130] KVM: TDX: Handle TDX PV MMIO hypercall

Quoted 71 lines · Isaku Yamahata
Quoted 31 lines · Binbin Wu
Quoted 29 lines · On 2/26/2024 4:26 PM, [email protected] wrote:

From: Sean Christopherson <[email protected]>

Export kvm_io_bus_read and kvm_mmio tracepoint and wire up TDX PV MMIO hypercall to the KVM backend functions.

kvm_io_bus_read/write() searches KVM device emulated in kernel of the given MMIO address and emulates the MMIO. As TDX PV MMIO also needs it, export kvm_io_bus_read(). kvm_io_bus_write() is already exported. TDX PV MMIO emulates some of MMIO itself. To add trace point consistently with x86 kvm, export kvm_mmio tracepoint.

---
   arch/x86/kvm/vmx/tdx.c | 114 +++++++++++++++++++++++++++++++++++++++++
   arch/x86/kvm/x86.c     |   1 +
   virt/kvm/kvm_main.c    |   2 +
   3 files changed, 117 insertions(+)
arch/x86/kvm/vmx/tdx.c +2 -0
index 55fc6cc6c816..389bb95d2af0 100644
@@ -1217,6 +1217,118 @@ static int tdx_emulate_io(struct kvm_vcpu *vcpu)
   	return ret;
   }
+static int tdx_complete_mmio(struct kvm_vcpu *vcpu)
+{
Quoted 8 lines

arch/x86/kvm/vmx/tdx.c static int tdx_emulate_io(struct kvm_vcpu *vcpu) · line 1227

Quoted 6 lines
arch/x86/kvm/vmx/tdx.c +6 -0
+	unsigned long val = 0;
+	gpa_t gpa;
+	int size;
+
+	KVM_BUG_ON(vcpu->mmio_needed != 1, vcpu->kvm);
+	vcpu->mmio_needed = 0;

mmio_needed is used by instruction emulator to setup the complete callback. Since TDX handle MMIO in a PV way, mmio_needed is not needed here.

Ok, we don't need to update mmio_needed.

Quoted 4 lines

arch/x86/kvm/vmx/tdx.c static int tdx_emulate_io(struct kvm_vcpu *vcpu) · line 1228

Quoted 2 lines
arch/x86/kvm/vmx/tdx.c +2 -0
+
+	if (!vcpu->mmio_is_write) {

It's also needed by instruction emulator, we can use vcpu->run->mmio.is_write instead.

No because vcpu->run->mmio is shared with user space. KVM need to stash it independently.

Quoted 5 lines

arch/x86/kvm/vmx/tdx.c static int tdx_emulate_io(struct kvm_vcpu *vcpu) · line 1231

Quoted 2 lines
arch/x86/kvm/vmx/tdx.c +2 -0
+		gpa = vcpu->mmio_fragments[0].gpa;
+		size = vcpu->mmio_fragments[0].len;

Since MMIO cross page boundary is not allowed according to the input checks from TDVMCALL, these mmio_fragments[] is not needed.
Just use vcpu->run->mmio.phys_addr and vcpu->run->mmio.len?

ditto.

Quoted 7 lines

arch/x86/kvm/vmx/tdx.c static int tdx_emulate_io(struct kvm_vcpu *vcpu) · line 1236

Quoted 5 lines
arch/x86/kvm/vmx/tdx.c +5 -0
+
+		memcpy(&val, vcpu->run->mmio.data, size);
+		tdvmcall_set_return_val(vcpu, val);
+		trace_kvm_mmio(KVM_TRACE_MMIO_READ, size, gpa, &val);
+	}

Tracepoint for KVM_TRACE_MMIO_WRITE is missing when it is handled in userspace.

tdx_mmio_write() has it before existing to the user space. It matches with how write_mmio() behaves in x86.c.

Quoted 8 lines

Hmm, to match with other code, we should remove trace_kvm_mmio(KVM_TRACE_MMIO_READ) and keep KVM_TRACE_MMIO_READ_UNSATISFIED in tdx_emulate_mmio(). That's how read_prepare() and read_exit_mmio() behaves.

For MMIO read
- When kernel can handle the MMIO, KVM_TRACE_MMIO_READ with data.
- When exiting to the user space, KVM_TRACE_MMIO_READ_UNSATISFIED before the exit. No trace after the user space handled the MMIO.

For MMIO read, in the emulator, there is still a trace after the userspace handled the MMIO. In complete_emulated_mmio(), if all fragments have been handled, it will
set vcpu->mmio_read_completed to 1 and call complete_emulated_io(). complete_emulated_io
    kvm_emulate_instruction(vcpu, EMULTYPE_NO_DECODE)
        x86_emulate_instruction
            x86_emulate_insn
                emulator_read_write
                    read_prepare
                        At this point, vcpu->mmio_read_completed is 1,
                        it traces KVM_TRACE_MMIO_READ with data
                        and then clear vcpu->mmio_read_completed

So to align with emulator, we should keep the trace for KVM_TRACE_MMIO_READ.

Quoted 83 lines

For MMIO write
- KVM_TRACE_MMIO_WRITE before handling it.

Quoted 55 lines

Also, the return code is only set when the emulation is done in kernel, but not set when it's handled in userspace.

arch/x86/kvm/vmx/tdx.c static int tdx_emulate_io(struct kvm_vcpu *vcpu) · line 1237

Quoted 2 lines
arch/x86/kvm/vmx/tdx.c +2 -0
+	return 1;
+}

How about the fixup as following:

(quoted diff) +0 -1
@@ -1173,19 +1173,18 @@ static int tdx_emulate_io(struct kvm_vcpu *vcpu)
 static int tdx_complete_mmio(struct kvm_vcpu *vcpu) { unsigned long val = 0;
- gpa_t gpa; - int size; - - vcpu->mmio_needed = 0; - - if

(!vcpu->mmio_is_write) { - gpa = vcpu->mmio_fragments[0].gpa; - size = vcpu->mmio_fragments[0].len; + gpa_t gpa = vcpu->run->mmio.phys_addr; + int size = vcpu->run->mmio.len; + if (vcpu->run->mmio.is_write) { + trace_kvm_mmio(KVM_TRACE_MMIO_WRITE, size, gpa, &val); + } else { memcpy(&val, vcpu->run->mmio.data, size); tdvmcall_set_return_val(vcpu, val); trace_kvm_mmio(KVM_TRACE_MMIO_READ, size, gpa, &val); } + + tdvmcall_set_return_code(vcpu, TDVMCALL_SUCCESS); return 1; }

Quoted 34 lines
arch/x86/kvm/vmx/tdx.c +34 -0
+
+static inline int tdx_mmio_write(struct kvm_vcpu *vcpu, gpa_t gpa, int size,
+				 unsigned long val)
+{
+	if (kvm_iodevice_write(vcpu, &vcpu->arch.apic->dev, gpa, size, &val) &&
+	    kvm_io_bus_write(vcpu, KVM_MMIO_BUS, gpa, size, &val))
+		return -EOPNOTSUPP;
+
+	trace_kvm_mmio(KVM_TRACE_MMIO_WRITE, size, gpa, &val);
+	return 0;
+}
+
+static inline int tdx_mmio_read(struct kvm_vcpu *vcpu, gpa_t gpa, int size)
+{
+	unsigned long val;
+
+	if (kvm_iodevice_read(vcpu, &vcpu->arch.apic->dev, gpa, size, &val) &&
+	    kvm_io_bus_read(vcpu, KVM_MMIO_BUS, gpa, size, &val))
+		return -EOPNOTSUPP;
+
+	tdvmcall_set_return_val(vcpu, val);
+	trace_kvm_mmio(KVM_TRACE_MMIO_READ, size, gpa, &val);
+	return 0;
+}
+
+static int tdx_emulate_mmio(struct kvm_vcpu *vcpu)
+{
+	struct kvm_memory_slot *slot;
+	int size, write, r;
+	unsigned long val;
+	gpa_t gpa;
+
+	KVM_BUG_ON(vcpu->mmio_needed, vcpu->kvm);
+

[...]

Quoted 5 lines

arch/x86/kvm/vmx/tdx.c static int tdx_emulate_io(struct kvm_vcpu *vcpu) · line 1310

Quoted 4 lines
arch/x86/kvm/vmx/tdx.c +4 -0
+
+	/* Request the device emulation to userspace device model. */
+	vcpu->mmio_needed = 1;
+	vcpu->mmio_is_write = write;

Then they can be dropped.

We may drop mmio_needed. mmio_is_write is needed as above.

Quoted 5 lines
Quoted 5 lines
arch/x86/kvm/vmx/tdx.c +5 -0
+	vcpu->arch.complete_userspace_io = tdx_complete_mmio;
+
+	vcpu->run->mmio.phys_addr = gpa;
+	vcpu->run->mmio.len = size;
+	vcpu->run->mmio.is_write = write;
Quoted 8 lines

arch/x86/kvm/vmx/tdx.c static int tdx_emulate_io(struct kvm_vcpu *vcpu) · line 1322

Quoted 7 lines
arch/x86/kvm/vmx/tdx.c +7 -0
+	vcpu->run->exit_reason = KVM_EXIT_MMIO;
+
+	if (write) {
+		memcpy(vcpu->run->mmio.data, &val, size);
+	} else {
+		vcpu->mmio_fragments[0].gpa = gpa;
+		vcpu->mmio_fragments[0].len = size;

These two lines can be dropped as well.

ditto.

#631 lore raw

Re: [PATCH v19 110/130] KVM: TDX: Handle TDX PV MMIO hypercall

Quoted 33 lines · Binbin Wu
Quoted 10 lines
Quoted 7 lines

arch/x86/kvm/vmx/tdx.c static int tdx_emulate_io(struct kvm_vcpu *vcpu) · line 1236

Quoted 5 lines
arch/x86/kvm/vmx/tdx.c +5 -0
+
+		memcpy(&val, vcpu->run->mmio.data, size);
+		tdvmcall_set_return_val(vcpu, val);
+		trace_kvm_mmio(KVM_TRACE_MMIO_READ, size, gpa, &val);
+	}

Tracepoint for KVM_TRACE_MMIO_WRITE is missing when it is handled in userspace.

tdx_mmio_write() has it before existing to the user space. It matches with how write_mmio() behaves in x86.c.

Quoted 8 lines

Hmm, to match with other code, we should remove trace_kvm_mmio(KVM_TRACE_MMIO_READ) and keep KVM_TRACE_MMIO_READ_UNSATISFIED in tdx_emulate_mmio(). That's how read_prepare() and read_exit_mmio() behaves.

For MMIO read
- When kernel can handle the MMIO, KVM_TRACE_MMIO_READ with data.
- When exiting to the user space, KVM_TRACE_MMIO_READ_UNSATISFIED before the exit. No trace after the user space handled the MMIO.

For MMIO read, in the emulator, there is still a trace after the userspace handled the MMIO.
In complete_emulated_mmio(), if all fragments have been handled, it will set vcpu->mmio_read_completed to 1 and call complete_emulated_io(). complete_emulated_io
    kvm_emulate_instruction(vcpu, EMULTYPE_NO_DECODE) x86_emulate_instruction
            x86_emulate_insn
                emulator_read_write
                    read_prepare
                        At this point, vcpu->mmio_read_completed is 1, it traces KVM_TRACE_MMIO_READ with data and then clear vcpu->mmio_read_completed

Quoted 1 line

So to align with emulator, we should keep the trace for KVM_TRACE_MMIO_READ.

Oops, you're right. Agreed to keep it.

Signature · 1 line
Isaku Yamahata <[email protected]>
#632 lore raw

PATCH v19 111/130 KVM: TDX: Implement callbacks for MSR operations for TDX

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

Implements set_msr/get_msr/has_emulated_msr methods for TDX to handle hypercall from guest TD for paravirtualized rdmsr and wrmsr. The TDX module virtualizes MSRs. For some MSRs, it injects #VE to the guest TD upon RDMSR or WRMSR. The exact list of such MSRs are defined in the spec.

Upon #VE, the guest TD may execute hypercalls, TDG.VP.VMCALL<INSTRUCTION.RDMSR> and TDG.VP.VMCALL<INSTRUCTION.WRMSR>, which are defined in GHCI (Guest-Host Communication Interface) so that the host VMM (e.g. KVM) can virtualize the MSRs.

There are three classes of MSRs virtualization.
- non-configurable: TDX module directly virtualizes it. VMM can't configure. the value set by KVM_SET_MSR_INDEX_LIST is ignored.
- configurable: TDX module directly virtualizes it. VMM can configure at the VM creation time. The value set by KVM_SET_MSR_INDEX_LIST is used.
- #VE case
Guest TD would issue TDG.VP.VMCALL<INSTRUCTION.{WRMSR,RDMSR> and VMM handles the MSR hypercall. The value set by KVM_SET_MSR_INDEX_LIST is used.

---
 arch/x86/kvm/vmx/main.c    | 44 +++++++++++++++++++++---
 arch/x86/kvm/vmx/tdx.c     | 70 ++++++++++++++++++++++++++++++++++++++
 arch/x86/kvm/vmx/x86_ops.h |  6 ++++
 arch/x86/kvm/x86.c         |  1 -
 arch/x86/kvm/x86.h         |  2 ++
 5 files changed, 118 insertions(+), 5 deletions(-)
arch/x86/kvm/vmx/main.c +40 -4
index c9a40456d965..ed46e7e57c18 100644
@@ -247,6 +247,42 @@ static void vt_handle_exit_irqoff(struct kvm_vcpu *vcpu)
 	vmx_handle_exit_irqoff(vcpu);
 }
 
+static int vt_set_msr(struct kvm_vcpu *vcpu, struct msr_data *msr_info)
+{
+	if (unlikely(is_td_vcpu(vcpu)))
+		return tdx_set_msr(vcpu, msr_info);
+
+	return vmx_set_msr(vcpu, msr_info);
+}
+
+/*
+ * The kvm parameter can be NULL (module initialization, or invocation before
+ * VM creation). Be sure to check the kvm parameter before using it.
+ */
+static bool vt_has_emulated_msr(struct kvm *kvm, u32 index)
+{
+	if (kvm && is_td(kvm))
+		return tdx_has_emulated_msr(index, true);
+
+	return vmx_has_emulated_msr(kvm, index);
+}
+
+static int vt_get_msr(struct kvm_vcpu *vcpu, struct msr_data *msr_info)
+{
+	if (unlikely(is_td_vcpu(vcpu)))
+		return tdx_get_msr(vcpu, msr_info);
+
+	return vmx_get_msr(vcpu, msr_info);
+}
+
+static void vt_msr_filter_changed(struct kvm_vcpu *vcpu)
+{
+	if (is_td_vcpu(vcpu))
+		return;
+
+	vmx_msr_filter_changed(vcpu);
+}
+
 static void vt_apicv_pre_state_restore(struct kvm_vcpu *vcpu)
 {
 	struct pi_desc *pi = vcpu_to_pi_desc(vcpu);
@@ -541,7 +577,7 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
 	/* TDX cpu enablement is done by tdx_hardware_setup(). */
 	.hardware_enable = vmx_hardware_enable,
 	.hardware_disable = vt_hardware_disable,
-	.has_emulated_msr = vmx_has_emulated_msr,
+	.has_emulated_msr = vt_has_emulated_msr,
 
 	.is_vm_type_supported = vt_is_vm_type_supported,
 	.max_vcpus = vt_max_vcpus,
@@ -563,8 +599,8 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
 
 	.update_exception_bitmap = vmx_update_exception_bitmap,
 	.get_msr_feature = vmx_get_msr_feature,
-	.get_msr = vmx_get_msr,
-	.set_msr = vmx_set_msr,
+	.get_msr = vt_get_msr,
+	.set_msr = vt_set_msr,
 	.get_segment_base = vmx_get_segment_base,
 	.get_segment = vmx_get_segment,
 	.set_segment = vmx_set_segment,
@@ -674,7 +710,7 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
 	.apic_init_signal_blocked = vmx_apic_init_signal_blocked,
 	.migrate_timers = vmx_migrate_timers,
 
-	.msr_filter_changed = vmx_msr_filter_changed,
+	.msr_filter_changed = vt_msr_filter_changed,
 	.complete_emulated_msr = kvm_complete_insn_gp,
 
 	.vcpu_deliver_sipi_vector = kvm_vcpu_deliver_sipi_vector,
arch/x86/kvm/vmx/tdx.c +70 -0

76 lines not on this page · raw message

arch/x86/kvm/vmx/x86_ops.h +6 -0
index a12e3bfc96dd..017a73ab34bb 100644
@@ -165,6 +165,9 @@ void tdx_deliver_interrupt(struct kvm_lapic *apic, int delivery_mode,
 void tdx_inject_nmi(struct kvm_vcpu *vcpu);
 void tdx_get_exit_info(struct kvm_vcpu *vcpu, u32 *reason,
 		u64 *info1, u64 *info2, u32 *intr_info, u32 *error_code);
+bool tdx_has_emulated_msr(u32 index, bool write);
+int tdx_get_msr(struct kvm_vcpu *vcpu, struct msr_data *msr);
+int tdx_set_msr(struct kvm_vcpu *vcpu, struct msr_data *msr);
 
 int tdx_vcpu_ioctl(struct kvm_vcpu *vcpu, void __user *argp);
 
@@ -214,6 +217,9 @@ static inline void tdx_deliver_interrupt(struct kvm_lapic *apic, int delivery_mo
 static inline void tdx_inject_nmi(struct kvm_vcpu *vcpu) {}
 static inline void tdx_get_exit_info(struct kvm_vcpu *vcpu, u32 *reason, u64 *info1,
 				     u64 *info2, u32 *intr_info, u32 *error_code) {}
+static inline bool tdx_has_emulated_msr(u32 index, bool write) { return false; }
+static inline int tdx_get_msr(struct kvm_vcpu *vcpu, struct msr_data *msr) { return 1; }
+static inline int tdx_set_msr(struct kvm_vcpu *vcpu, struct msr_data *msr) { return 1; }
 
 static inline int tdx_vcpu_ioctl(struct kvm_vcpu *vcpu, void __user *argp) { return -EOPNOTSUPP; }
 
arch/x86/kvm/x86.c +0 -1
index d5b18cad9dcd..0e1d3853eeb4 100644
@@ -90,7 +90,6 @@
 #include "trace.h"
 
 #define MAX_IO_MSRS 256
-#define KVM_MAX_MCE_BANKS 32
 
 struct kvm_caps kvm_caps __read_mostly = {
 	.supported_mce_cap = MCG_CTL_P | MCG_SER_P,
arch/x86/kvm/x86.h +2 -0
index 4e40c23d66ed..c87b7a777b67 100644
@@ -9,6 +9,8 @@
 #include "kvm_cache_regs.h"
 #include "kvm_emulate.h"
 
+#define KVM_MAX_MCE_BANKS 32
+
 bool __kvm_is_vm_type_supported(unsigned long type);
 
 struct kvm_caps {
Signature · 1 line
2.25.1
#633 lore raw

Re: [PATCH v19 111/130] KVM: TDX: Implement callbacks for MSR operations for TDX

Quoted 34 lines · On Mon, Feb 26, 2024 at 12:26:53AM -0800, [email protected] wrote:
arch/x86/kvm/vmx/tdx.c +34 -0
+bool tdx_has_emulated_msr(u32 index, bool write)
+{
+	switch (index) {
+	case MSR_IA32_UCODE_REV:
+	case MSR_IA32_ARCH_CAPABILITIES:
+	case MSR_IA32_POWER_CTL:
+	case MSR_IA32_CR_PAT:
+	case MSR_IA32_TSC_DEADLINE:
+	case MSR_IA32_MISC_ENABLE:
+	case MSR_PLATFORM_INFO:
+	case MSR_MISC_FEATURES_ENABLES:
+	case MSR_IA32_MCG_CAP:
+	case MSR_IA32_MCG_STATUS:
+	case MSR_IA32_MCG_CTL:
+	case MSR_IA32_MCG_EXT_CTL:
+	case MSR_IA32_MC0_CTL ... MSR_IA32_MCx_CTL(KVM_MAX_MCE_BANKS) - 1:
+	case MSR_IA32_MC0_CTL2 ... MSR_IA32_MCx_CTL2(KVM_MAX_MCE_BANKS) - 1:
+		/* MSR_IA32_MCx_{CTL, STATUS, ADDR, MISC, CTL2} */
+		return true;
+	case APIC_BASE_MSR ... APIC_BASE_MSR + 0xff:
+		/*
+		 * x2APIC registers that are virtualized by the CPU can't be
+		 * emulated, KVM doesn't have access to the virtual APIC page.
+		 */
+		switch (index) {
+		case X2APIC_MSR(APIC_TASKPRI):
+		case X2APIC_MSR(APIC_PROCPRI):
+		case X2APIC_MSR(APIC_EOI):
+		case X2APIC_MSR(APIC_ISR) ... X2APIC_MSR(APIC_ISR + APIC_ISR_NR):
+		case X2APIC_MSR(APIC_TMR) ... X2APIC_MSR(APIC_TMR + APIC_ISR_NR):
+		case X2APIC_MSR(APIC_IRR) ... X2APIC_MSR(APIC_IRR + APIC_ISR_NR):
+			return false;
+		default:
+			return true;

arch/x86/kvm/vmx/tdx.c void tdx_get_exit_info(struct kvm_vcpu *vcpu, u32 *reason, · line 1933

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +10 -0
+		}
+	case MSR_IA32_APICBASE:
+	case MSR_EFER:
+		return !write;
+	case 0x4b564d00 ... 0x4b564dff:
+		/* KVM custom MSRs */
+		return tdx_is_emulated_kvm_msr(index, write);
+	default:
+		return false;
+	}

The only call site with a non-Null KVM parameter is:

	r = static_call(kvm_x86_has_emulated_msr)(kvm, MSR_IA32_SMBASE);

Only MSR_IA32_SMBASE needs to be handled. So, this function is much more complicated than it should be.

#634 lore raw

Re: [PATCH v19 111/130] KVM: TDX: Implement callbacks for MSR operations for TDX

Quoted 48 lines · Chao Gao
Quoted 34 lines · On Mon, Feb 26, 2024 at 12:26:53AM -0800, [email protected] wrote:
arch/x86/kvm/vmx/tdx.c +34 -0
+bool tdx_has_emulated_msr(u32 index, bool write)
+{
+	switch (index) {
+	case MSR_IA32_UCODE_REV:
+	case MSR_IA32_ARCH_CAPABILITIES:
+	case MSR_IA32_POWER_CTL:
+	case MSR_IA32_CR_PAT:
+	case MSR_IA32_TSC_DEADLINE:
+	case MSR_IA32_MISC_ENABLE:
+	case MSR_PLATFORM_INFO:
+	case MSR_MISC_FEATURES_ENABLES:
+	case MSR_IA32_MCG_CAP:
+	case MSR_IA32_MCG_STATUS:
+	case MSR_IA32_MCG_CTL:
+	case MSR_IA32_MCG_EXT_CTL:
+	case MSR_IA32_MC0_CTL ... MSR_IA32_MCx_CTL(KVM_MAX_MCE_BANKS) - 1:
+	case MSR_IA32_MC0_CTL2 ... MSR_IA32_MCx_CTL2(KVM_MAX_MCE_BANKS) - 1:
+		/* MSR_IA32_MCx_{CTL, STATUS, ADDR, MISC, CTL2} */
+		return true;
+	case APIC_BASE_MSR ... APIC_BASE_MSR + 0xff:
+		/*
+		 * x2APIC registers that are virtualized by the CPU can't be
+		 * emulated, KVM doesn't have access to the virtual APIC page.
+		 */
+		switch (index) {
+		case X2APIC_MSR(APIC_TASKPRI):
+		case X2APIC_MSR(APIC_PROCPRI):
+		case X2APIC_MSR(APIC_EOI):
+		case X2APIC_MSR(APIC_ISR) ... X2APIC_MSR(APIC_ISR + APIC_ISR_NR):
+		case X2APIC_MSR(APIC_TMR) ... X2APIC_MSR(APIC_TMR + APIC_ISR_NR):
+		case X2APIC_MSR(APIC_IRR) ... X2APIC_MSR(APIC_IRR + APIC_ISR_NR):
+			return false;
+		default:
+			return true;

arch/x86/kvm/vmx/tdx.c void tdx_get_exit_info(struct kvm_vcpu *vcpu, u32 *reason, · line 1933

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +10 -0
+		}
+	case MSR_IA32_APICBASE:
+	case MSR_EFER:
+		return !write;
+	case 0x4b564d00 ... 0x4b564dff:
+		/* KVM custom MSRs */
+		return tdx_is_emulated_kvm_msr(index, write);
+	default:
+		return false;
+	}

The only call site with a non-Null KVM parameter is:

Quoted 4 lines
	r = static_call(kvm_x86_has_emulated_msr)(kvm, MSR_IA32_SMBASE);

Only MSR_IA32_SMBASE needs to be handled. So, this function is much more complicated than it should be.

No, because it's also used by tdx_{g,s}et_msr().

#635 lore raw

Re: [PATCH v19 111/130] KVM: TDX: Implement callbacks for MSR operations for TDX

In reply to [email protected]
Quoted 46 lines · On Mon, Feb 26, 2024, [email protected] wrote:
arch/x86/kvm/vmx/tdx.c +38 -0
index 389bb95d2af0..c8f991b69720 100644
@@ -1877,6 +1877,76 @@ void tdx_get_exit_info(struct kvm_vcpu *vcpu, u32 *reason,
 	*error_code = 0;
 }
 
+static bool tdx_is_emulated_kvm_msr(u32 index, bool write)
+{
+	switch (index) {
+	case MSR_KVM_POLL_CONTROL:
+		return true;
+	default:
+		return false;
+	}
+}
+
+bool tdx_has_emulated_msr(u32 index, bool write)
+{
+	switch (index) {
+	case MSR_IA32_UCODE_REV:
+	case MSR_IA32_ARCH_CAPABILITIES:
+	case MSR_IA32_POWER_CTL:
+	case MSR_IA32_CR_PAT:
+	case MSR_IA32_TSC_DEADLINE:
+	case MSR_IA32_MISC_ENABLE:
+	case MSR_PLATFORM_INFO:
+	case MSR_MISC_FEATURES_ENABLES:
+	case MSR_IA32_MCG_CAP:
+	case MSR_IA32_MCG_STATUS:
+	case MSR_IA32_MCG_CTL:
+	case MSR_IA32_MCG_EXT_CTL:
+	case MSR_IA32_MC0_CTL ... MSR_IA32_MCx_CTL(KVM_MAX_MCE_BANKS) - 1:
+	case MSR_IA32_MC0_CTL2 ... MSR_IA32_MCx_CTL2(KVM_MAX_MCE_BANKS) - 1:
+		/* MSR_IA32_MCx_{CTL, STATUS, ADDR, MISC, CTL2} */
+		return true;
+	case APIC_BASE_MSR ... APIC_BASE_MSR + 0xff:
+		/*
+		 * x2APIC registers that are virtualized by the CPU can't be
+		 * emulated, KVM doesn't have access to the virtual APIC page.
+		 */
+		switch (index) {
+		case X2APIC_MSR(APIC_TASKPRI):
+		case X2APIC_MSR(APIC_PROCPRI):
+		case X2APIC_MSR(APIC_EOI):

arch/x86/kvm/vmx/tdx.c void tdx_get_exit_info(struct kvm_vcpu *vcpu, u32 *reason, · line 1927

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +10 -0
+		case X2APIC_MSR(APIC_ISR) ... X2APIC_MSR(APIC_ISR + APIC_ISR_NR):
+		case X2APIC_MSR(APIC_TMR) ... X2APIC_MSR(APIC_TMR + APIC_ISR_NR):
+		case X2APIC_MSR(APIC_IRR) ... X2APIC_MSR(APIC_IRR + APIC_ISR_NR):
+			return false;
+		default:
+			return true;
+		}
+	case MSR_IA32_APICBASE:
+	case MSR_EFER:
+		return !write;

Meh, for literally two MSRs, just open code them in tdx_set_msr() and drop the @write param. Or alternatively add:

static bool tdx_is_read_only_msr(u32 msr){

{
	return msr == MSR_IA32_APICBASE || msr == MSR_EFER;
}

arch/x86/kvm/vmx/tdx.c void tdx_get_exit_info(struct kvm_vcpu *vcpu, u32 *reason, · line 1928

Quoted 1 line
arch/x86/kvm/vmx/tdx.c +1 -0
+	case 0x4b564d00 ... 0x4b564dff:

This is silly, just do

	case MSR_KVM_POLL_CONTROL:
		return false;

and let everything else go through the default statement, no?

arch/x86/kvm/vmx/tdx.c void tdx_get_exit_info(struct kvm_vcpu *vcpu, u32 *reason, · line 1940

Quoted 12 lines
arch/x86/kvm/vmx/tdx.c +12 -0
+		/* KVM custom MSRs */
+		return tdx_is_emulated_kvm_msr(index, write);
+	default:
+		return false;
+	}
+}
+
+int tdx_get_msr(struct kvm_vcpu *vcpu, struct msr_data *msr)
+{
+	if (tdx_has_emulated_msr(msr->index, false))
+		return kvm_get_msr_common(vcpu, msr);
+	return 1;

Please invert these and make the happy path the not-taken path, i.e.

	if (!tdx_has_emulated_msr(msr->index))
		return 1;

	return kvm_get_msr_common(vcpu, msr);

The standard kernel pattern is

if (error)
return <error thingie>

return <happy thingie>

arch/x86/kvm/vmx/tdx.c void tdx_get_exit_info(struct kvm_vcpu *vcpu, u32 *reason, · line 1945

Quoted 5 lines
arch/x86/kvm/vmx/tdx.c +5 -0
+}
+
+int tdx_set_msr(struct kvm_vcpu *vcpu, struct msr_data *msr)
+{
+	if (tdx_has_emulated_msr(msr->index, true))

As above:

	if (tdx_is_read_only_msr(msr->index))
		return 1;

	if (!tdx_has_emulated_msr(msr->index))
		return 1;

	return kvm_set_msr_common(vcpu, msr);
Quoted 17 lines
arch/x86/kvm/x86.c +0 -1
index d5b18cad9dcd..0e1d3853eeb4 100644
@@ -90,7 +90,6 @@
 #include "trace.h"
 
 #define MAX_IO_MSRS 256
-#define KVM_MAX_MCE_BANKS 32
 
 struct kvm_caps kvm_caps __read_mostly = {
 	.supported_mce_cap = MCG_CTL_P | MCG_SER_P,
arch/x86/kvm/x86.h +0 -0
index 4e40c23d66ed..c87b7a777b67 100644

arch/x86/kvm/x86.h line 12

Quoted 4 lines
arch/x86/kvm/x86.h +1 -0
@@ -9,6 +9,8 @@
 #include "kvm_cache_regs.h"
 #include "kvm_emulate.h"
 
+#define KVM_MAX_MCE_BANKS 32

Split this to a separate. Yes, it's trivial, but that's _exactly_ why it should be in a separate patch. The more trivial refactoring you split out, the more we can apply _now_ and take off your hands.

#636 lore raw

Re: [PATCH v19 111/130] KVM: TDX: Implement callbacks for MSR operations for TDX

Quoted 61 lines · Sean Christopherson
Quoted 46 lines · On Mon, Feb 26, 2024, [email protected] wrote:
arch/x86/kvm/vmx/tdx.c +38 -0
index 389bb95d2af0..c8f991b69720 100644
@@ -1877,6 +1877,76 @@ void tdx_get_exit_info(struct kvm_vcpu *vcpu, u32 *reason,
 	*error_code = 0;
 }
 
+static bool tdx_is_emulated_kvm_msr(u32 index, bool write)
+{
+	switch (index) {
+	case MSR_KVM_POLL_CONTROL:
+		return true;
+	default:
+		return false;
+	}
+}
+
+bool tdx_has_emulated_msr(u32 index, bool write)
+{
+	switch (index) {
+	case MSR_IA32_UCODE_REV:
+	case MSR_IA32_ARCH_CAPABILITIES:
+	case MSR_IA32_POWER_CTL:
+	case MSR_IA32_CR_PAT:
+	case MSR_IA32_TSC_DEADLINE:
+	case MSR_IA32_MISC_ENABLE:
+	case MSR_PLATFORM_INFO:
+	case MSR_MISC_FEATURES_ENABLES:
+	case MSR_IA32_MCG_CAP:
+	case MSR_IA32_MCG_STATUS:
+	case MSR_IA32_MCG_CTL:
+	case MSR_IA32_MCG_EXT_CTL:
+	case MSR_IA32_MC0_CTL ... MSR_IA32_MCx_CTL(KVM_MAX_MCE_BANKS) - 1:
+	case MSR_IA32_MC0_CTL2 ... MSR_IA32_MCx_CTL2(KVM_MAX_MCE_BANKS) - 1:
+		/* MSR_IA32_MCx_{CTL, STATUS, ADDR, MISC, CTL2} */
+		return true;
+	case APIC_BASE_MSR ... APIC_BASE_MSR + 0xff:
+		/*
+		 * x2APIC registers that are virtualized by the CPU can't be
+		 * emulated, KVM doesn't have access to the virtual APIC page.
+		 */
+		switch (index) {
+		case X2APIC_MSR(APIC_TASKPRI):
+		case X2APIC_MSR(APIC_PROCPRI):
+		case X2APIC_MSR(APIC_EOI):

arch/x86/kvm/vmx/tdx.c void tdx_get_exit_info(struct kvm_vcpu *vcpu, u32 *reason, · line 1927

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +10 -0
+		case X2APIC_MSR(APIC_ISR) ... X2APIC_MSR(APIC_ISR + APIC_ISR_NR):
+		case X2APIC_MSR(APIC_TMR) ... X2APIC_MSR(APIC_TMR + APIC_ISR_NR):
+		case X2APIC_MSR(APIC_IRR) ... X2APIC_MSR(APIC_IRR + APIC_ISR_NR):
+			return false;
+		default:
+			return true;
+		}
+	case MSR_IA32_APICBASE:
+	case MSR_EFER:
+		return !write;

Meh, for literally two MSRs, just open code them in tdx_set_msr() and drop the @write param. Or alternatively add:

Quoted 4 lines

static bool tdx_is_read_only_msr(u32 msr){

{
	return msr == MSR_IA32_APICBASE || msr == MSR_EFER;
}

Sure will add.

Quoted 9 lines

arch/x86/kvm/vmx/tdx.c void tdx_get_exit_info(struct kvm_vcpu *vcpu, u32 *reason, · line 1928

Quoted 2 lines
arch/x86/kvm/vmx/tdx.c +1 -0
+	case 0x4b564d00 ... 0x4b564dff:

This is silly, just do

	case MSR_KVM_POLL_CONTROL:
		return false;

and let everything else go through the default statement, no?

Now tdx_is_emulated_kvm_msr() is trivial, will open code it.

Quoted 35 lines

arch/x86/kvm/vmx/tdx.c void tdx_get_exit_info(struct kvm_vcpu *vcpu, u32 *reason, · line 1940

Quoted 12 lines
arch/x86/kvm/vmx/tdx.c +12 -0
+		/* KVM custom MSRs */
+		return tdx_is_emulated_kvm_msr(index, write);
+	default:
+		return false;
+	}
+}
+
+int tdx_get_msr(struct kvm_vcpu *vcpu, struct msr_data *msr)
+{
+	if (tdx_has_emulated_msr(msr->index, false))
+		return kvm_get_msr_common(vcpu, msr);
+	return 1;

Please invert these and make the happy path the not-taken path, i.e.

	if (!tdx_has_emulated_msr(msr->index))
		return 1;

	return kvm_get_msr_common(vcpu, msr);

The standard kernel pattern is

if (error)
return <error thingie>

return <happy thingie>

arch/x86/kvm/vmx/tdx.c void tdx_get_exit_info(struct kvm_vcpu *vcpu, u32 *reason, · line 1945

Quoted 5 lines
arch/x86/kvm/vmx/tdx.c +5 -0
+}
+
+int tdx_set_msr(struct kvm_vcpu *vcpu, struct msr_data *msr)
+{
+	if (tdx_has_emulated_msr(msr->index, true))

As above:

Quoted 7 lines
	if (tdx_is_read_only_msr(msr->index))
		return 1;

	if (!tdx_has_emulated_msr(msr->index))
		return 1;

	return kvm_set_msr_common(vcpu, msr);

Sure, will update them.

Quoted 20 lines
Quoted 20 lines
arch/x86/kvm/x86.c +0 -1
index d5b18cad9dcd..0e1d3853eeb4 100644
@@ -90,7 +90,6 @@
 #include "trace.h"
 
 #define MAX_IO_MSRS 256
-#define KVM_MAX_MCE_BANKS 32
 
 struct kvm_caps kvm_caps __read_mostly = {
 	.supported_mce_cap = MCG_CTL_P | MCG_SER_P,
arch/x86/kvm/x86.h +0 -0
index 4e40c23d66ed..c87b7a777b67 100644
@@ -9,6 +9,8 @@
 #include "kvm_cache_regs.h"
 #include "kvm_emulate.h"
 
Quoted 5 lines

arch/x86/kvm/x86.h line 12

Quoted 1 line
arch/x86/kvm/x86.h +1 -0
+#define KVM_MAX_MCE_BANKS 32

Split this to a separate. Yes, it's trivial, but that's _exactly_ why it should be in a separate patch. The more trivial refactoring you split out, the more we can apply _now_ and take off your hands.

Will split it.

Signature · 1 line
Isaku Yamahata <[email protected]>
#637 lore raw

Re: [PATCH v19 111/130] KVM: TDX: Implement callbacks for MSR operations for TDX

Quoted 63 lines · Isaku Yamahata
Quoted 60 lines · Sean Christopherson
Quoted 46 lines · On Mon, Feb 26, 2024, [email protected] wrote:
arch/x86/kvm/vmx/tdx.c +38 -0
index 389bb95d2af0..c8f991b69720 100644
@@ -1877,6 +1877,76 @@ void tdx_get_exit_info(struct kvm_vcpu *vcpu, u32 *reason,
  	*error_code = 0;
  }
 
+static bool tdx_is_emulated_kvm_msr(u32 index, bool write)
+{
+	switch (index) {
+	case MSR_KVM_POLL_CONTROL:
+		return true;
+	default:
+		return false;
+	}
+}
+
+bool tdx_has_emulated_msr(u32 index, bool write)
+{
+	switch (index) {
+	case MSR_IA32_UCODE_REV:
+	case MSR_IA32_ARCH_CAPABILITIES:
+	case MSR_IA32_POWER_CTL:
+	case MSR_IA32_CR_PAT:
+	case MSR_IA32_TSC_DEADLINE:
+	case MSR_IA32_MISC_ENABLE:
+	case MSR_PLATFORM_INFO:
+	case MSR_MISC_FEATURES_ENABLES:
+	case MSR_IA32_MCG_CAP:
+	case MSR_IA32_MCG_STATUS:
+	case MSR_IA32_MCG_CTL:
+	case MSR_IA32_MCG_EXT_CTL:
+	case MSR_IA32_MC0_CTL ... MSR_IA32_MCx_CTL(KVM_MAX_MCE_BANKS) - 1:
+	case MSR_IA32_MC0_CTL2 ... MSR_IA32_MCx_CTL2(KVM_MAX_MCE_BANKS) - 1:
+		/* MSR_IA32_MCx_{CTL, STATUS, ADDR, MISC, CTL2} */
+		return true;
+	case APIC_BASE_MSR ... APIC_BASE_MSR + 0xff:
+		/*
+		 * x2APIC registers that are virtualized by the CPU can't be
+		 * emulated, KVM doesn't have access to the virtual APIC page.
+		 */
+		switch (index) {
+		case X2APIC_MSR(APIC_TASKPRI):
+		case X2APIC_MSR(APIC_PROCPRI):
+		case X2APIC_MSR(APIC_EOI):

arch/x86/kvm/vmx/tdx.c void tdx_get_exit_info(struct kvm_vcpu *vcpu, u32 *reason, · line 1927

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +10 -0
+		case X2APIC_MSR(APIC_ISR) ... X2APIC_MSR(APIC_ISR + APIC_ISR_NR):
+		case X2APIC_MSR(APIC_TMR) ... X2APIC_MSR(APIC_TMR + APIC_ISR_NR):
+		case X2APIC_MSR(APIC_IRR) ... X2APIC_MSR(APIC_IRR + APIC_ISR_NR):
+			return false;
+		default:
+			return true;
+		}
+	case MSR_IA32_APICBASE:
+	case MSR_EFER:
+		return !write;

Meh, for literally two MSRs, just open code them in tdx_set_msr() and drop the @write param. Or alternatively add:

Quoted 11 lines
Quoted 4 lines

static bool tdx_is_read_only_msr(u32 msr){

{
	return msr == MSR_IA32_APICBASE || msr == MSR_EFER;
}

Sure will add.

Quoted 5 lines

arch/x86/kvm/vmx/tdx.c void tdx_get_exit_info(struct kvm_vcpu *vcpu, u32 *reason, · line 1928

Quoted 1 line
arch/x86/kvm/vmx/tdx.c +1 -0
+	case 0x4b564d00 ... 0x4b564dff:

This is silly, just do

	case MSR_KVM_POLL_CONTROL:
		return false;

Shoud return true here, right?

Quoted 13 lines
Quoted 2 lines

and let everything else go through the default statement, no?

Now tdx_is_emulated_kvm_msr() is trivial, will open code it.

Quoted 8 lines
Quoted 8 lines
arch/x86/kvm/vmx/tdx.c +7 -0
+		/* KVM custom MSRs */
+		return tdx_is_emulated_kvm_msr(index, write);
+	default:
+		return false;
+	}
+}
+
 
#638 lore raw

Re: [PATCH v19 111/130] KVM: TDX: Implement callbacks for MSR operations for TDX

In reply to [email protected]
Quoted 7 lines · On 2/26/2024 4:26 PM, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

Implements set_msr/get_msr/has_emulated_msr methods for TDX to handle hypercall from guest TD for paravirtualized rdmsr and wrmsr. The TDX module virtualizes MSRs. For some MSRs, it injects #VE to the guest TD upon RDMSR or WRMSR. The exact list of such MSRs are defined in the spec.

Quoted 8 lines

Upon #VE, the guest TD may execute hypercalls, TDG.VP.VMCALL<INSTRUCTION.RDMSR> and TDG.VP.VMCALL<INSTRUCTION.WRMSR>, which are defined in GHCI (Guest-Host Communication Interface) so that the host VMM (e.g. KVM) can virtualize the MSRs.

There are three classes of MSRs virtualization.
- non-configurable: TDX module directly virtualizes it. VMM can't configure. the value set by KVM_SET_MSR_INDEX_LIST is ignored.

There is no KVM_SET_MSR_INDEX_LIST in current kvm code. Do you mean KVM_SET_MSRS?

Quoted 7 lines

- configurable: TDX module directly virtualizes it. VMM can configure at the VM creation time. The value set by KVM_SET_MSR_INDEX_LIST is used.
- #VE case
Guest TD would issue TDG.VP.VMCALL<INSTRUCTION.{WRMSR,RDMSR> and VMM handles the MSR hypercall. The value set by KVM_SET_MSR_INDEX_LIST is used.

#639 lore raw

Re: [PATCH v19 111/130] KVM: TDX: Implement callbacks for MSR operations for TDX

Quoted 15 lines · Binbin Wu
Quoted 12 lines · On 2/26/2024 4:26 PM, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

Implements set_msr/get_msr/has_emulated_msr methods for TDX to handle hypercall from guest TD for paravirtualized rdmsr and wrmsr. The TDX module virtualizes MSRs. For some MSRs, it injects #VE to the guest TD upon RDMSR or WRMSR. The exact list of such MSRs are defined in the spec.

Upon #VE, the guest TD may execute hypercalls, TDG.VP.VMCALL<INSTRUCTION.RDMSR> and TDG.VP.VMCALL<INSTRUCTION.WRMSR>, which are defined in GHCI (Guest-Host Communication Interface) so that the host VMM (e.g. KVM) can virtualize the MSRs.

Quoted 6 lines
Quoted 3 lines

There are three classes of MSRs virtualization.
- non-configurable: TDX module directly virtualizes it. VMM can't configure. the value set by KVM_SET_MSR_INDEX_LIST is ignored.

There is no KVM_SET_MSR_INDEX_LIST in current kvm code. Do you mean KVM_SET_MSRS?

Yes, will fix it. Thank you for catching it.

Signature · 1 line
Isaku Yamahata <[email protected]>
#640 lore raw

Re: [PATCH v19 111/130] KVM: TDX: Implement callbacks for MSR operations for TDX

In reply to [email protected]
Quoted 25 lines · On 2/26/2024 4:26 PM, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

Implements set_msr/get_msr/has_emulated_msr methods for TDX to handle hypercall from guest TD for paravirtualized rdmsr and wrmsr. The TDX module virtualizes MSRs. For some MSRs, it injects #VE to the guest TD upon RDMSR or WRMSR. The exact list of such MSRs are defined in the spec.

Upon #VE, the guest TD may execute hypercalls, TDG.VP.VMCALL<INSTRUCTION.RDMSR> and TDG.VP.VMCALL<INSTRUCTION.WRMSR>, which are defined in GHCI (Guest-Host Communication Interface) so that the host VMM (e.g. KVM) can virtualize the MSRs.

There are three classes of MSRs virtualization.
- non-configurable: TDX module directly virtualizes it. VMM can't configure. the value set by KVM_SET_MSR_INDEX_LIST is ignored.
- configurable: TDX module directly virtualizes it. VMM can configure at the VM creation time. The value set by KVM_SET_MSR_INDEX_LIST is used.
- #VE case
Guest TD would issue TDG.VP.VMCALL<INSTRUCTION.{WRMSR,RDMSR> and VMM handles the MSR hypercall. The value set by KVM_SET_MSR_INDEX_LIST is used.

---

[...]

Quoted 4 lines
arch/x86/kvm/vmx/tdx.c +4 -0
+
+bool tdx_has_emulated_msr(u32 index, bool write)
+{
+	switch (index) {

arch/x86/kvm/vmx/tdx.c void tdx_get_exit_info(struct kvm_vcpu *vcpu, u32 *reason, · line 1902

Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +10 -0
+	case MSR_IA32_UCODE_REV:
+	case MSR_IA32_ARCH_CAPABILITIES:
+	case MSR_IA32_POWER_CTL:
+	case MSR_IA32_CR_PAT:
+	case MSR_IA32_TSC_DEADLINE:
+	case MSR_IA32_MISC_ENABLE:
+	case MSR_PLATFORM_INFO:
+	case MSR_MISC_FEATURES_ENABLES:
+	case MSR_IA32_MCG_CAP:
+	case MSR_IA32_MCG_STATUS:

It not about this patch directly.

Intel SDM says:
"An attempt to write to IA32_MCG_STATUS with any value other than 0 would result in #GP".

But in set_msr_mce(), IA32_MCG_STATUS is set without any check. Should it be checked against 0 if it is not host_initiated?

#641 lore raw

PATCH v19 112/130 KVM: TDX: Handle TDX PV rdmsr/wrmsr hypercall

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

Wire up TDX PV rdmsr/wrmsr hypercall to the KVM backend function.

---
 arch/x86/kvm/vmx/tdx.c | 39 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 39 insertions(+)
arch/x86/kvm/vmx/tdx.c +39 -0
index c8f991b69720..4c635bfcaf7a 100644
@@ -1329,6 +1329,41 @@ static int tdx_emulate_mmio(struct kvm_vcpu *vcpu)
 	return 1;
 }
 
+static int tdx_emulate_rdmsr(struct kvm_vcpu *vcpu)
+{
+	u32 index = tdvmcall_a0_read(vcpu);
+	u64 data;
+
+	if (!kvm_msr_allowed(vcpu, index, KVM_MSR_FILTER_READ) ||
+	    kvm_get_msr(vcpu, index, &data)) {
+		trace_kvm_msr_read_ex(index);
+		tdvmcall_set_return_code(vcpu, TDVMCALL_INVALID_OPERAND);
+		return 1;
+	}
+	trace_kvm_msr_read(index, data);
+
+	tdvmcall_set_return_code(vcpu, TDVMCALL_SUCCESS);
+	tdvmcall_set_return_val(vcpu, data);
+	return 1;
+}
+
+static int tdx_emulate_wrmsr(struct kvm_vcpu *vcpu)
+{
+	u32 index = tdvmcall_a0_read(vcpu);
+	u64 data = tdvmcall_a1_read(vcpu);
+
+	if (!kvm_msr_allowed(vcpu, index, KVM_MSR_FILTER_WRITE) ||
+	    kvm_set_msr(vcpu, index, data)) {
+		trace_kvm_msr_write_ex(index, data);
+		tdvmcall_set_return_code(vcpu, TDVMCALL_INVALID_OPERAND);
+		return 1;
+	}
+
+	trace_kvm_msr_write(index, data);
+	tdvmcall_set_return_code(vcpu, TDVMCALL_SUCCESS);
+	return 1;
+}
+
 static int handle_tdvmcall(struct kvm_vcpu *vcpu)
 {
 	if (tdvmcall_exit_type(vcpu))
@@ -1343,6 +1378,10 @@ static int handle_tdvmcall(struct kvm_vcpu *vcpu)
 		return tdx_emulate_io(vcpu);
 	case EXIT_REASON_EPT_VIOLATION:
 		return tdx_emulate_mmio(vcpu);
+	case EXIT_REASON_MSR_READ:
+		return tdx_emulate_rdmsr(vcpu);
+	case EXIT_REASON_MSR_WRITE:
+		return tdx_emulate_wrmsr(vcpu);
 	default:
 		break;
 	}
Signature · 1 line
2.25.1
#642 lore raw

PATCH v19 113/130 KVM: TDX: Handle MSR MTRRCap and MTRRDefType access

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

Handle MTRRCap RO MSR to return all features are unsupported and handle MTRRDefType MSR to accept only E=1,FE=0,type=writeback. enable MTRR, disable Fixed range MTRRs, default memory type=writeback

TDX virtualizes that cpuid to report MTRR to guest TD and TDX enforces guest CR0.CD=0. If guest tries to set CR0.CD=1, it results in #GP. While updating MTRR requires to set CR0.CD=1 (and other cache flushing operations). It means guest TD can't update MTRR. Virtualize MTRR as all features disabled and default memory type as writeback.

---
 arch/x86/kvm/vmx/tdx.c | 99 ++++++++++++++++++++++++++++++++++--------
 1 file changed, 82 insertions(+), 17 deletions(-)
arch/x86/kvm/vmx/tdx.c +82 -17
index 4c635bfcaf7a..2bddaef495d1 100644
@@ -611,18 +611,7 @@ u8 tdx_get_mt_mask(struct kvm_vcpu *vcpu, gfn_t gfn, bool is_mmio)
 	if (!kvm_arch_has_noncoherent_dma(vcpu->kvm))
 		return (MTRR_TYPE_WRBACK << VMX_EPT_MT_EPTE_SHIFT) | VMX_EPT_IPAT_BIT;
 
-	/*
-	 * TDX enforces CR0.CD = 0 and KVM MTRR emulation enforces writeback.
-	 * TODO: implement MTRR MSR emulation so that
-	 * MTRRCap: SMRR=0: SMRR interface unsupported
-	 *          WC=0: write combining unsupported
-	 *          FIX=0: Fixed range registers unsupported
-	 *          VCNT=0: number of variable range regitsers = 0
-	 * MTRRDefType: E=1, FE=0, type=writeback only. Don't allow other value.
-	 *              E=1: enable MTRR
-	 *              FE=0: disable fixed range MTRRs
-	 *              type: default memory type=writeback
-	 */
+	/* TDX enforces CR0.CD = 0 and KVM MTRR emulation enforces writeback. */
 	return MTRR_TYPE_WRBACK << VMX_EPT_MT_EPTE_SHIFT;
 }
 
@@ -1932,7 +1921,9 @@ bool tdx_has_emulated_msr(u32 index, bool write)
 	case MSR_IA32_UCODE_REV:
 	case MSR_IA32_ARCH_CAPABILITIES:
 	case MSR_IA32_POWER_CTL:
+	case MSR_MTRRcap:
 	case MSR_IA32_CR_PAT:
+	case MSR_MTRRdefType:
 	case MSR_IA32_TSC_DEADLINE:
 	case MSR_IA32_MISC_ENABLE:
 	case MSR_PLATFORM_INFO:
@@ -1974,16 +1965,47 @@ bool tdx_has_emulated_msr(u32 index, bool write)
 
 int tdx_get_msr(struct kvm_vcpu *vcpu, struct msr_data *msr)
 {
-	if (tdx_has_emulated_msr(msr->index, false))
-		return kvm_get_msr_common(vcpu, msr);
-	return 1;
+	switch (msr->index) {
+	case MSR_MTRRcap:
+		/*
+		 * Override kvm_mtrr_get_msr() which hardcodes the value.
+		 * Report SMRR = 0, WC = 0, FIX = 0 VCNT = 0 to disable MTRR
+		 * effectively.
+		 */
+		msr->data = 0;
+		return 0;
+	default:
+		if (tdx_has_emulated_msr(msr->index, false))
+			return kvm_get_msr_common(vcpu, msr);
+		return 1;
+	}
 }
 
 int tdx_set_msr(struct kvm_vcpu *vcpu, struct msr_data *msr)
 {
-	if (tdx_has_emulated_msr(msr->index, true))
+	switch (msr->index) {
+	case MSR_MTRRdefType:
+		/*
+		 * Allow writeback only for all memory.
+		 * Because it's reported that fixed range MTRR isn't supported
+		 * and VCNT=0, enforce MTRRDefType.FE = 0 and don't care
+		 * variable range MTRRs. Only default memory type matters.
+		 *
+		 * bit 11 E: MTRR enable/disable
+		 * bit 12 FE: Fixed-range MTRRs enable/disable
+		 * (E, FE) = (1, 1): enable MTRR and Fixed range MTRR
+		 * (E, FE) = (1, 0): enable MTRR, disable Fixed range MTRR
+		 * (E, FE) = (0, *): disable all MTRRs.  all physical memory
+		 *                   is UC
+		 */
+		if (msr->data != ((1 << 11) | MTRR_TYPE_WRBACK))
+			return 1;
 		return kvm_set_msr_common(vcpu, msr);
-	return 1;
+	default:
+		if (tdx_has_emulated_msr(msr->index, true))
+			return kvm_set_msr_common(vcpu, msr);
+		return 1;
+	}
 }
 
 static int tdx_get_capabilities(struct kvm_tdx_cmd *cmd)
@@ -2704,6 +2726,45 @@ static int tdx_td_vcpu_init(struct kvm_vcpu *vcpu, u64 vcpu_rcx)
 	return ret;
 }
 
+static int tdx_vcpu_init_mtrr(struct kvm_vcpu *vcpu)
+{
+	struct msr_data msr;
+	int ret;
+	int i;
+
+	/*
+	 * To avoid confusion with reporting VNCT = 0, explicitly disable
+	 * vaiale-range reisters.
+	 */
+	for (i = 0; i < KVM_NR_VAR_MTRR; i++) {
+		/* phymask */
+		msr = (struct msr_data) {
+			.host_initiated = true,
+			.index = 0x200 + 2 * i + 1,
+			.data = 0,	/* valid = 0 to disable. */
+		};
+		ret = kvm_set_msr_common(vcpu, &msr);
+		if (ret)
+			return -EINVAL;
+	}
+
+	/* Set MTRR to use writeback on reset. */
+	msr = (struct msr_data) {
+		.host_initiated = true,
+		.index = MSR_MTRRdefType,
+		/*
+		 * Set E(enable MTRR)=1, FE(enable fixed range MTRR)=0, default
+		 * type=writeback on reset to avoid UC.  Note E=0 means all
+		 * memory is UC.
+		 */
+		.data = (1 << 11) | MTRR_TYPE_WRBACK,
+	};
+	ret = kvm_set_msr_common(vcpu, &msr);
+	if (ret)
+		return -EINVAL;
+	return 0;
+}
+
 int tdx_vcpu_ioctl(struct kvm_vcpu *vcpu, void __user *argp)
 {
 	struct msr_data apic_base_msr;
@@ -2741,6 +2802,10 @@ int tdx_vcpu_ioctl(struct kvm_vcpu *vcpu, void __user *argp)
 	if (kvm_set_apic_base(vcpu, &apic_base_msr))
 		return -EINVAL;
 
+	ret = tdx_vcpu_init_mtrr(vcpu);
+	if (ret)
+		return ret;
+
 	ret = tdx_td_vcpu_init(vcpu, (u64)cmd.data);
 	if (ret)
 		return ret;
Signature · 1 line
2.25.1
#643 lore raw

PATCH v19 114/130 KVM: TDX: Handle MSR IA32_FEAT_CTL MSR and IA32_MCG_EXT_CTL

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

MCE and MCA is advertised via cpuid based on the TDX module spec. Guest kernel can access IA32_FEAT_CTL for checking if LMCE is enabled by platform and IA32_MCG_EXT_CTL to enable LMCE. Make TDX KVM handle them. Otherwise guest MSR access to them with TDG.VP.VMCALL<MSR> on VE results in GP in guest.

Because LMCE is disabled with qemu by default, "-cpu lmce=on" to qemu command line is needed to reproduce it.

---
 arch/x86/kvm/vmx/tdx.c | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)
arch/x86/kvm/vmx/tdx.c +20 -0
index 2bddaef495d1..3481c0b6ef2c 100644
@@ -1952,6 +1952,7 @@ bool tdx_has_emulated_msr(u32 index, bool write)
 		default:
 			return true;
 		}
+	case MSR_IA32_FEAT_CTL:
 	case MSR_IA32_APICBASE:
 	case MSR_EFER:
 		return !write;
@@ -1966,6 +1967,20 @@ bool tdx_has_emulated_msr(u32 index, bool write)
 int tdx_get_msr(struct kvm_vcpu *vcpu, struct msr_data *msr)
 {
 	switch (msr->index) {
+	case MSR_IA32_FEAT_CTL:
+		/*
+		 * MCE and MCA are advertised via cpuid. guest kernel could
+		 * check if LMCE is enabled or not.
+		 */
+		msr->data = FEAT_CTL_LOCKED;
+		if (vcpu->arch.mcg_cap & MCG_LMCE_P)
+			msr->data |= FEAT_CTL_LMCE_ENABLED;
+		return 0;
+	case MSR_IA32_MCG_EXT_CTL:
+		if (!msr->host_initiated && !(vcpu->arch.mcg_cap & MCG_LMCE_P))
+			return 1;
+		msr->data = vcpu->arch.mcg_ext_ctl;
+		return 0;
 	case MSR_MTRRcap:
 		/*
 		 * Override kvm_mtrr_get_msr() which hardcodes the value.
@@ -1984,6 +1999,11 @@ int tdx_get_msr(struct kvm_vcpu *vcpu, struct msr_data *msr)
 int tdx_set_msr(struct kvm_vcpu *vcpu, struct msr_data *msr)
 {
 	switch (msr->index) {
+	case MSR_IA32_MCG_EXT_CTL:
+		if (!msr->host_initiated && !(vcpu->arch.mcg_cap & MCG_LMCE_P))
+			return 1;
+		vcpu->arch.mcg_ext_ctl = msr->data;
+		return 0;
 	case MSR_MTRRdefType:
 		/*
 		 * Allow writeback only for all memory.
Signature · 1 line
2.25.1
#644 lore raw

PATCH v19 115/130 KVM: TDX: Handle TDG.VP.VMCALL<GetTdVmCallInfo> hypercall

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

Implement TDG.VP.VMCALL<GetTdVmCallInfo> hypercall. If the input value is zero, return success code and zero in output registers.

TDG.VP.VMCALL<GetTdVmCallInfo> hypercall is a subleaf of TDG.VP.VMCALL to enumerate which TDG.VP.VMCALL sub leaves are supported. This hypercall is for future enhancement of the Guest-Host-Communication Interface (GHCI) specification. The GHCI version of 344426-001US defines it to require input R12 to be zero and to return zero in output registers, R11, R12, R13, and R14 so that guest TD enumerates no enhancement.

---
v19:
- rename TDG_VP_VMCALL_GET_TD_VM_CALL_INFO => TDVMCALL_GET_TD_VM_CALL_INFO

---
 arch/x86/include/asm/shared/tdx.h |  1 +
 arch/x86/kvm/vmx/tdx.c            | 16 ++++++++++++++++
 2 files changed, 17 insertions(+)
arch/x86/include/asm/shared/tdx.h +1 -0
index 28c4a62b7dba..3e8ce567fde0 100644
@@ -22,6 +22,7 @@
 #define TDCS_NOTIFY_ENABLES		0x9100000000000010
 
 /* TDX hypercall Leaf IDs */
+#define TDVMCALL_GET_TD_VM_CALL_INFO	0x10000
 #define TDVMCALL_MAP_GPA		0x10001
 #define TDVMCALL_GET_QUOTE		0x10002
 #define TDVMCALL_REPORT_FATAL_ERROR	0x10003
arch/x86/kvm/vmx/tdx.c +16 -0
index 3481c0b6ef2c..725cb40d0814 100644
@@ -1353,6 +1353,20 @@ static int tdx_emulate_wrmsr(struct kvm_vcpu *vcpu)
 	return 1;
 }
 
+static int tdx_get_td_vm_call_info(struct kvm_vcpu *vcpu)
+{
+	if (tdvmcall_a0_read(vcpu))
+		tdvmcall_set_return_code(vcpu, TDVMCALL_INVALID_OPERAND);
+	else {
+		tdvmcall_set_return_code(vcpu, TDVMCALL_SUCCESS);
+		kvm_r11_write(vcpu, 0);
+		tdvmcall_a0_write(vcpu, 0);
+		tdvmcall_a1_write(vcpu, 0);
+		tdvmcall_a2_write(vcpu, 0);
+	}
+	return 1;
+}
+
 static int handle_tdvmcall(struct kvm_vcpu *vcpu)
 {
 	if (tdvmcall_exit_type(vcpu))
@@ -1371,6 +1385,8 @@ static int handle_tdvmcall(struct kvm_vcpu *vcpu)
 		return tdx_emulate_rdmsr(vcpu);
 	case EXIT_REASON_MSR_WRITE:
 		return tdx_emulate_wrmsr(vcpu);
+	case TDVMCALL_GET_TD_VM_CALL_INFO:
+		return tdx_get_td_vm_call_info(vcpu);
 	default:
 		break;
 	}
Signature · 1 line
2.25.1
#645 lore raw

PATCH v19 116/130 KVM: TDX: Silently discard SMI request

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

TDX doesn't support system-management mode (SMM) and system-management interrupt (SMI) in guest TDs. Because guest state (vcpu state, memory state) is protected, it must go through the TDX module APIs to change guest state, injecting SMI and changing vcpu mode into SMM. The TDX module doesn't provide a way for VMM to inject SMI into guest TD and a way for VMM to switch guest vcpu mode into SMM.

We have two options in KVM when handling SMM or SMI in the guest TD or the device model (e.g. QEMU): 1) silently ignore the request or 2) return a meaningful error.

For simplicity, we implemented the option 1).

---
 arch/x86/kvm/smm.h         |  7 +++++-
 arch/x86/kvm/vmx/main.c    | 45 ++++++++++++++++++++++++++++++++++----
 arch/x86/kvm/vmx/tdx.c     | 29 ++++++++++++++++++++++++
 arch/x86/kvm/vmx/x86_ops.h | 12 ++++++++++
 4 files changed, 88 insertions(+), 5 deletions(-)
arch/x86/kvm/smm.h +6 -1
index a1cf2ac5bd78..bc77902f5c18 100644
@@ -142,7 +142,12 @@ union kvm_smram {
 
 static inline int kvm_inject_smi(struct kvm_vcpu *vcpu)
 {
-	kvm_make_request(KVM_REQ_SMI, vcpu);
+	/*
+	 * If SMM isn't supported (e.g. TDX), silently discard SMI request.
+	 * Assume that SMM supported = MSR_IA32_SMBASE supported.
+	 */
+	if (static_call(kvm_x86_has_emulated_msr)(vcpu->kvm, MSR_IA32_SMBASE))
+		kvm_make_request(KVM_REQ_SMI, vcpu);
 	return 0;
 }
 
arch/x86/kvm/vmx/main.c +41 -4

57 lines not on this page · raw message

arch/x86/kvm/vmx/tdx.c +29 -0
index 725cb40d0814..d9b36373e7d0 100644
@@ -2044,6 +2044,35 @@ int tdx_set_msr(struct kvm_vcpu *vcpu, struct msr_data *msr)
 	}
 }
 
+#ifdef CONFIG_KVM_SMM
+int tdx_smi_allowed(struct kvm_vcpu *vcpu, bool for_injection)
+{
+	/* SMI isn't supported for TDX. */
+	WARN_ON_ONCE(1);
+	return false;
+}
+
+int tdx_enter_smm(struct kvm_vcpu *vcpu, union kvm_smram *smram)
+{
+	/* smi_allowed() is always false for TDX as above. */
+	WARN_ON_ONCE(1);
+	return 0;
+}
+
+int tdx_leave_smm(struct kvm_vcpu *vcpu, const union kvm_smram *smram)
+{
+	WARN_ON_ONCE(1);
+	return 0;
+}
+
+void tdx_enable_smi_window(struct kvm_vcpu *vcpu)
+{
+	/* SMI isn't supported for TDX.  Silently discard SMI request. */
+	WARN_ON_ONCE(1);
+	vcpu->arch.smi_pending = false;
+}
+#endif
+
 static int tdx_get_capabilities(struct kvm_tdx_cmd *cmd)
 {
 	struct kvm_tdx_capabilities __user *user_caps;
arch/x86/kvm/vmx/x86_ops.h +12 -0
index 017a73ab34bb..7c63b2b48125 100644
@@ -241,4 +241,16 @@ int tdx_pre_memory_mapping(struct kvm_vcpu *vcpu,
 void tdx_post_memory_mapping(struct kvm_vcpu *vcpu, struct kvm_memory_mapping *mapping) {}
 #endif
 
+#if defined(CONFIG_INTEL_TDX_HOST) && defined(CONFIG_KVM_SMM)
+int tdx_smi_allowed(struct kvm_vcpu *vcpu, bool for_injection);
+int tdx_enter_smm(struct kvm_vcpu *vcpu, union kvm_smram *smram);
+int tdx_leave_smm(struct kvm_vcpu *vcpu, const union kvm_smram *smram);
+void tdx_enable_smi_window(struct kvm_vcpu *vcpu);
+#else
+static inline int tdx_smi_allowed(struct kvm_vcpu *vcpu, bool for_injection) { return false; }
+static inline int tdx_enter_smm(struct kvm_vcpu *vcpu, union kvm_smram *smram) { return 0; }
+static inline int tdx_leave_smm(struct kvm_vcpu *vcpu, const union kvm_smram *smram) { return 0; }
+static inline void tdx_enable_smi_window(struct kvm_vcpu *vcpu) {}
+#endif
+
 #endif /* __KVM_X86_VMX_X86_OPS_H */
Signature · 1 line
2.25.1
#646 lore raw

Re: [PATCH v19 116/130] KVM: TDX: Silently discard SMI request

Quoted 7 lines · On 2/26/2024 4:26 PM, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

TDX doesn't support system-management mode (SMM) and system-management interrupt (SMI) in guest TDs. Because guest state (vcpu state, memory state) is protected, it must go through the TDX module APIs to change guest state, injecting SMI and changing vcpu mode into SMM. The TDX module doesn't provide a way for VMM to inject SMI into guest TD and a way for VMM

                                                            ^ or

Quoted 10 lines

to switch guest vcpu mode into SMM.

We have two options in KVM when handling SMM or SMI in the guest TD or the device model (e.g. QEMU): 1) silently ignore the request or 2) return a meaningful error.

For simplicity, we implemented the option 1).

---

[...]

arch/x86/kvm/vmx/main.c static void vt_msr_filter_changed(struct kvm_vcpu *vcpu) · line 315

Quoted 6 lines
arch/x86/kvm/vmx/main.c +6 -0
+
+static void vt_enable_smi_window(struct kvm_vcpu *vcpu)
+{
+	if (is_td_vcpu(vcpu)) {
+		tdx_enable_smi_window(vcpu);
+		return;

Can "return tdx_enable_smi_window(vcpu);" directly.

Quoted 7 lines
arch/x86/kvm/vmx/main.c +7 -0
+	}
+
+	/* RSM will cause a vmexit anyway.  */
+	vmx_enable_smi_window(vcpu);
+}
+#endif
+

[...]

arch/x86/kvm/vmx/x86_ops.h int tdx_pre_memory_mapping(struct kvm_vcpu *vcpu, · line 249

Quoted 7 lines
arch/x86/kvm/vmx/x86_ops.h +6 -0
+#if defined(CONFIG_INTEL_TDX_HOST) && defined(CONFIG_KVM_SMM)
+int tdx_smi_allowed(struct kvm_vcpu *vcpu, bool for_injection);
+int tdx_enter_smm(struct kvm_vcpu *vcpu, union kvm_smram *smram);
+int tdx_leave_smm(struct kvm_vcpu *vcpu, const union kvm_smram *smram);
+void tdx_enable_smi_window(struct kvm_vcpu *vcpu);
+#else

#elif defined(CONFIG_KVM_SMM)

These functions are only needed when CONFIG_KVM_SMM is defined.

Quoted 7 lines
arch/x86/kvm/vmx/x86_ops.h +6 -0
+static inline int tdx_smi_allowed(struct kvm_vcpu *vcpu, bool for_injection) { return false; }
+static inline int tdx_enter_smm(struct kvm_vcpu *vcpu, union kvm_smram *smram) { return 0; }
+static inline int tdx_leave_smm(struct kvm_vcpu *vcpu, const union kvm_smram *smram) { return 0; }
+static inline void tdx_enable_smi_window(struct kvm_vcpu *vcpu) {}
+#endif
+
  #endif /* __KVM_X86_VMX_X86_OPS_H */
#647 lore raw

Re: [PATCH v19 116/130] KVM: TDX: Silently discard SMI request

In reply to [email protected]
Quoted 29 lines · On Mon, Feb 26, 2024, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

TDX doesn't support system-management mode (SMM) and system-management interrupt (SMI) in guest TDs. Because guest state (vcpu state, memory state) is protected, it must go through the TDX module APIs to change guest state, injecting SMI and changing vcpu mode into SMM. The TDX module doesn't provide a way for VMM to inject SMI into guest TD and a way for VMM to switch guest vcpu mode into SMM.

We have two options in KVM when handling SMM or SMI in the guest TD or the device model (e.g. QEMU): 1) silently ignore the request or 2) return a meaningful error.

For simplicity, we implemented the option 1).

---
 arch/x86/kvm/smm.h         |  7 +++++-
 arch/x86/kvm/vmx/main.c    | 45 ++++++++++++++++++++++++++++++++++----
 arch/x86/kvm/vmx/tdx.c     | 29 ++++++++++++++++++++++++
 arch/x86/kvm/vmx/x86_ops.h | 12 ++++++++++
 4 files changed, 88 insertions(+), 5 deletions(-)
arch/x86/kvm/smm.h +0 -0
index a1cf2ac5bd78..bc77902f5c18 100644
@@ -142,7 +142,12 @@ union kvm_smram {
 

arch/x86/kvm/smm.h union kvm_smram { · line 151

Quoted 10 lines
arch/x86/kvm/smm.h +6 -1
 static inline int kvm_inject_smi(struct kvm_vcpu *vcpu)
 {
-	kvm_make_request(KVM_REQ_SMI, vcpu);
+	/*
+	 * If SMM isn't supported (e.g. TDX), silently discard SMI request.
+	 * Assume that SMM supported = MSR_IA32_SMBASE supported.
+	 */
+	if (static_call(kvm_x86_has_emulated_msr)(vcpu->kvm, MSR_IA32_SMBASE))
+		kvm_make_request(KVM_REQ_SMI, vcpu);
 	return 0;

No, just do what KVM already does for CONFIG_KVM_SMM=n, and return -ENOTTY. The *entire* point of have a return code is to handle setups that don't support SMM.

	if (!static_call(kvm_x86_has_emulated_msr)(vcpu->kvm, MSR_IA32_SMBASE)))
		return -ENOTTY;

And with that, I would drop the comment, it's pretty darn clear what "assumption" is being made. In quotes because it's not an assumption, it's literally KVM's implementation.

And then the changelog can say "do what KVM does for CONFIG_KVM_SMM=n" without having to explain why we decided to do something completely arbitrary for TDX.

Quoted 7 lines
arch/x86/kvm/smm.h +0 -0
 }
 
arch/x86/kvm/vmx/main.c +0 -0
index ed46e7e57c18..4f3b872cd401 100644

arch/x86/kvm/vmx/main.c static void vt_msr_filter_changed(struct kvm_vcpu *vcpu) · line 290

Quoted 8 lines
arch/x86/kvm/vmx/main.c +5 -0
@@ -283,6 +283,43 @@ static void vt_msr_filter_changed(struct kvm_vcpu *vcpu)
 	vmx_msr_filter_changed(vcpu);
 }
 
+#ifdef CONFIG_KVM_SMM
+static int vt_smi_allowed(struct kvm_vcpu *vcpu, bool for_injection)
+{
+	if (is_td_vcpu(vcpu))
+		return tdx_smi_allowed(vcpu, for_injection);

Adding stubs for something that TDX will never support is silly. Bug the VM and return an error.

	if (KVM_BUG_ON(is_td_vcpu(vcpu)))
		return -EIO;

And I wouldn't even bother with vt_* wrappers, just put that right in vmx_*(). Same thing for everything below.

#648 lore raw

Re: [PATCH v19 116/130] KVM: TDX: Silently discard SMI request

Quoted 47 lines · Sean Christopherson
Quoted 29 lines · On Mon, Feb 26, 2024, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

TDX doesn't support system-management mode (SMM) and system-management interrupt (SMI) in guest TDs. Because guest state (vcpu state, memory state) is protected, it must go through the TDX module APIs to change guest state, injecting SMI and changing vcpu mode into SMM. The TDX module doesn't provide a way for VMM to inject SMI into guest TD and a way for VMM to switch guest vcpu mode into SMM.

We have two options in KVM when handling SMM or SMI in the guest TD or the device model (e.g. QEMU): 1) silently ignore the request or 2) return a meaningful error.

For simplicity, we implemented the option 1).

---
 arch/x86/kvm/smm.h         |  7 +++++-
 arch/x86/kvm/vmx/main.c    | 45 ++++++++++++++++++++++++++++++++++----
 arch/x86/kvm/vmx/tdx.c     | 29 ++++++++++++++++++++++++
 arch/x86/kvm/vmx/x86_ops.h | 12 ++++++++++
 4 files changed, 88 insertions(+), 5 deletions(-)
arch/x86/kvm/smm.h +0 -0
index a1cf2ac5bd78..bc77902f5c18 100644
@@ -142,7 +142,12 @@ union kvm_smram {
 

arch/x86/kvm/smm.h union kvm_smram { · line 151

Quoted 10 lines
arch/x86/kvm/smm.h +6 -1
 static inline int kvm_inject_smi(struct kvm_vcpu *vcpu)
 {
-	kvm_make_request(KVM_REQ_SMI, vcpu);
+	/*
+	 * If SMM isn't supported (e.g. TDX), silently discard SMI request.
+	 * Assume that SMM supported = MSR_IA32_SMBASE supported.
+	 */
+	if (static_call(kvm_x86_has_emulated_msr)(vcpu->kvm, MSR_IA32_SMBASE))
+		kvm_make_request(KVM_REQ_SMI, vcpu);
 	return 0;

No, just do what KVM already does for CONFIG_KVM_SMM=n, and return -ENOTTY. The *entire* point of have a return code is to handle setups that don't support SMM.

	if (!static_call(kvm_x86_has_emulated_msr)(vcpu->kvm, MSR_IA32_SMBASE)))
		return -ENOTTY;
Quoted 6 lines

And with that, I would drop the comment, it's pretty darn clear what "assumption" is being made. In quotes because it's not an assumption, it's literally KVM's implementation.

And then the changelog can say "do what KVM does for CONFIG_KVM_SMM=n" without having to explain why we decided to do something completely arbitrary for TDX.

Ok.

Quoted 19 lines
Quoted 7 lines
arch/x86/kvm/smm.h +0 -0
 }
 
arch/x86/kvm/vmx/main.c +0 -0
index ed46e7e57c18..4f3b872cd401 100644

arch/x86/kvm/vmx/main.c static void vt_msr_filter_changed(struct kvm_vcpu *vcpu) · line 290

Quoted 8 lines
arch/x86/kvm/vmx/main.c +5 -0
@@ -283,6 +283,43 @@ static void vt_msr_filter_changed(struct kvm_vcpu *vcpu)
 	vmx_msr_filter_changed(vcpu);
 }
 
+#ifdef CONFIG_KVM_SMM
+static int vt_smi_allowed(struct kvm_vcpu *vcpu, bool for_injection)
+{
+	if (is_td_vcpu(vcpu))
+		return tdx_smi_allowed(vcpu, for_injection);

Adding stubs for something that TDX will never support is silly. Bug the VM and return an error.

Quoted 5 lines
	if (KVM_BUG_ON(is_td_vcpu(vcpu)))
		return -EIO;

And I wouldn't even bother with vt_* wrappers, just put that right in vmx_*(). Same thing for everything below.

Will drop them. Those are traces to support guest debug. It's future topic and we have arch.guest_state_protected check now.

Signature · 1 line
Isaku Yamahata <[email protected]>
#649 lore raw

Re: [PATCH v19 116/130] KVM: TDX: Silently discard SMI request

In reply to Sean Christopherson
Quoted 71 lines · Sean Christopherson
Quoted 29 lines · On Mon, Feb 26, 2024, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

TDX doesn't support system-management mode (SMM) and system-management interrupt (SMI) in guest TDs. Because guest state (vcpu state, memory state) is protected, it must go through the TDX module APIs to change guest state, injecting SMI and changing vcpu mode into SMM. The TDX module doesn't provide a way for VMM to inject SMI into guest TD and a way for VMM to switch guest vcpu mode into SMM.

We have two options in KVM when handling SMM or SMI in the guest TD or the device model (e.g. QEMU): 1) silently ignore the request or 2) return a meaningful error.

For simplicity, we implemented the option 1).

---
  arch/x86/kvm/smm.h         |  7 +++++-
  arch/x86/kvm/vmx/main.c    | 45 ++++++++++++++++++++++++++++++++++----
  arch/x86/kvm/vmx/tdx.c     | 29 ++++++++++++++++++++++++
  arch/x86/kvm/vmx/x86_ops.h | 12 ++++++++++
  4 files changed, 88 insertions(+), 5 deletions(-)
arch/x86/kvm/smm.h +0 -0
index a1cf2ac5bd78..bc77902f5c18 100644
@@ -142,7 +142,12 @@ union kvm_smram {
 

arch/x86/kvm/smm.h union kvm_smram { · line 151

Quoted 10 lines
arch/x86/kvm/smm.h +6 -1
  static inline int kvm_inject_smi(struct kvm_vcpu *vcpu)
  {
-	kvm_make_request(KVM_REQ_SMI, vcpu);
+	/*
+	 * If SMM isn't supported (e.g. TDX), silently discard SMI request.
+	 * Assume that SMM supported = MSR_IA32_SMBASE supported.
+	 */
+	if (static_call(kvm_x86_has_emulated_msr)(vcpu->kvm, MSR_IA32_SMBASE))
+		kvm_make_request(KVM_REQ_SMI, vcpu);
  	return 0;

No, just do what KVM already does for CONFIG_KVM_SMM=n, and return -ENOTTY. The *entire* point of have a return code is to handle setups that don't support SMM.

	if (!static_call(kvm_x86_has_emulated_msr)(vcpu->kvm, MSR_IA32_SMBASE)))
		return -ENOTTY;

And with that, I would drop the comment, it's pretty darn clear what "assumption" is being made. In quotes because it's not an assumption, it's literally KVM's implementation.

And then the changelog can say "do what KVM does for CONFIG_KVM_SMM=n" without having to explain why we decided to do something completely arbitrary for TDX.

Quoted 7 lines
arch/x86/kvm/smm.h +0 -0
  }
 
arch/x86/kvm/vmx/main.c +0 -0
index ed46e7e57c18..4f3b872cd401 100644

arch/x86/kvm/vmx/main.c static void vt_msr_filter_changed(struct kvm_vcpu *vcpu) · line 290

Quoted 8 lines
arch/x86/kvm/vmx/main.c +5 -0
@@ -283,6 +283,43 @@ static void vt_msr_filter_changed(struct kvm_vcpu *vcpu)
  	vmx_msr_filter_changed(vcpu);
  }
 
+#ifdef CONFIG_KVM_SMM
+static int vt_smi_allowed(struct kvm_vcpu *vcpu, bool for_injection)
+{
+	if (is_td_vcpu(vcpu))
+		return tdx_smi_allowed(vcpu, for_injection);

Adding stubs for something that TDX will never support is silly. Bug the VM and return an error.

Quoted 2 lines
	if (KVM_BUG_ON(is_td_vcpu(vcpu)))
		return -EIO;

is_td_vcpu() is defined in tdx.h.
Do you mind using open code to check whether the VM is TD in vmx.c? "vcpu->kvm->arch.vm_type == KVM_X86_TDX_VM"

Quoted 4 lines

And I wouldn't even bother with vt_* wrappers, just put that right in vmx_*(). Same thing for everything below.

maintainer
#650 lore raw

Re: [PATCH v19 116/130] KVM: TDX: Silently discard SMI request

Quoted 18 lines · Binbin Wu
Quoted 18 lines
Quoted 7 lines
  }
arch/x86/kvm/vmx/main.c +0 -0
index ed46e7e57c18..4f3b872cd401 100644

arch/x86/kvm/vmx/main.c static void vt_msr_filter_changed(struct kvm_vcpu *vcpu) · line 290

Quoted 8 lines
arch/x86/kvm/vmx/main.c +5 -0
@@ -283,6 +283,43 @@ static void vt_msr_filter_changed(struct kvm_vcpu *vcpu)
     vmx_msr_filter_changed(vcpu);
  }
 
+#ifdef CONFIG_KVM_SMM
+static int vt_smi_allowed(struct kvm_vcpu *vcpu, bool for_injection)
+{
+    if (is_td_vcpu(vcpu))
+            return tdx_smi_allowed(vcpu, for_injection);

Adding stubs for something that TDX will never support is silly. Bug the VM and return an error.

Quoted 6 lines
Quoted 2 lines
      if (KVM_BUG_ON(is_td_vcpu(vcpu)))
              return -EIO;

is_td_vcpu() is defined in tdx.h.
Do you mind using open code to check whether the VM is TD in vmx.c? "vcpu->kvm->arch.vm_type == KVM_X86_TDX_VM"

I'd move it to some place that main.c can see. Or vmx.c as Sean says below, but I am not sure I like the idea too much.

Paolo

Quoted 2 lines
Quoted 2 lines

And I wouldn't even bother with vt_* wrappers, just put that right in vmx_*(). Same thing for everything below.

If it's a KVM_BUG_ON()

#651 lore raw

Re: [PATCH v19 116/130] KVM: TDX: Silently discard SMI request

Quoted 19 lines · Paolo Bonzini
Quoted 18 lines · Binbin Wu
Quoted 18 lines
Quoted 7 lines
   }
arch/x86/kvm/vmx/main.c +0 -0
index ed46e7e57c18..4f3b872cd401 100644

arch/x86/kvm/vmx/main.c static void vt_msr_filter_changed(struct kvm_vcpu *vcpu) · line 290

Quoted 8 lines
arch/x86/kvm/vmx/main.c +5 -0
@@ -283,6 +283,43 @@ static void vt_msr_filter_changed(struct kvm_vcpu *vcpu)
      vmx_msr_filter_changed(vcpu);
   }
 
+#ifdef CONFIG_KVM_SMM
+static int vt_smi_allowed(struct kvm_vcpu *vcpu, bool for_injection)
+{
+    if (is_td_vcpu(vcpu))
+            return tdx_smi_allowed(vcpu, for_injection);

Adding stubs for something that TDX will never support is silly. Bug the VM and return an error.

Quoted 6 lines
Quoted 5 lines
Quoted 2 lines
       if (KVM_BUG_ON(is_td_vcpu(vcpu)))
               return -EIO;

is_td_vcpu() is defined in tdx.h.
Do you mind using open code to check whether the VM is TD in vmx.c? "vcpu->kvm->arch.vm_type == KVM_X86_TDX_VM"

I'd move it to some place that main.c can see.

is_td_vcpu() can be seen in main.c

Quoted 2 lines

Or vmx.c as Sean says
below, but I am not sure I like the idea too much.

Which you may not like? Remove the vt_* wrapper or use KVM_BUG_ON()?

Quoted 7 lines

Paolo

Quoted 2 lines
Quoted 2 lines

And I wouldn't even bother with vt_* wrappers, just put that right in vmx_*(). Same thing for everything below.

If it's a KVM_BUG_ON()

maintainer
#652 lore raw

Re: [PATCH v19 116/130] KVM: TDX: Silently discard SMI request

Quoted 8 lines · Binbin Wu
Quoted 5 lines · Paolo Bonzini
Quoted 3 lines · Binbin Wu

is_td_vcpu() is defined in tdx.h.
Do you mind using open code to check whether the VM is TD in vmx.c? "vcpu->kvm->arch.vm_type == KVM_X86_TDX_VM"

I'd move it to some place that main.c can see.

is_td_vcpu() can be seen in main.c

Ok.

Quoted 4 lines
Quoted 2 lines

Or vmx.c as Sean says
below, but I am not sure I like the idea too much.

Which you may not like? Remove the vt_* wrapper or use KVM_BUG_ON()?

Removing the vt_* wrapper.

Paolo

#653 lore raw

PATCH v19 117/130 KVM: TDX: Silently ignore INIT/SIPI

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

The TDX module API doesn't provide API for VMM to inject INIT IPI and SIPI. Instead it defines the different protocols to boot application processors. Ignore INIT and SIPI events for the TDX guest.

There are two options. 1) (silently) ignore INIT/SIPI request or 2) return error to guest TDs somehow. Given that TDX guest is paravirtualized to boot AP, the option 1 is chosen for simplicity.

---
 arch/x86/include/asm/kvm-x86-ops.h |  1 +
 arch/x86/include/asm/kvm_host.h    |  2 ++
 arch/x86/kvm/lapic.c               | 19 +++++++++++-------
 arch/x86/kvm/svm/svm.c             |  1 +
 arch/x86/kvm/vmx/main.c            | 32 ++++++++++++++++++++++++++++--
 arch/x86/kvm/vmx/tdx.c             |  4 ++--
 6 files changed, 48 insertions(+), 11 deletions(-)
arch/x86/include/asm/kvm-x86-ops.h +1 -0
index 22d93d4124c8..85c04aad6ab3 100644
@@ -149,6 +149,7 @@ KVM_X86_OP_OPTIONAL(migrate_timers)
 KVM_X86_OP(msr_filter_changed)
 KVM_X86_OP(complete_emulated_msr)
 KVM_X86_OP(vcpu_deliver_sipi_vector)
+KVM_X86_OP(vcpu_deliver_init)
 KVM_X86_OP_OPTIONAL_RET0(vcpu_get_apicv_inhibit_reasons);
 KVM_X86_OP_OPTIONAL(get_untagged_addr)
 KVM_X86_OP_OPTIONAL_RET0(gmem_max_level)
arch/x86/include/asm/kvm_host.h +2 -0
index bb8be091f996..2686c080820b 100644
@@ -1836,6 +1836,7 @@ struct kvm_x86_ops {
 	int (*complete_emulated_msr)(struct kvm_vcpu *vcpu, int err);
 
 	void (*vcpu_deliver_sipi_vector)(struct kvm_vcpu *vcpu, u8 vector);
+	void (*vcpu_deliver_init)(struct kvm_vcpu *vcpu);
 
 	/*
 	 * Returns vCPU specific APICv inhibit reasons
@@ -2092,6 +2093,7 @@ void kvm_get_segment(struct kvm_vcpu *vcpu, struct kvm_segment *var, int seg);
 void kvm_set_segment(struct kvm_vcpu *vcpu, struct kvm_segment *var, int seg);
 int kvm_load_segment_descriptor(struct kvm_vcpu *vcpu, u16 selector, int seg);
 void kvm_vcpu_deliver_sipi_vector(struct kvm_vcpu *vcpu, u8 vector);
+void kvm_vcpu_deliver_init(struct kvm_vcpu *vcpu);
 
 int kvm_task_switch(struct kvm_vcpu *vcpu, u16 tss_selector, int idt_index,
 		    int reason, bool has_error_code, u32 error_code);
arch/x86/kvm/lapic.c +12 -7
index 8025c7f614e0..431074679e83 100644
@@ -3268,6 +3268,16 @@ int kvm_lapic_set_pv_eoi(struct kvm_vcpu *vcpu, u64 data, unsigned long len)
 	return 0;
 }
 
+void kvm_vcpu_deliver_init(struct kvm_vcpu *vcpu)
+{
+	kvm_vcpu_reset(vcpu, true);
+	if (kvm_vcpu_is_bsp(vcpu))
+		vcpu->arch.mp_state = KVM_MP_STATE_RUNNABLE;
+	else
+		vcpu->arch.mp_state = KVM_MP_STATE_INIT_RECEIVED;
+}
+EXPORT_SYMBOL_GPL(kvm_vcpu_deliver_init);
+
 int kvm_apic_accept_events(struct kvm_vcpu *vcpu)
 {
 	struct kvm_lapic *apic = vcpu->arch.apic;
@@ -3299,13 +3309,8 @@ int kvm_apic_accept_events(struct kvm_vcpu *vcpu)
 		return 0;
 	}
 
-	if (test_and_clear_bit(KVM_APIC_INIT, &apic->pending_events)) {
-		kvm_vcpu_reset(vcpu, true);
-		if (kvm_vcpu_is_bsp(apic->vcpu))
-			vcpu->arch.mp_state = KVM_MP_STATE_RUNNABLE;
-		else
-			vcpu->arch.mp_state = KVM_MP_STATE_INIT_RECEIVED;
-	}
+	if (test_and_clear_bit(KVM_APIC_INIT, &apic->pending_events))
+		static_call(kvm_x86_vcpu_deliver_init)(vcpu);
 	if (test_and_clear_bit(KVM_APIC_SIPI, &apic->pending_events)) {
 		if (vcpu->arch.mp_state == KVM_MP_STATE_INIT_RECEIVED) {
 			/* evaluate pending_events before reading the vector */
arch/x86/kvm/svm/svm.c +1 -0
index f76dd52d29ba..27546d993809 100644
@@ -5037,6 +5037,7 @@ static struct kvm_x86_ops svm_x86_ops __initdata = {
 	.complete_emulated_msr = svm_complete_emulated_msr,
 
 	.vcpu_deliver_sipi_vector = svm_vcpu_deliver_sipi_vector,
+	.vcpu_deliver_init = kvm_vcpu_deliver_init,
 	.vcpu_get_apicv_inhibit_reasons = avic_vcpu_get_apicv_inhibit_reasons,
 };
 
arch/x86/kvm/vmx/main.c +30 -2
index 4f3b872cd401..84d2dc818cf7 100644
@@ -320,6 +320,14 @@ static void vt_enable_smi_window(struct kvm_vcpu *vcpu)
 }
 #endif
 
+static bool vt_apic_init_signal_blocked(struct kvm_vcpu *vcpu)
+{
+	if (is_td_vcpu(vcpu))
+		return true;
+
+	return vmx_apic_init_signal_blocked(vcpu);
+}
+
 static void vt_apicv_pre_state_restore(struct kvm_vcpu *vcpu)
 {
 	struct pi_desc *pi = vcpu_to_pi_desc(vcpu);
@@ -348,6 +356,25 @@ static void vt_deliver_interrupt(struct kvm_lapic *apic, int delivery_mode,
 	vmx_deliver_interrupt(apic, delivery_mode, trig_mode, vector);
 }
 
+static void vt_vcpu_deliver_sipi_vector(struct kvm_vcpu *vcpu, u8 vector)
+{
+	if (is_td_vcpu(vcpu))
+		return;
+
+	kvm_vcpu_deliver_sipi_vector(vcpu, vector);
+}
+
+static void vt_vcpu_deliver_init(struct kvm_vcpu *vcpu)
+{
+	if (is_td_vcpu(vcpu)) {
+		/* TDX doesn't support INIT.  Ignore INIT event */
+		vcpu->arch.mp_state = KVM_MP_STATE_RUNNABLE;
+		return;
+	}
+
+	kvm_vcpu_deliver_init(vcpu);
+}
+
 static void vt_flush_tlb_all(struct kvm_vcpu *vcpu)
 {
 	if (is_td_vcpu(vcpu)) {
@@ -744,13 +771,14 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
 #endif
 
 	.check_emulate_instruction = vmx_check_emulate_instruction,
-	.apic_init_signal_blocked = vmx_apic_init_signal_blocked,
+	.apic_init_signal_blocked = vt_apic_init_signal_blocked,
 	.migrate_timers = vmx_migrate_timers,
 
 	.msr_filter_changed = vt_msr_filter_changed,
 	.complete_emulated_msr = kvm_complete_insn_gp,
 
-	.vcpu_deliver_sipi_vector = kvm_vcpu_deliver_sipi_vector,
+	.vcpu_deliver_sipi_vector = vt_vcpu_deliver_sipi_vector,
+	.vcpu_deliver_init = vt_vcpu_deliver_init,
 
 	.get_untagged_addr = vmx_get_untagged_addr,
 
arch/x86/kvm/vmx/tdx.c +2 -2
index d9b36373e7d0..4c7c83105342 100644
@@ -769,8 +769,8 @@ void tdx_vcpu_free(struct kvm_vcpu *vcpu)
 void tdx_vcpu_reset(struct kvm_vcpu *vcpu, bool init_event)
 {
 
-	/* Ignore INIT silently because TDX doesn't support INIT event. */
-	if (init_event)
+	/* vcpu_deliver_init method silently discards INIT event. */
+	if (KVM_BUG_ON(init_event, vcpu->kvm))
 		return;
 	if (KVM_BUG_ON(is_td_vcpu_created(to_tdx(vcpu)), vcpu->kvm))
 		return;
Signature · 1 line
2.25.1
#654 lore raw

Re: [PATCH v19 117/130] KVM: TDX: Silently ignore INIT/SIPI

Quoted 12 lines · On 2/26/2024 4:26 PM, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

The TDX module API doesn't provide API for VMM to inject INIT IPI and SIPI. Instead it defines the different protocols to boot application processors. Ignore INIT and SIPI events for the TDX guest.

There are two options. 1) (silently) ignore INIT/SIPI request or 2) return error to guest TDs somehow. Given that TDX guest is paravirtualized to boot AP, the option 1 is chosen for simplicity.

---

[...]

arch/x86/kvm/vmx/main.c static void vt_deliver_interrupt(struct kvm_lapic *apic, int delivery_mode, · line 371

Quoted 6 lines
arch/x86/kvm/vmx/main.c +6 -0
+
+static void vt_vcpu_deliver_init(struct kvm_vcpu *vcpu)
+{
+	if (is_td_vcpu(vcpu)) {
+		/* TDX doesn't support INIT.  Ignore INIT event */
+		vcpu->arch.mp_state = KVM_MP_STATE_RUNNABLE;

Why change the mp_state to KVM_MP_STATE_RUNNABLE here?

And I am not sure whether we can say the INIT event is ignored since mp_state could be modified.

Quoted 6 lines
arch/x86/kvm/vmx/main.c +6 -0
+		return;
+	}
+
+	kvm_vcpu_deliver_init(vcpu);
+}
+
#655 lore raw

Re: [PATCH v19 117/130] KVM: TDX: Silently ignore INIT/SIPI

Quoted 24 lines · Binbin Wu
Quoted 12 lines · On 2/26/2024 4:26 PM, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

The TDX module API doesn't provide API for VMM to inject INIT IPI and SIPI. Instead it defines the different protocols to boot application processors. Ignore INIT and SIPI events for the TDX guest.

There are two options. 1) (silently) ignore INIT/SIPI request or 2) return error to guest TDs somehow. Given that TDX guest is paravirtualized to boot AP, the option 1 is chosen for simplicity.

---

[...]

arch/x86/kvm/vmx/main.c static void vt_deliver_interrupt(struct kvm_lapic *apic, int delivery_mode, · line 371

Quoted 6 lines
arch/x86/kvm/vmx/main.c +6 -0
+
+static void vt_vcpu_deliver_init(struct kvm_vcpu *vcpu)
+{
+	if (is_td_vcpu(vcpu)) {
+		/* TDX doesn't support INIT.  Ignore INIT event */
+		vcpu->arch.mp_state = KVM_MP_STATE_RUNNABLE;

Why change the mp_state to KVM_MP_STATE_RUNNABLE here?

Quoted 2 lines

And I am not sure whether we can say the INIT event is ignored since mp_state could be modified.

We should drop the line. Now it's not necessary and KVM_TDX_INIT_VCPU change it.

Signature · 1 line
Isaku Yamahata <[email protected]>
#656 lore raw

Re: [PATCH v19 117/130] KVM: TDX: Silently ignore INIT/SIPI

In reply to [email protected]
Quoted 106 lines · On 2/26/2024 4:26 PM, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

The TDX module API doesn't provide API for VMM to inject INIT IPI and SIPI. Instead it defines the different protocols to boot application processors. Ignore INIT and SIPI events for the TDX guest.

There are two options. 1) (silently) ignore INIT/SIPI request or 2) return error to guest TDs somehow. Given that TDX guest is paravirtualized to boot AP, the option 1 is chosen for simplicity.

---
  arch/x86/include/asm/kvm-x86-ops.h |  1 +
  arch/x86/include/asm/kvm_host.h    |  2 ++
  arch/x86/kvm/lapic.c               | 19 +++++++++++-------
  arch/x86/kvm/svm/svm.c             |  1 +
  arch/x86/kvm/vmx/main.c            | 32 ++++++++++++++++++++++++++++--
  arch/x86/kvm/vmx/tdx.c             |  4 ++--
  6 files changed, 48 insertions(+), 11 deletions(-)
arch/x86/include/asm/kvm-x86-ops.h +1 -0
index 22d93d4124c8..85c04aad6ab3 100644
@@ -149,6 +149,7 @@ KVM_X86_OP_OPTIONAL(migrate_timers)
  KVM_X86_OP(msr_filter_changed)
  KVM_X86_OP(complete_emulated_msr)
  KVM_X86_OP(vcpu_deliver_sipi_vector)
+KVM_X86_OP(vcpu_deliver_init)
  KVM_X86_OP_OPTIONAL_RET0(vcpu_get_apicv_inhibit_reasons);
  KVM_X86_OP_OPTIONAL(get_untagged_addr)
  KVM_X86_OP_OPTIONAL_RET0(gmem_max_level)
arch/x86/include/asm/kvm_host.h +2 -0
index bb8be091f996..2686c080820b 100644
@@ -1836,6 +1836,7 @@ struct kvm_x86_ops {
  	int (*complete_emulated_msr)(struct kvm_vcpu *vcpu, int err);
 
  	void (*vcpu_deliver_sipi_vector)(struct kvm_vcpu *vcpu, u8 vector);
+	void (*vcpu_deliver_init)(struct kvm_vcpu *vcpu);
 
  	/*
  	 * Returns vCPU specific APICv inhibit reasons
@@ -2092,6 +2093,7 @@ void kvm_get_segment(struct kvm_vcpu *vcpu, struct kvm_segment *var, int seg);
  void kvm_set_segment(struct kvm_vcpu *vcpu, struct kvm_segment *var, int seg);
  int kvm_load_segment_descriptor(struct kvm_vcpu *vcpu, u16 selector, int seg);
  void kvm_vcpu_deliver_sipi_vector(struct kvm_vcpu *vcpu, u8 vector);
+void kvm_vcpu_deliver_init(struct kvm_vcpu *vcpu);
 
  int kvm_task_switch(struct kvm_vcpu *vcpu, u16 tss_selector, int idt_index,
  		    int reason, bool has_error_code, u32 error_code);
arch/x86/kvm/lapic.c +12 -7
index 8025c7f614e0..431074679e83 100644
@@ -3268,6 +3268,16 @@ int kvm_lapic_set_pv_eoi(struct kvm_vcpu *vcpu, u64 data, unsigned long len)
  	return 0;
  }
 
+void kvm_vcpu_deliver_init(struct kvm_vcpu *vcpu)
+{
+	kvm_vcpu_reset(vcpu, true);
+	if (kvm_vcpu_is_bsp(vcpu))
+		vcpu->arch.mp_state = KVM_MP_STATE_RUNNABLE;
+	else
+		vcpu->arch.mp_state = KVM_MP_STATE_INIT_RECEIVED;
+}
+EXPORT_SYMBOL_GPL(kvm_vcpu_deliver_init);
+
  int kvm_apic_accept_events(struct kvm_vcpu *vcpu)
  {
  	struct kvm_lapic *apic = vcpu->arch.apic;
@@ -3299,13 +3309,8 @@ int kvm_apic_accept_events(struct kvm_vcpu *vcpu)
  		return 0;
  	}
 
-	if (test_and_clear_bit(KVM_APIC_INIT, &apic->pending_events)) {
-		kvm_vcpu_reset(vcpu, true);
-		if (kvm_vcpu_is_bsp(apic->vcpu))
-			vcpu->arch.mp_state = KVM_MP_STATE_RUNNABLE;
-		else
-			vcpu->arch.mp_state = KVM_MP_STATE_INIT_RECEIVED;
-	}
+	if (test_and_clear_bit(KVM_APIC_INIT, &apic->pending_events))
+		static_call(kvm_x86_vcpu_deliver_init)(vcpu);
  	if (test_and_clear_bit(KVM_APIC_SIPI, &apic->pending_events)) {
  		if (vcpu->arch.mp_state == KVM_MP_STATE_INIT_RECEIVED) {
  			/* evaluate pending_events before reading the vector */
arch/x86/kvm/svm/svm.c +1 -0
index f76dd52d29ba..27546d993809 100644
@@ -5037,6 +5037,7 @@ static struct kvm_x86_ops svm_x86_ops __initdata = {
  	.complete_emulated_msr = svm_complete_emulated_msr,
 
  	.vcpu_deliver_sipi_vector = svm_vcpu_deliver_sipi_vector,
+	.vcpu_deliver_init = kvm_vcpu_deliver_init,
  	.vcpu_get_apicv_inhibit_reasons = avic_vcpu_get_apicv_inhibit_reasons,
  };
 
arch/x86/kvm/vmx/main.c +0 -0
index 4f3b872cd401..84d2dc818cf7 100644

arch/x86/kvm/vmx/main.c static void vt_enable_smi_window(struct kvm_vcpu *vcpu) · line 326

Quoted 7 lines
arch/x86/kvm/vmx/main.c +4 -0
@@ -320,6 +320,14 @@ static void vt_enable_smi_window(struct kvm_vcpu *vcpu)
  }
  #endif
 
+static bool vt_apic_init_signal_blocked(struct kvm_vcpu *vcpu)
+{
+	if (is_td_vcpu(vcpu))
+		return true;

Since for TD, INIT is always blocked, then in kvm_apic_accept_events(), the code path to handle INIT/SIPI delivery will not be called, i.e, the OPs .vcpu_deliver_init() and .vcpu_deliver_sipi_vector() are never called for TD. Seems no need to add the new interface  vcpu_deliver_init or the new wrapper vt_vcpu_deliver_sipi_vector().

And consider the INIT/SIPI for TD:
- Normally, for TD, INIT ans SIPI should not be set in APIC's pending_events.
  Maybe we can call KVM_BUG_ON() in vt_apic_init_signal_blocked() for TD?
- If INIT and SIPI are allowed be set in APIC's pending_events for somehow, the current code has a problem, it will never clear INIT bit in APIC's pending_events. Then kvm_apic_accept_events() needs to execute more check code if INIT was once set.
  INIT bit should be cleared with this assumption.

Quoted 65 lines
arch/x86/kvm/vmx/main.c +26 -2
+
+	return vmx_apic_init_signal_blocked(vcpu);
+}
+
  static void vt_apicv_pre_state_restore(struct kvm_vcpu *vcpu)
  {
  	struct pi_desc *pi = vcpu_to_pi_desc(vcpu);
@@ -348,6 +356,25 @@ static void vt_deliver_interrupt(struct kvm_lapic *apic, int delivery_mode,
  	vmx_deliver_interrupt(apic, delivery_mode, trig_mode, vector);
  }
 
+static void vt_vcpu_deliver_sipi_vector(struct kvm_vcpu *vcpu, u8 vector)
+{
+	if (is_td_vcpu(vcpu))
+		return;
+
+	kvm_vcpu_deliver_sipi_vector(vcpu, vector);
+}
+
+static void vt_vcpu_deliver_init(struct kvm_vcpu *vcpu)
+{
+	if (is_td_vcpu(vcpu)) {
+		/* TDX doesn't support INIT.  Ignore INIT event */
+		vcpu->arch.mp_state = KVM_MP_STATE_RUNNABLE;
+		return;
+	}
+
+	kvm_vcpu_deliver_init(vcpu);
+}
+
  static void vt_flush_tlb_all(struct kvm_vcpu *vcpu)
  {
  	if (is_td_vcpu(vcpu)) {
@@ -744,13 +771,14 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
  #endif
 
  	.check_emulate_instruction = vmx_check_emulate_instruction,
-	.apic_init_signal_blocked = vmx_apic_init_signal_blocked,
+	.apic_init_signal_blocked = vt_apic_init_signal_blocked,
  	.migrate_timers = vmx_migrate_timers,
 
  	.msr_filter_changed = vt_msr_filter_changed,
  	.complete_emulated_msr = kvm_complete_insn_gp,
 
-	.vcpu_deliver_sipi_vector = kvm_vcpu_deliver_sipi_vector,
+	.vcpu_deliver_sipi_vector = vt_vcpu_deliver_sipi_vector,
+	.vcpu_deliver_init = vt_vcpu_deliver_init,
 
  	.get_untagged_addr = vmx_get_untagged_addr,
 
arch/x86/kvm/vmx/tdx.c +2 -2
index d9b36373e7d0..4c7c83105342 100644
@@ -769,8 +769,8 @@ void tdx_vcpu_free(struct kvm_vcpu *vcpu)
  void tdx_vcpu_reset(struct kvm_vcpu *vcpu, bool init_event)
  {
 
-	/* Ignore INIT silently because TDX doesn't support INIT event. */
-	if (init_event)
+	/* vcpu_deliver_init method silently discards INIT event. */
+	if (KVM_BUG_ON(init_event, vcpu->kvm))
  		return;
  	if (KVM_BUG_ON(is_td_vcpu_created(to_tdx(vcpu)), vcpu->kvm))
  		return;
#657 lore raw

Re: [PATCH v19 117/130] KVM: TDX: Silently ignore INIT/SIPI

Quoted 116 lines · Binbin Wu
Quoted 99 lines · On 2/26/2024 4:26 PM, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

The TDX module API doesn't provide API for VMM to inject INIT IPI and SIPI. Instead it defines the different protocols to boot application processors. Ignore INIT and SIPI events for the TDX guest.

There are two options. 1) (silently) ignore INIT/SIPI request or 2) return error to guest TDs somehow. Given that TDX guest is paravirtualized to boot AP, the option 1 is chosen for simplicity.

---
  arch/x86/include/asm/kvm-x86-ops.h |  1 +
  arch/x86/include/asm/kvm_host.h    |  2 ++
  arch/x86/kvm/lapic.c               | 19 +++++++++++-------
  arch/x86/kvm/svm/svm.c             |  1 +
  arch/x86/kvm/vmx/main.c            | 32 ++++++++++++++++++++++++++++--
  arch/x86/kvm/vmx/tdx.c             |  4 ++--
  6 files changed, 48 insertions(+), 11 deletions(-)
arch/x86/include/asm/kvm-x86-ops.h +1 -0
index 22d93d4124c8..85c04aad6ab3 100644
@@ -149,6 +149,7 @@ KVM_X86_OP_OPTIONAL(migrate_timers)
  KVM_X86_OP(msr_filter_changed)
  KVM_X86_OP(complete_emulated_msr)
  KVM_X86_OP(vcpu_deliver_sipi_vector)
+KVM_X86_OP(vcpu_deliver_init)
  KVM_X86_OP_OPTIONAL_RET0(vcpu_get_apicv_inhibit_reasons);
  KVM_X86_OP_OPTIONAL(get_untagged_addr)
  KVM_X86_OP_OPTIONAL_RET0(gmem_max_level)
arch/x86/include/asm/kvm_host.h +2 -0
index bb8be091f996..2686c080820b 100644
@@ -1836,6 +1836,7 @@ struct kvm_x86_ops {
  	int (*complete_emulated_msr)(struct kvm_vcpu *vcpu, int err);
  	void (*vcpu_deliver_sipi_vector)(struct kvm_vcpu *vcpu, u8 vector);
+	void (*vcpu_deliver_init)(struct kvm_vcpu *vcpu);
  	/*
  	 * Returns vCPU specific APICv inhibit reasons
@@ -2092,6 +2093,7 @@ void kvm_get_segment(struct kvm_vcpu *vcpu, struct kvm_segment *var, int seg);
  void kvm_set_segment(struct kvm_vcpu *vcpu, struct kvm_segment *var, int seg);
  int kvm_load_segment_descriptor(struct kvm_vcpu *vcpu, u16 selector, int seg);
  void kvm_vcpu_deliver_sipi_vector(struct kvm_vcpu *vcpu, u8 vector);
+void kvm_vcpu_deliver_init(struct kvm_vcpu *vcpu);
  int kvm_task_switch(struct kvm_vcpu *vcpu, u16 tss_selector, int idt_index,
  		    int reason, bool has_error_code, u32 error_code);
arch/x86/kvm/lapic.c +12 -7
index 8025c7f614e0..431074679e83 100644
@@ -3268,6 +3268,16 @@ int kvm_lapic_set_pv_eoi(struct kvm_vcpu *vcpu, u64 data, unsigned long len)
  	return 0;
  }
+void kvm_vcpu_deliver_init(struct kvm_vcpu *vcpu)
+{
+	kvm_vcpu_reset(vcpu, true);
+	if (kvm_vcpu_is_bsp(vcpu))
+		vcpu->arch.mp_state = KVM_MP_STATE_RUNNABLE;
+	else
+		vcpu->arch.mp_state = KVM_MP_STATE_INIT_RECEIVED;
+}
+EXPORT_SYMBOL_GPL(kvm_vcpu_deliver_init);
+
  int kvm_apic_accept_events(struct kvm_vcpu *vcpu)
  {
  	struct kvm_lapic *apic = vcpu->arch.apic;
@@ -3299,13 +3309,8 @@ int kvm_apic_accept_events(struct kvm_vcpu *vcpu)
  		return 0;
  	}
-	if (test_and_clear_bit(KVM_APIC_INIT, &apic->pending_events)) {
-		kvm_vcpu_reset(vcpu, true);
-		if (kvm_vcpu_is_bsp(apic->vcpu))
-			vcpu->arch.mp_state = KVM_MP_STATE_RUNNABLE;
-		else
-			vcpu->arch.mp_state = KVM_MP_STATE_INIT_RECEIVED;
-	}
+	if (test_and_clear_bit(KVM_APIC_INIT, &apic->pending_events))
+		static_call(kvm_x86_vcpu_deliver_init)(vcpu);
  	if (test_and_clear_bit(KVM_APIC_SIPI, &apic->pending_events)) {
  		if (vcpu->arch.mp_state == KVM_MP_STATE_INIT_RECEIVED) {
  			/* evaluate pending_events before reading the vector */
arch/x86/kvm/svm/svm.c +1 -0
index f76dd52d29ba..27546d993809 100644
@@ -5037,6 +5037,7 @@ static struct kvm_x86_ops svm_x86_ops __initdata = {
  	.complete_emulated_msr = svm_complete_emulated_msr,
  	.vcpu_deliver_sipi_vector = svm_vcpu_deliver_sipi_vector,
+	.vcpu_deliver_init = kvm_vcpu_deliver_init,
  	.vcpu_get_apicv_inhibit_reasons = avic_vcpu_get_apicv_inhibit_reasons,
  };
arch/x86/kvm/vmx/main.c +0 -0
index 4f3b872cd401..84d2dc818cf7 100644

arch/x86/kvm/vmx/main.c static void vt_enable_smi_window(struct kvm_vcpu *vcpu) · line 325

Quoted 6 lines
arch/x86/kvm/vmx/main.c +4 -0
@@ -320,6 +320,14 @@ static void vt_enable_smi_window(struct kvm_vcpu *vcpu)
  }
  #endif
+static bool vt_apic_init_signal_blocked(struct kvm_vcpu *vcpu)
+{
+	if (is_td_vcpu(vcpu))
+		return true;

Since for TD, INIT is always blocked, then in kvm_apic_accept_events(), the code path to handle INIT/SIPI delivery will not be called, i.e, the OPs .vcpu_deliver_init() and .vcpu_deliver_sipi_vector() are never called for TD.
Seems no need to add the new interface  vcpu_deliver_init or the new wrapper vt_vcpu_deliver_sipi_vector().

Quoted 10 lines

And consider the INIT/SIPI for TD:
- Normally, for TD, INIT ans SIPI should not be set in APIC's pending_events.
  Maybe we can call KVM_BUG_ON() in vt_apic_init_signal_blocked() for TD?
- If INIT and SIPI are allowed be set in APIC's pending_events for somehow, the current code has a problem, it will never clear INIT bit in APIC's pending_events.
  Then kvm_apic_accept_events() needs to execute more check code if INIT was once set.
  INIT bit should be cleared with this assumption.

KVM_SET_MP_STATE and KVM_SET_VCPU_EVENTS can set INIT/SIPI by the user space. If we change those two IOCTLs to reject INIT/SIPI for TDX, we can go for the above "Normally" option. Because I didn't want to touch the common functions, I ended in extra callbacks. The user space shouldn't use those two IOCTLs for TDX, though.

Signature · 1 line
Isaku Yamahata <[email protected]>
#658 lore raw

Re: [PATCH v19 117/130] KVM: TDX: Silently ignore INIT/SIPI

Quoted 128 lines · Isaku Yamahata
Quoted 114 lines · Binbin Wu
Quoted 99 lines · On 2/26/2024 4:26 PM, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

The TDX module API doesn't provide API for VMM to inject INIT IPI and SIPI. Instead it defines the different protocols to boot application processors. Ignore INIT and SIPI events for the TDX guest.

There are two options. 1) (silently) ignore INIT/SIPI request or 2) return error to guest TDs somehow. Given that TDX guest is paravirtualized to boot AP, the option 1 is chosen for simplicity.

---
   arch/x86/include/asm/kvm-x86-ops.h |  1 +
   arch/x86/include/asm/kvm_host.h    |  2 ++
   arch/x86/kvm/lapic.c               | 19 +++++++++++-------
   arch/x86/kvm/svm/svm.c             |  1 +
   arch/x86/kvm/vmx/main.c            | 32 ++++++++++++++++++++++++++++--
   arch/x86/kvm/vmx/tdx.c             |  4 ++--
   6 files changed, 48 insertions(+), 11 deletions(-)
arch/x86/include/asm/kvm-x86-ops.h +1 -0
index 22d93d4124c8..85c04aad6ab3 100644
@@ -149,6 +149,7 @@ KVM_X86_OP_OPTIONAL(migrate_timers)
   KVM_X86_OP(msr_filter_changed)
   KVM_X86_OP(complete_emulated_msr)
   KVM_X86_OP(vcpu_deliver_sipi_vector)
+KVM_X86_OP(vcpu_deliver_init)
   KVM_X86_OP_OPTIONAL_RET0(vcpu_get_apicv_inhibit_reasons);
   KVM_X86_OP_OPTIONAL(get_untagged_addr)
   KVM_X86_OP_OPTIONAL_RET0(gmem_max_level)
arch/x86/include/asm/kvm_host.h +2 -0
index bb8be091f996..2686c080820b 100644
@@ -1836,6 +1836,7 @@ struct kvm_x86_ops {
   	int (*complete_emulated_msr)(struct kvm_vcpu *vcpu, int err);
   	void (*vcpu_deliver_sipi_vector)(struct kvm_vcpu *vcpu, u8 vector);
+	void (*vcpu_deliver_init)(struct kvm_vcpu *vcpu);
   	/*
   	 * Returns vCPU specific APICv inhibit reasons
@@ -2092,6 +2093,7 @@ void kvm_get_segment(struct kvm_vcpu *vcpu, struct kvm_segment *var, int seg);
   void kvm_set_segment(struct kvm_vcpu *vcpu, struct kvm_segment *var, int seg);
   int kvm_load_segment_descriptor(struct kvm_vcpu *vcpu, u16 selector, int seg);
   void kvm_vcpu_deliver_sipi_vector(struct kvm_vcpu *vcpu, u8 vector);
+void kvm_vcpu_deliver_init(struct kvm_vcpu *vcpu);
   int kvm_task_switch(struct kvm_vcpu *vcpu, u16 tss_selector, int idt_index,
   		    int reason, bool has_error_code, u32 error_code);
arch/x86/kvm/lapic.c +12 -7
index 8025c7f614e0..431074679e83 100644
@@ -3268,6 +3268,16 @@ int kvm_lapic_set_pv_eoi(struct kvm_vcpu *vcpu, u64 data, unsigned long len)
   	return 0;
   }
+void kvm_vcpu_deliver_init(struct kvm_vcpu *vcpu)
+{
+	kvm_vcpu_reset(vcpu, true);
+	if (kvm_vcpu_is_bsp(vcpu))
+		vcpu->arch.mp_state = KVM_MP_STATE_RUNNABLE;
+	else
+		vcpu->arch.mp_state = KVM_MP_STATE_INIT_RECEIVED;
+}
+EXPORT_SYMBOL_GPL(kvm_vcpu_deliver_init);
+
   int kvm_apic_accept_events(struct kvm_vcpu *vcpu)
   {
   	struct kvm_lapic *apic = vcpu->arch.apic;
@@ -3299,13 +3309,8 @@ int kvm_apic_accept_events(struct kvm_vcpu *vcpu)
   		return 0;
   	}
-	if (test_and_clear_bit(KVM_APIC_INIT, &apic->pending_events)) {
-		kvm_vcpu_reset(vcpu, true);
-		if (kvm_vcpu_is_bsp(apic->vcpu))
-			vcpu->arch.mp_state = KVM_MP_STATE_RUNNABLE;
-		else
-			vcpu->arch.mp_state = KVM_MP_STATE_INIT_RECEIVED;
-	}
+	if (test_and_clear_bit(KVM_APIC_INIT, &apic->pending_events))
+		static_call(kvm_x86_vcpu_deliver_init)(vcpu);
   	if (test_and_clear_bit(KVM_APIC_SIPI, &apic->pending_events)) {
   		if (vcpu->arch.mp_state == KVM_MP_STATE_INIT_RECEIVED) {
   			/* evaluate pending_events before reading the vector */
arch/x86/kvm/svm/svm.c +1 -0
index f76dd52d29ba..27546d993809 100644
@@ -5037,6 +5037,7 @@ static struct kvm_x86_ops svm_x86_ops __initdata = {
   	.complete_emulated_msr = svm_complete_emulated_msr,
   	.vcpu_deliver_sipi_vector = svm_vcpu_deliver_sipi_vector,
+	.vcpu_deliver_init = kvm_vcpu_deliver_init,
   	.vcpu_get_apicv_inhibit_reasons = avic_vcpu_get_apicv_inhibit_reasons,
   };
arch/x86/kvm/vmx/main.c +0 -0
index 4f3b872cd401..84d2dc818cf7 100644

arch/x86/kvm/vmx/main.c static void vt_enable_smi_window(struct kvm_vcpu *vcpu) · line 325

Quoted 6 lines
arch/x86/kvm/vmx/main.c +4 -0
@@ -320,6 +320,14 @@ static void vt_enable_smi_window(struct kvm_vcpu *vcpu)
   }
   #endif
+static bool vt_apic_init_signal_blocked(struct kvm_vcpu *vcpu)
+{
+	if (is_td_vcpu(vcpu))
+		return true;

Since for TD, INIT is always blocked, then in kvm_apic_accept_events(), the code path to handle INIT/SIPI delivery will not be called, i.e, the OPs .vcpu_deliver_init() and .vcpu_deliver_sipi_vector() are never called for TD.
Seems no need to add the new interface  vcpu_deliver_init or the new wrapper vt_vcpu_deliver_sipi_vector().

Quoted 10 lines

And consider the INIT/SIPI for TD:
- Normally, for TD, INIT ans SIPI should not be set in APIC's pending_events.
  Maybe we can call KVM_BUG_ON() in vt_apic_init_signal_blocked() for TD?
- If INIT and SIPI are allowed be set in APIC's pending_events for somehow, the current code has a problem, it will never clear INIT bit in APIC's pending_events.
  Then kvm_apic_accept_events() needs to execute more check code if INIT was
once set.
  INIT bit should be cleared with this assumption.

Quoted 5 lines

KVM_SET_MP_STATE and KVM_SET_VCPU_EVENTS can set INIT/SIPI by the user space. If we change those two IOCTLs to reject INIT/SIPI for TDX, we can go for the above "Normally" option. Because I didn't want to touch the common functions, I ended in extra callbacks. The user space shouldn't use those two IOCTLs for TDX, though.

Even though INIT/SIPI can be set by userspace to APIC's pending_events, for TD, INIT is always blocked, then in kvm_apic_accept_events(), the code path to handle INIT/SIPI delivery will not be called, i.e, the OPs .vcpu_deliver_init() and .vcpu_deliver_sipi_vector() are never called for TD.

Can we just drop the new OPs .vcpu_deliver_init() and the new wrapper vt_vcpu_deliver_sipi_vector()?

#659 lore raw

Re: [PATCH v19 117/130] KVM: TDX: Silently ignore INIT/SIPI

Quoted 137 lines · Binbin Wu
Quoted 128 lines · Isaku Yamahata
Quoted 114 lines · Binbin Wu
Quoted 99 lines · On 2/26/2024 4:26 PM, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

The TDX module API doesn't provide API for VMM to inject INIT IPI and SIPI. Instead it defines the different protocols to boot application processors. Ignore INIT and SIPI events for the TDX guest.

There are two options. 1) (silently) ignore INIT/SIPI request or 2) return error to guest TDs somehow. Given that TDX guest is paravirtualized to boot AP, the option 1 is chosen for simplicity.

---
   arch/x86/include/asm/kvm-x86-ops.h |  1 +
   arch/x86/include/asm/kvm_host.h    |  2 ++
   arch/x86/kvm/lapic.c               | 19 +++++++++++-------
   arch/x86/kvm/svm/svm.c             |  1 +
   arch/x86/kvm/vmx/main.c            | 32 ++++++++++++++++++++++++++++--
   arch/x86/kvm/vmx/tdx.c             |  4 ++--
   6 files changed, 48 insertions(+), 11 deletions(-)
arch/x86/include/asm/kvm-x86-ops.h +1 -0
index 22d93d4124c8..85c04aad6ab3 100644
@@ -149,6 +149,7 @@ KVM_X86_OP_OPTIONAL(migrate_timers)
   KVM_X86_OP(msr_filter_changed)
   KVM_X86_OP(complete_emulated_msr)
   KVM_X86_OP(vcpu_deliver_sipi_vector)
+KVM_X86_OP(vcpu_deliver_init)
   KVM_X86_OP_OPTIONAL_RET0(vcpu_get_apicv_inhibit_reasons);
   KVM_X86_OP_OPTIONAL(get_untagged_addr)
   KVM_X86_OP_OPTIONAL_RET0(gmem_max_level)
arch/x86/include/asm/kvm_host.h +2 -0
index bb8be091f996..2686c080820b 100644
@@ -1836,6 +1836,7 @@ struct kvm_x86_ops {
   	int (*complete_emulated_msr)(struct kvm_vcpu *vcpu, int err);
   	void (*vcpu_deliver_sipi_vector)(struct kvm_vcpu *vcpu, u8 vector);
+	void (*vcpu_deliver_init)(struct kvm_vcpu *vcpu);
   	/*
   	 * Returns vCPU specific APICv inhibit reasons
@@ -2092,6 +2093,7 @@ void kvm_get_segment(struct kvm_vcpu *vcpu, struct kvm_segment *var, int seg);
   void kvm_set_segment(struct kvm_vcpu *vcpu, struct kvm_segment *var, int seg);
   int kvm_load_segment_descriptor(struct kvm_vcpu *vcpu, u16 selector, int seg);
   void kvm_vcpu_deliver_sipi_vector(struct kvm_vcpu *vcpu, u8 vector);
+void kvm_vcpu_deliver_init(struct kvm_vcpu *vcpu);
   int kvm_task_switch(struct kvm_vcpu *vcpu, u16 tss_selector, int idt_index,
   		    int reason, bool has_error_code, u32 error_code);
arch/x86/kvm/lapic.c +12 -7
index 8025c7f614e0..431074679e83 100644
@@ -3268,6 +3268,16 @@ int kvm_lapic_set_pv_eoi(struct kvm_vcpu *vcpu, u64 data, unsigned long len)
   	return 0;
   }
+void kvm_vcpu_deliver_init(struct kvm_vcpu *vcpu)
+{
+	kvm_vcpu_reset(vcpu, true);
+	if (kvm_vcpu_is_bsp(vcpu))
+		vcpu->arch.mp_state = KVM_MP_STATE_RUNNABLE;
+	else
+		vcpu->arch.mp_state = KVM_MP_STATE_INIT_RECEIVED;
+}
+EXPORT_SYMBOL_GPL(kvm_vcpu_deliver_init);
+
   int kvm_apic_accept_events(struct kvm_vcpu *vcpu)
   {
   	struct kvm_lapic *apic = vcpu->arch.apic;
@@ -3299,13 +3309,8 @@ int kvm_apic_accept_events(struct kvm_vcpu *vcpu)
   		return 0;
   	}
-	if (test_and_clear_bit(KVM_APIC_INIT, &apic->pending_events)) {
-		kvm_vcpu_reset(vcpu, true);
-		if (kvm_vcpu_is_bsp(apic->vcpu))
-			vcpu->arch.mp_state = KVM_MP_STATE_RUNNABLE;
-		else
-			vcpu->arch.mp_state = KVM_MP_STATE_INIT_RECEIVED;
-	}
+	if (test_and_clear_bit(KVM_APIC_INIT, &apic->pending_events))
+		static_call(kvm_x86_vcpu_deliver_init)(vcpu);
   	if (test_and_clear_bit(KVM_APIC_SIPI, &apic->pending_events)) {
   		if (vcpu->arch.mp_state == KVM_MP_STATE_INIT_RECEIVED) {
   			/* evaluate pending_events before reading the vector */
arch/x86/kvm/svm/svm.c +1 -0
index f76dd52d29ba..27546d993809 100644
@@ -5037,6 +5037,7 @@ static struct kvm_x86_ops svm_x86_ops __initdata = {
   	.complete_emulated_msr = svm_complete_emulated_msr,
   	.vcpu_deliver_sipi_vector = svm_vcpu_deliver_sipi_vector,
+	.vcpu_deliver_init = kvm_vcpu_deliver_init,
   	.vcpu_get_apicv_inhibit_reasons = avic_vcpu_get_apicv_inhibit_reasons,
   };
arch/x86/kvm/vmx/main.c +0 -0
index 4f3b872cd401..84d2dc818cf7 100644

arch/x86/kvm/vmx/main.c static void vt_enable_smi_window(struct kvm_vcpu *vcpu) · line 325

Quoted 6 lines
arch/x86/kvm/vmx/main.c +4 -0
@@ -320,6 +320,14 @@ static void vt_enable_smi_window(struct kvm_vcpu *vcpu)
   }
   #endif
+static bool vt_apic_init_signal_blocked(struct kvm_vcpu *vcpu)
+{
+	if (is_td_vcpu(vcpu))
+		return true;

Since for TD, INIT is always blocked, then in kvm_apic_accept_events(), the code path to handle INIT/SIPI delivery will not be called, i.e, the OPs .vcpu_deliver_init() and .vcpu_deliver_sipi_vector() are never called for TD.
Seems no need to add the new interface  vcpu_deliver_init or the new wrapper vt_vcpu_deliver_sipi_vector().

Quoted 10 lines

And consider the INIT/SIPI for TD:
- Normally, for TD, INIT ans SIPI should not be set in APIC's pending_events.
  Maybe we can call KVM_BUG_ON() in vt_apic_init_signal_blocked() for TD?
- If INIT and SIPI are allowed be set in APIC's pending_events for somehow, the current code has a problem, it will never clear INIT bit in APIC's pending_events.
  Then kvm_apic_accept_events() needs to execute more check code if INIT was
once set.
  INIT bit should be cleared with this assumption.

Quoted 5 lines

KVM_SET_MP_STATE and KVM_SET_VCPU_EVENTS can set INIT/SIPI by the user space. If we change those two IOCTLs to reject INIT/SIPI for TDX, we can go for the above "Normally" option. Because I didn't want to touch the common functions, I ended in extra callbacks. The user space shouldn't use those two IOCTLs for TDX, though.

Quoted 8 lines

Even though INIT/SIPI can be set by userspace to APIC's pending_events, for TD, INIT is always blocked, then in kvm_apic_accept_events(), the code path to handle INIT/SIPI delivery will not be called, i.e, the OPs .vcpu_deliver_init() and .vcpu_deliver_sipi_vector() are never called for TD.

Can we just drop the new OPs .vcpu_deliver_init() and the new wrapper vt_vcpu_deliver_sipi_vector()?

Ah, you're right. We simply always block INIT/SIPI. We should update the commit message. How about this? Added option 3.

The TDX module API doesn't provide API for VMM to inject INIT IPI and SIPI. Instead it defines the different protocols to boot application processors. Ignore INIT and SIPI events for the TDX guest.

There are two options. 1) (silently) ignore INIT/SIPI request or 2) return error to guest TDs somehow. 3) Always block INIT/SIPI always. Given that TDX guest is paravirtualized to boot AP, the option 3 is chosen for simplicity.

Signature · 1 line
Isaku Yamahata <[email protected]>
#660 lore raw

PATCH v19 118/130 KVM: TDX: Add methods to ignore accesses to CPU state

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

TDX protects TDX guest state from VMM. Implement access methods for TDX guest state to ignore them or return zero. Because those methods can be called by kvm ioctls to set/get cpu registers, they don't have KVM_BUG_ON except one method.

---
 arch/x86/kvm/vmx/main.c    | 289 +++++++++++++++++++++++++++++++++----
 arch/x86/kvm/vmx/tdx.c     |  48 +++++-
 arch/x86/kvm/vmx/x86_ops.h |  13 ++
 3 files changed, 321 insertions(+), 29 deletions(-)
arch/x86/kvm/vmx/main.c +262 -27
index 84d2dc818cf7..9fb3f28d8259 100644
@@ -375,6 +375,200 @@ static void vt_vcpu_deliver_init(struct kvm_vcpu *vcpu)
 	kvm_vcpu_deliver_init(vcpu);
 }
 
+static void vt_vcpu_after_set_cpuid(struct kvm_vcpu *vcpu)
+{
+	if (is_td_vcpu(vcpu))
+		return;
+
+	vmx_vcpu_after_set_cpuid(vcpu);
+}
+
+static void vt_update_exception_bitmap(struct kvm_vcpu *vcpu)
+{
+	if (is_td_vcpu(vcpu))
+		return;
+
+	vmx_update_exception_bitmap(vcpu);
+}
+
+static u64 vt_get_segment_base(struct kvm_vcpu *vcpu, int seg)
+{
+	if (is_td_vcpu(vcpu))
+		return tdx_get_segment_base(vcpu, seg);
+
+	return vmx_get_segment_base(vcpu, seg);
+}
+
+static void vt_get_segment(struct kvm_vcpu *vcpu, struct kvm_segment *var,
+			      int seg)
+{
+	if (is_td_vcpu(vcpu)) {
+		tdx_get_segment(vcpu, var, seg);
+		return;
+	}
+
+	vmx_get_segment(vcpu, var, seg);
+}
+
+static void vt_set_segment(struct kvm_vcpu *vcpu, struct kvm_segment *var,
+			      int seg)
+{
+	if (is_td_vcpu(vcpu))
+		return;
+
+	vmx_set_segment(vcpu, var, seg);
+}
+
+static int vt_get_cpl(struct kvm_vcpu *vcpu)
+{
+	if (is_td_vcpu(vcpu))
+		return tdx_get_cpl(vcpu);
+
+	return vmx_get_cpl(vcpu);
+}
+
+static void vt_get_cs_db_l_bits(struct kvm_vcpu *vcpu, int *db, int *l)
+{
+	if (is_td_vcpu(vcpu)) {
+		*db = 0;
+		*l = 0;
+		return;
+	}
+
+	vmx_get_cs_db_l_bits(vcpu, db, l);
+}
+
+static bool vt_is_valid_cr0(struct kvm_vcpu *vcpu, unsigned long cr0)
+{
+	if (is_td_vcpu(vcpu))
+		return true;
+
+	return vmx_is_valid_cr0(vcpu, cr0);
+}
+
+static void vt_set_cr0(struct kvm_vcpu *vcpu, unsigned long cr0)
+{
+	if (is_td_vcpu(vcpu))
+		return;
+
+	vmx_set_cr0(vcpu, cr0);
+}
+
+static bool vt_is_valid_cr4(struct kvm_vcpu *vcpu, unsigned long cr4)
+{
+	if (is_td_vcpu(vcpu))
+		return true;
+
+	return vmx_is_valid_cr4(vcpu, cr4);
+}
+
+static void vt_set_cr4(struct kvm_vcpu *vcpu, unsigned long cr4)
+{
+	if (is_td_vcpu(vcpu))
+		return;
+
+	vmx_set_cr4(vcpu, cr4);
+}
+
+static int vt_set_efer(struct kvm_vcpu *vcpu, u64 efer)
+{
+	if (is_td_vcpu(vcpu))
+		return 0;
+
+	return vmx_set_efer(vcpu, efer);
+}
+
+static void vt_get_idt(struct kvm_vcpu *vcpu, struct desc_ptr *dt)
+{
+	if (is_td_vcpu(vcpu)) {
+		memset(dt, 0, sizeof(*dt));
+		return;
+	}
+
+	vmx_get_idt(vcpu, dt);
+}
+
+static void vt_set_idt(struct kvm_vcpu *vcpu, struct desc_ptr *dt)
+{
+	if (is_td_vcpu(vcpu))
+		return;
+
+	vmx_set_idt(vcpu, dt);
+}
+
+static void vt_get_gdt(struct kvm_vcpu *vcpu, struct desc_ptr *dt)
+{
+	if (is_td_vcpu(vcpu)) {
+		memset(dt, 0, sizeof(*dt));
+		return;
+	}
+
+	vmx_get_gdt(vcpu, dt);
+}
+
+static void vt_set_gdt(struct kvm_vcpu *vcpu, struct desc_ptr *dt)
+{
+	if (is_td_vcpu(vcpu))
+		return;
+
+	vmx_set_gdt(vcpu, dt);
+}
+
+static void vt_set_dr7(struct kvm_vcpu *vcpu, unsigned long val)
+{
+	if (is_td_vcpu(vcpu))
+		return;
+
+	vmx_set_dr7(vcpu, val);
+}
+
+static void vt_sync_dirty_debug_regs(struct kvm_vcpu *vcpu)
+{
+	/*
+	 * MOV-DR exiting is always cleared for TD guest, even in debug mode.
+	 * Thus KVM_DEBUGREG_WONT_EXIT can never be set and it should never
+	 * reach here for TD vcpu.
+	 */
+	if (KVM_BUG_ON(is_td_vcpu(vcpu), vcpu->kvm))
+		return;
+
+	vmx_sync_dirty_debug_regs(vcpu);
+}
+
+static void vt_cache_reg(struct kvm_vcpu *vcpu, enum kvm_reg reg)
+{
+	if (is_td_vcpu(vcpu)) {
+		tdx_cache_reg(vcpu, reg);
+		return;
+	}
+
+	vmx_cache_reg(vcpu, reg);
+}
+
+static unsigned long vt_get_rflags(struct kvm_vcpu *vcpu)
+{
+	if (is_td_vcpu(vcpu))
+		return tdx_get_rflags(vcpu);
+
+	return vmx_get_rflags(vcpu);
+}
+
+static void vt_set_rflags(struct kvm_vcpu *vcpu, unsigned long rflags)
+{
+	if (is_td_vcpu(vcpu))
+		return;
+
+	vmx_set_rflags(vcpu, rflags);
+}
+
+static bool vt_get_if_flag(struct kvm_vcpu *vcpu)
+{
+	if (is_td_vcpu(vcpu))
+		return false;
+
+	return vmx_get_if_flag(vcpu);
+}
+
 static void vt_flush_tlb_all(struct kvm_vcpu *vcpu)
 {
 	if (is_td_vcpu(vcpu)) {
@@ -521,6 +715,14 @@ static void vt_inject_irq(struct kvm_vcpu *vcpu, bool reinjected)
 	vmx_inject_irq(vcpu, reinjected);
 }
 
+static void vt_inject_exception(struct kvm_vcpu *vcpu)
+{
+	if (is_td_vcpu(vcpu))
+		return;
+
+	vmx_inject_exception(vcpu);
+}
+
 static void vt_cancel_injection(struct kvm_vcpu *vcpu)
 {
 	if (is_td_vcpu(vcpu))
@@ -567,6 +769,39 @@ static void vt_get_exit_info(struct kvm_vcpu *vcpu, u32 *reason,
 	vmx_get_exit_info(vcpu, reason, info1, info2, intr_info, error_code);
 }
 
+
+static void vt_update_cr8_intercept(struct kvm_vcpu *vcpu, int tpr, int irr)
+{
+	if (is_td_vcpu(vcpu))
+		return;
+
+	vmx_update_cr8_intercept(vcpu, tpr, irr);
+}
+
+static void vt_load_eoi_exitmap(struct kvm_vcpu *vcpu, u64 *eoi_exit_bitmap)
+{
+	if (is_td_vcpu(vcpu))
+		return;
+
+	vmx_load_eoi_exitmap(vcpu, eoi_exit_bitmap);
+}
+
+static int vt_set_tss_addr(struct kvm *kvm, unsigned int addr)
+{
+	if (is_td(kvm))
+		return 0;
+
+	return vmx_set_tss_addr(kvm, addr);
+}
+
+static int vt_set_identity_map_addr(struct kvm *kvm, u64 ident_addr)
+{
+	if (is_td(kvm))
+		return 0;
+
+	return vmx_set_identity_map_addr(kvm, ident_addr);
+}
+
 static u8 vt_get_mt_mask(struct kvm_vcpu *vcpu, gfn_t gfn, bool is_mmio)
 {
 	if (is_td_vcpu(vcpu))
@@ -661,30 +896,30 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
 	.vcpu_load = vt_vcpu_load,
 	.vcpu_put = vt_vcpu_put,
 
-	.update_exception_bitmap = vmx_update_exception_bitmap,
+	.update_exception_bitmap = vt_update_exception_bitmap,
 	.get_msr_feature = vmx_get_msr_feature,
 	.get_msr = vt_get_msr,
 	.set_msr = vt_set_msr,
-	.get_segment_base = vmx_get_segment_base,
-	.get_segment = vmx_get_segment,
-	.set_segment = vmx_set_segment,
-	.get_cpl = vmx_get_cpl,
-	.get_cs_db_l_bits = vmx_get_cs_db_l_bits,
-	.is_valid_cr0 = vmx_is_valid_cr0,
-	.set_cr0 = vmx_set_cr0,
-	.is_valid_cr4 = vmx_is_valid_cr4,
-	.set_cr4 = vmx_set_cr4,
-	.set_efer = vmx_set_efer,
-	.get_idt = vmx_get_idt,
-	.set_idt = vmx_set_idt,
-	.get_gdt = vmx_get_gdt,
-	.set_gdt = vmx_set_gdt,
-	.set_dr7 = vmx_set_dr7,
-	.sync_dirty_debug_regs = vmx_sync_dirty_debug_regs,
-	.cache_reg = vmx_cache_reg,
-	.get_rflags = vmx_get_rflags,
-	.set_rflags = vmx_set_rflags,
-	.get_if_flag = vmx_get_if_flag,
+	.get_segment_base = vt_get_segment_base,
+	.get_segment = vt_get_segment,
+	.set_segment = vt_set_segment,
+	.get_cpl = vt_get_cpl,
+	.get_cs_db_l_bits = vt_get_cs_db_l_bits,
+	.is_valid_cr0 = vt_is_valid_cr0,
+	.set_cr0 = vt_set_cr0,
+	.is_valid_cr4 = vt_is_valid_cr4,
+	.set_cr4 = vt_set_cr4,
+	.set_efer = vt_set_efer,
+	.get_idt = vt_get_idt,
+	.set_idt = vt_set_idt,
+	.get_gdt = vt_get_gdt,
+	.set_gdt = vt_set_gdt,
+	.set_dr7 = vt_set_dr7,
+	.sync_dirty_debug_regs = vt_sync_dirty_debug_regs,
+	.cache_reg = vt_cache_reg,
+	.get_rflags = vt_get_rflags,
+	.set_rflags = vt_set_rflags,
+	.get_if_flag = vt_get_if_flag,
 
 	.flush_tlb_all = vt_flush_tlb_all,
 	.flush_tlb_current = vt_flush_tlb_current,
@@ -701,7 +936,7 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
 	.patch_hypercall = vmx_patch_hypercall,
 	.inject_irq = vt_inject_irq,
 	.inject_nmi = vt_inject_nmi,
-	.inject_exception = vmx_inject_exception,
+	.inject_exception = vt_inject_exception,
 	.cancel_injection = vt_cancel_injection,
 	.interrupt_allowed = vt_interrupt_allowed,
 	.nmi_allowed = vt_nmi_allowed,
@@ -709,11 +944,11 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
 	.set_nmi_mask = vt_set_nmi_mask,
 	.enable_nmi_window = vt_enable_nmi_window,
 	.enable_irq_window = vt_enable_irq_window,
-	.update_cr8_intercept = vmx_update_cr8_intercept,
+	.update_cr8_intercept = vt_update_cr8_intercept,
 	.set_virtual_apic_mode = vmx_set_virtual_apic_mode,
 	.set_apic_access_page_addr = vmx_set_apic_access_page_addr,
 	.refresh_apicv_exec_ctrl = vmx_refresh_apicv_exec_ctrl,
-	.load_eoi_exitmap = vmx_load_eoi_exitmap,
+	.load_eoi_exitmap = vt_load_eoi_exitmap,
 	.apicv_pre_state_restore = vt_apicv_pre_state_restore,
 	.required_apicv_inhibits = VMX_REQUIRED_APICV_INHIBITS,
 	.hwapic_irr_update = vmx_hwapic_irr_update,
@@ -724,13 +959,13 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
 	.dy_apicv_has_pending_interrupt = pi_has_pending_interrupt,
 	.protected_apic_has_interrupt = vt_protected_apic_has_interrupt,
 
-	.set_tss_addr = vmx_set_tss_addr,
-	.set_identity_map_addr = vmx_set_identity_map_addr,
+	.set_tss_addr = vt_set_tss_addr,
+	.set_identity_map_addr = vt_set_identity_map_addr,
 	.get_mt_mask = vt_get_mt_mask,
 
 	.get_exit_info = vt_get_exit_info,
 
-	.vcpu_after_set_cpuid = vmx_vcpu_after_set_cpuid,
+	.vcpu_after_set_cpuid = vt_vcpu_after_set_cpuid,
 
 	.has_wbinvd_exit = cpu_has_vmx_wbinvd_exit,
 
arch/x86/kvm/vmx/tdx.c +46 -2

60 lines not on this page · raw message

arch/x86/kvm/vmx/x86_ops.h +13 -0
index 7c63b2b48125..727c4d418601 100644
@@ -169,6 +169,12 @@ bool tdx_has_emulated_msr(u32 index, bool write);
 int tdx_get_msr(struct kvm_vcpu *vcpu, struct msr_data *msr);
 int tdx_set_msr(struct kvm_vcpu *vcpu, struct msr_data *msr);
 
+int tdx_get_cpl(struct kvm_vcpu *vcpu);
+void tdx_cache_reg(struct kvm_vcpu *vcpu, enum kvm_reg reg);
+unsigned long tdx_get_rflags(struct kvm_vcpu *vcpu);
+u64 tdx_get_segment_base(struct kvm_vcpu *vcpu, int seg);
+void tdx_get_segment(struct kvm_vcpu *vcpu, struct kvm_segment *var, int seg);
+
 int tdx_vcpu_ioctl(struct kvm_vcpu *vcpu, void __user *argp);
 
 void tdx_flush_tlb(struct kvm_vcpu *vcpu);
@@ -221,6 +227,13 @@ static inline bool tdx_has_emulated_msr(u32 index, bool write) { return false; }
 static inline int tdx_get_msr(struct kvm_vcpu *vcpu, struct msr_data *msr) { return 1; }
 static inline int tdx_set_msr(struct kvm_vcpu *vcpu, struct msr_data *msr) { return 1; }
 
+static inline int tdx_get_cpl(struct kvm_vcpu *vcpu) { return 0; }
+static inline void tdx_cache_reg(struct kvm_vcpu *vcpu, enum kvm_reg reg) {}
+static inline unsigned long tdx_get_rflags(struct kvm_vcpu *vcpu) { return 0; }
+static inline u64 tdx_get_segment_base(struct kvm_vcpu *vcpu, int seg) { return 0; }
+static inline void tdx_get_segment(struct kvm_vcpu *vcpu, struct kvm_segment *var,
+				   int seg) {}
+
 static inline int tdx_vcpu_ioctl(struct kvm_vcpu *vcpu, void __user *argp) { return -EOPNOTSUPP; }
 
 static inline void tdx_flush_tlb(struct kvm_vcpu *vcpu) {}
Signature · 1 line
2.25.1
#661 lore raw

Re: [PATCH v19 118/130] KVM: TDX: Add methods to ignore accesses to CPU state

Quoted 32 lines · On 2/26/2024 4:27 PM, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

TDX protects TDX guest state from VMM. Implement access methods for TDX guest state to ignore them or return zero. Because those methods can be called by kvm ioctls to set/get cpu registers, they don't have KVM_BUG_ON except one method.

---
  arch/x86/kvm/vmx/main.c    | 289 +++++++++++++++++++++++++++++++++----
  arch/x86/kvm/vmx/tdx.c     |  48 +++++-
  arch/x86/kvm/vmx/x86_ops.h |  13 ++
  3 files changed, 321 insertions(+), 29 deletions(-)
arch/x86/kvm/vmx/main.c +10 -0
index 84d2dc818cf7..9fb3f28d8259 100644
@@ -375,6 +375,200 @@ static void vt_vcpu_deliver_init(struct kvm_vcpu *vcpu)
  	kvm_vcpu_deliver_init(vcpu);
  }
 
+static void vt_vcpu_after_set_cpuid(struct kvm_vcpu *vcpu)
+{
+	if (is_td_vcpu(vcpu))
+		return;
+
+	vmx_vcpu_after_set_cpuid(vcpu);
+}
+
+static void vt_update_exception_bitmap(struct kvm_vcpu *vcpu)
+{

arch/x86/kvm/vmx/main.c static void vt_vcpu_deliver_init(struct kvm_vcpu *vcpu) · line 397

Quoted 10 lines
arch/x86/kvm/vmx/main.c +10 -0
+	if (is_td_vcpu(vcpu))
+		return;
+
+	vmx_update_exception_bitmap(vcpu);
+}
+
+static u64 vt_get_segment_base(struct kvm_vcpu *vcpu, int seg)
+{
+	if (is_td_vcpu(vcpu))
+		return tdx_get_segment_base(vcpu, seg);

Could just return 0?
Not need to add a function, since it's only called here and it's more straight forward to read the code without jump to the definition of these functions.

Similarly, we can use open code for tdx_get_cpl(), tdx_get_rflags() and tdx_get_segment(), which return 0 or memset with 0.

Quoted 4 lines
arch/x86/kvm/vmx/main.c +4 -0
+
+	return vmx_get_segment_base(vcpu, seg);
+}
+

[...]

Quoted 6 lines
arch/x86/kvm/vmx/main.c +5 -0
+int tdx_get_cpl(struct kvm_vcpu *vcpu)
+{
+	return 0;
+}
+

[...]

Quoted 50 lines
arch/x86/kvm/vmx/main.c +16 -0
+
+unsigned long tdx_get_rflags(struct kvm_vcpu *vcpu)
+{
+	return 0;
+}
+
+u64 tdx_get_segment_base(struct kvm_vcpu *vcpu, int seg)
+{
+	return 0;
+}
+
+void tdx_get_segment(struct kvm_vcpu *vcpu, struct kvm_segment *var, int seg)
+{
+	memset(var, 0, sizeof(*var));
+}
+
  static int tdx_get_capabilities(struct kvm_tdx_cmd *cmd)
  {
  	struct kvm_tdx_capabilities __user *user_caps;
arch/x86/kvm/vmx/x86_ops.h +13 -0
index 7c63b2b48125..727c4d418601 100644
@@ -169,6 +169,12 @@ bool tdx_has_emulated_msr(u32 index, bool write);
  int tdx_get_msr(struct kvm_vcpu *vcpu, struct msr_data *msr);
  int tdx_set_msr(struct kvm_vcpu *vcpu, struct msr_data *msr);
 
+int tdx_get_cpl(struct kvm_vcpu *vcpu);
+void tdx_cache_reg(struct kvm_vcpu *vcpu, enum kvm_reg reg);
+unsigned long tdx_get_rflags(struct kvm_vcpu *vcpu);
+u64 tdx_get_segment_base(struct kvm_vcpu *vcpu, int seg);
+void tdx_get_segment(struct kvm_vcpu *vcpu, struct kvm_segment *var, int seg);
+
  int tdx_vcpu_ioctl(struct kvm_vcpu *vcpu, void __user *argp);
 
  void tdx_flush_tlb(struct kvm_vcpu *vcpu);
@@ -221,6 +227,13 @@ static inline bool tdx_has_emulated_msr(u32 index, bool write) { return false; }
  static inline int tdx_get_msr(struct kvm_vcpu *vcpu, struct msr_data *msr) { return 1; }
  static inline int tdx_set_msr(struct kvm_vcpu *vcpu, struct msr_data *msr) { return 1; }
 
+static inline int tdx_get_cpl(struct kvm_vcpu *vcpu) { return 0; }
+static inline void tdx_cache_reg(struct kvm_vcpu *vcpu, enum kvm_reg reg) {}
+static inline unsigned long tdx_get_rflags(struct kvm_vcpu *vcpu) { return 0; }
+static inline u64 tdx_get_segment_base(struct kvm_vcpu *vcpu, int seg) { return 0; }
+static inline void tdx_get_segment(struct kvm_vcpu *vcpu, struct kvm_segment *var,
+				   int seg) {}
+
  static inline int tdx_vcpu_ioctl(struct kvm_vcpu *vcpu, void __user *argp) { return -EOPNOTSUPP; }
 
  static inline void tdx_flush_tlb(struct kvm_vcpu *vcpu) {}
#662 lore raw

Re: [PATCH v19 118/130] KVM: TDX: Add methods to ignore accesses to CPU state

Quoted 49 lines · Binbin Wu
Quoted 31 lines · On 2/26/2024 4:27 PM, [email protected] wrote:

From: Isaku Yamahata <[email protected]>

TDX protects TDX guest state from VMM. Implement access methods for TDX guest state to ignore them or return zero. Because those methods can be called by kvm ioctls to set/get cpu registers, they don't have KVM_BUG_ON except one method.

---
  arch/x86/kvm/vmx/main.c    | 289 +++++++++++++++++++++++++++++++++----
  arch/x86/kvm/vmx/tdx.c     |  48 +++++-
  arch/x86/kvm/vmx/x86_ops.h |  13 ++
  3 files changed, 321 insertions(+), 29 deletions(-)
arch/x86/kvm/vmx/main.c +10 -0
index 84d2dc818cf7..9fb3f28d8259 100644
@@ -375,6 +375,200 @@ static void vt_vcpu_deliver_init(struct kvm_vcpu *vcpu)
  	kvm_vcpu_deliver_init(vcpu);
  }
+static void vt_vcpu_after_set_cpuid(struct kvm_vcpu *vcpu)
+{
+	if (is_td_vcpu(vcpu))
+		return;
+
+	vmx_vcpu_after_set_cpuid(vcpu);
+}
+
+static void vt_update_exception_bitmap(struct kvm_vcpu *vcpu)
+{

arch/x86/kvm/vmx/main.c static void vt_vcpu_deliver_init(struct kvm_vcpu *vcpu) · line 396

Quoted 10 lines
arch/x86/kvm/vmx/main.c +10 -0
+	if (is_td_vcpu(vcpu))
+		return;
+
+	vmx_update_exception_bitmap(vcpu);
+}
+
+static u64 vt_get_segment_base(struct kvm_vcpu *vcpu, int seg)
+{
+	if (is_td_vcpu(vcpu))
+		return tdx_get_segment_base(vcpu, seg);

Could just return 0?
Not need to add a function, since it's only called here and it's more straight forward to read the code without jump to the definition of these functions.

Quoted 2 lines

Similarly, we can use open code for tdx_get_cpl(), tdx_get_rflags() and tdx_get_segment(), which return 0 or memset with 0.

Yes, we should drop them. They came from the TDX guest debug. But right now we drop the support and we have guard with vcpu->arch.guest_state_protected.

guest debug is a future topic now.

Signature · 1 line
Isaku Yamahata <[email protected]>
#663 lore raw

PATCH v19 119/130 KVM: TDX: Add methods to ignore guest instruction emulation

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

Because TDX protects TDX guest state from VMM, instructions in guest memory cannot be emulated. Implement methods to ignore guest instruction emulator.

---
 arch/x86/kvm/vmx/main.c | 28 ++++++++++++++++++++++++++--
 1 file changed, 26 insertions(+), 2 deletions(-)
arch/x86/kvm/vmx/main.c +26 -2
index 9fb3f28d8259..9aaa9bd7abf3 100644
@@ -320,6 +320,30 @@ static void vt_enable_smi_window(struct kvm_vcpu *vcpu)
 }
 #endif
 
+static int vt_check_emulate_instruction(struct kvm_vcpu *vcpu, int emul_type,
+					void *insn, int insn_len)
+{
+	if (is_td_vcpu(vcpu))
+		return X86EMUL_RETRY_INSTR;
+
+	return vmx_check_emulate_instruction(vcpu, emul_type, insn, insn_len);
+}
+
+static int vt_check_intercept(struct kvm_vcpu *vcpu,
+				 struct x86_instruction_info *info,
+				 enum x86_intercept_stage stage,
+				 struct x86_exception *exception)
+{
+	/*
+	 * This call back is triggered by the x86 instruction emulator. TDX
+	 * doesn't allow guest memory inspection.
+	 */
+	if (KVM_BUG_ON(is_td_vcpu(vcpu), vcpu->kvm))
+		return X86EMUL_UNHANDLEABLE;
+
+	return vmx_check_intercept(vcpu, info, stage, exception);
+}
+
 static bool vt_apic_init_signal_blocked(struct kvm_vcpu *vcpu)
 {
 	if (is_td_vcpu(vcpu))
@@ -976,7 +1000,7 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
 
 	.load_mmu_pgd = vt_load_mmu_pgd,
 
-	.check_intercept = vmx_check_intercept,
+	.check_intercept = vt_check_intercept,
 	.handle_exit_irqoff = vt_handle_exit_irqoff,
 
 	.request_immediate_exit = vt_request_immediate_exit,
@@ -1005,7 +1029,7 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
 	.enable_smi_window = vt_enable_smi_window,
 #endif
 
-	.check_emulate_instruction = vmx_check_emulate_instruction,
+	.check_emulate_instruction = vt_check_emulate_instruction,
 	.apic_init_signal_blocked = vt_apic_init_signal_blocked,
 	.migrate_timers = vmx_migrate_timers,
 
Signature · 1 line
2.25.1
#664 lore raw

PATCH v19 120/130 KVM: TDX: Add a method to ignore dirty logging

In reply to [email protected]

From: Isaku Yamahata <[email protected]>

Currently TDX KVM doesn't support tracking dirty pages (yet). Implement a method to ignore it. Because the flag for kvm memory slot to enable dirty logging isn't accepted for TDX, warn on the method is called for TDX.

---
 arch/x86/kvm/vmx/main.c | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)
arch/x86/kvm/vmx/main.c +9 -1
index 9aaa9bd7abf3..991bfdecaed2 100644
@@ -834,6 +834,14 @@ static u8 vt_get_mt_mask(struct kvm_vcpu *vcpu, gfn_t gfn, bool is_mmio)
 	return vmx_get_mt_mask(vcpu, gfn, is_mmio);
 }
 
+static void vt_update_cpu_dirty_logging(struct kvm_vcpu *vcpu)
+{
+	if (KVM_BUG_ON(is_td_vcpu(vcpu), vcpu->kvm))
+		return;
+
+	vmx_update_cpu_dirty_logging(vcpu);
+}
+
 static int vt_mem_enc_ioctl(struct kvm *kvm, void __user *argp)
 {
 	if (!is_td(kvm))
@@ -1008,7 +1016,7 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
 	.sched_in = vt_sched_in,
 
 	.cpu_dirty_log_size = PML_ENTITY_NUM,
-	.update_cpu_dirty_logging = vmx_update_cpu_dirty_logging,
+	.update_cpu_dirty_logging = vt_update_cpu_dirty_logging,
 
 	.nested_ops = &vmx_nested_ops,
 
Signature · 1 line
2.25.1
#665 lore raw

Re: [PATCH v19 120/130] KVM: TDX: Add a method to ignore dirty logging

arch/x86/kvm/vmx/main.c static u8 vt_get_mt_mask(struct kvm_vcpu *vcpu, gfn_t gfn, bool is_mmio) · line 843

Quoted 8 lines · On Mon, 2024-02-26 at 00:27 -0800, [email protected] wrote:

arch/x86/kvm/vmx/main.c +7 -0
+static void vt_update_cpu_dirty_logging(struct kvm_vcpu *vcpu)
+{
+       if (KVM_BUG_ON(is_td_vcpu(vcpu), vcpu->kvm))
+               return;
+
+       vmx_update_cpu_dirty_logging(vcpu);
+}

Discussed this first part offline, but logging it here. Since guest_memfd cannot have dirty logging, this is essentially bugging the VM if somehow they manage anyway. But it should be blocked via the code in check_memory_region_flags().

On the subject of warnings and KVM_BUG_ON(), my feeling so far is that this series is quite aggressive about these. Is it due the complexity of the series? I think maybe we can remove some of the simple ones, but not sure if there was already some discussion on what level is appropriate.

#666 lore raw

Re: [PATCH v19 120/130] KVM: TDX: Add a method to ignore dirty logging

Quoted 14 lines · Rick P

arch/x86/kvm/vmx/main.c static u8 vt_get_mt_mask(struct kvm_vcpu *vcpu, gfn_t gfn, bool is_mmio) · line 843

Quoted 8 lines · On Mon, 2024-02-26 at 00:27 -0800, [email protected] wrote:

arch/x86/kvm/vmx/main.c +7 -0
+static void vt_update_cpu_dirty_logging(struct kvm_vcpu *vcpu)
+{
+       if (KVM_BUG_ON(is_td_vcpu(vcpu), vcpu->kvm))
+               return;
+
+       vmx_update_cpu_dirty_logging(vcpu);
+}

Discussed this first part offline, but logging it here. Since guest_memfd cannot have dirty logging, this is essentially bugging the VM if somehow they manage anyway. But it should be blocked via the code in check_memory_region_flags().

Will drop this patch.

Quoted 5 lines

On the subject of warnings and KVM_BUG_ON(), my feeling so far is that this series is quite aggressive about these. Is it due the complexity of the series? I think maybe we can remove some of the simple ones, but not sure if there was already some discussion on what level is appropriate.

KVM_BUG_ON() was helpful at the early stage. Because we don't hit them recently, it's okay to remove them. Will remove them.

Signature · 1 line
Isaku Yamahata <[email protected]>
#667 lore raw

Re: [PATCH v19 120/130] KVM: TDX: Add a method to ignore dirty logging

Quoted 9 lines · Isaku Yamahata
Quoted 8 lines

On the subject of warnings and KVM_BUG_ON(), my feeling so far is that
this series is quite aggressive about these. Is it due the complexity
of the series? I think maybe we can remove some of the simple ones, but
not sure if there was already some discussion on what level is appropriate.

Quoted 3 lines

KVM_BUG_ON() was helpful at the early stage.  Because we don't hit them
recently, it's okay to remove them.  Will remove them.

Hmm. We probably need to do it case by case.

#668 lore raw

Re: [PATCH v19 120/130] KVM: TDX: Add a method to ignore dirty logging

Quoted 10 lines · Rick P
Quoted 9 lines · Isaku Yamahata
Quoted 8 lines

On the subject of warnings and KVM_BUG_ON(), my feeling so far is that
this series is quite aggressive about these. Is it due the complexity
of the series? I think maybe we can remove some of the simple ones, but
not sure if there was already some discussion on what level is appropriate.

Quoted 5 lines
Quoted 3 lines

KVM_BUG_ON() was helpful at the early stage.  Because we don't hit them
recently, it's okay to remove them.  Will remove them.

Hmm. We probably need to do it case by case.

I categorize as follows. Unless otherwise, I'll update this series.

- dirty log check
As we will drop this ptach, we'll have no call site.

- KVM_BUG_ON() in main.c
We should drop them because their logic isn't complex.

- KVM_BUG_ON() in tdx.c
- The error check of the return value from SEAMCALL
We should keep it as it's unexpected error from TDX module. When we hit this, we should mark the guest bugged and prevent further operation. It's hard to deduce the reason. TDX mdoule might be broken.

- Other check
We should drop them.

Signature · 1 line
Isaku Yamahata <[email protected]>
#669 lore raw

Re: [PATCH v19 120/130] KVM: TDX: Add a method to ignore dirty logging

Quoted 7 lines · Isaku Yamahata

I categorize as follows. Unless otherwise, I'll update this series.

- dirty log check
  As we will drop this ptach, we'll have no call site.

- KVM_BUG_ON() in main.c
  We should drop them because their logic isn't complex.

What about "KVM: TDX: Add methods to ignore guest instruction emulation"? Is it cleanly blocked somehow?

Quoted 8 lines


- KVM_BUG_ON() in tdx.c
  - The error check of the return value from SEAMCALL
    We should keep it as it's unexpected error from TDX module. When we hit
    this, we should mark the guest bugged and prevent further operation.  It's
    hard to deduce the reason.  TDX mdoule might be broken.

Yes. Makes sense.

Quoted 3 lines

  - Other check
    We should drop them.

Offhand, I'm not sure what is in this category.