Skip to contentSkip to conversation

TDX KVM selftests

PATCH Superseded Lisa Wang 8 participants 106 messages Jul 22 – Oct 12

Subsystems (8)
x86 KVM Kselftest Crypto API +4 more subsystems

v14 of 16 Superseded Latest is v16 →

lore
Share & tools
lore

Lisa Wang

author
#0 lore raw

v14 cover TDX KVM selftests

This patch series focuses on setting up a TDX VM and adding all code necessary to run a basic lifecycle test.

Unlike standard KVM selftests can set up the VM through guest registers, TDX module protects TDs' register state from the host. This feature of TDX causes problems on VM boot state initialization and the ucall implementation.

In standard KVM selftests, the host directly initializes the guest state by manipulating Special Registers (SREGs) and General Purpose Registers (GPRs) via IOCTLs (KVM_SET_SREGS, etc.) before the first KVM_RUN.

To bypass direct register initialization by the host, we utilize the standard x86 reset vector as the default entry point.

The mechanism works as follows:
1. The host places register values into a specific memory region and inserts boot code at the VM's default starting point.
2. When the VM starts, it executes this boot code to "pull" values from memory and manually set up its own SREGs and GPRs.
3. Once the environment is ready, the boot code jumps to the guest code.

The standard x86 ucall() implementation uses PIO, but it does not actually transmit data through the 4-byte PIO data. Instead, it relies on the host reading the ucall address directly from the guest's RDI register.

TDX selftests cannot utilize the standard x86 ucall implementation, because the host is unable to access the guest's RDI register. Based on this restriction, we used TDCALL with an 8-byte MMIO data to implement ucall. This method is the cleanest and efficient implementation for the overall ucall architecture.

v14 revision for TDX KVM selftests based on kvm/next and guest_memfd:
In-place conversion support[1]. This series needs some changes from in-place conversion v9. For ease of testing, an extra hack commit has been added. The complete code is available at:
https://github.com/googleprodkernel/linux-cc/commits/tdx-selftests-v14

Changes from v13[2]:
1. Fixed some bugs, including typo and commit messages.
2. Outlined TDCALL from tdx.c to tdx.S.
3. Used HPET(0xfed00000) for ucall GPA address.
4. Supported guest_memfd w/wo in-place conversion support.

Thanks review from Ackerley, Binbin, Chenyi, Sean and Xiaoyao!

Series is organized by:
1. Patches 1 - 4: Initialize the TDX VM
2. Patches 5 - 8: Add the TDX boot code
3. Patches 9 - 13: Set up the boot region
4. Patches 14 - 17: Set up the vCPU
5. Patches 18 - 19: Finalize the TDX VM
6. Patches 20 - 22: Implement the ucall and run the TDX test

[1]: https://lore.kernel.org/all/[email protected]/T/
[2]: https://lore.kernel.org/all/[email protected]/

---
Ackerley Tng (2):
KVM: selftests: Add helpers to init TDX memory and finalize VM
KVM: selftests: Add ucall support for TDX

Erdem Aktas (2):
KVM: selftests: Add TDX boot code
KVM: selftests: Implement MMIO WRITE for the TDX VM

Isaku Yamahata (2):
KVM: selftests: Update kvm_init_vm_address_properties() for TDX
KVM: selftests: TDX: Use KVM_TDX_CAPABILITIES to validate TDs' attribute configuration

Lisa Wang (2):
KVM: selftests: Require guest_memfd for TDX VMs
KVM: selftests: Support guest_memfd in-place conversion

Sagi Shahar (13):
KVM: selftests: Initialize the TDX VM
KVM: selftests: Expose segment definitions to assembly files
tools: include: Add kbuild.h for assembly structure offsets
KVM: selftests: Introduce structures for TDX guest boot parameters
KVM: selftests: Expose functions to get default sregs values
KVM: selftests: Set up TDX boot code region
KVM: selftests: Set up TDX boot parameters region
KVM: selftests: Expose function to allocate vCPU stack
KVM: selftests: Call KVM_TDX_INIT_VCPU when creating a new TDX vcpu
KVM: selftests: Load per-vCPU guest stack in TDX boot parameters
KVM: selftests: Set entry point for TDX guest code
KVM: selftests: Finalize TD memory as part of kvm_arch_vm_finalize_vcpus
KVM: selftests: Add TDX lifecycle test

Sean Christopherson (1):
KVM: selftests: Add macros to simplify creating VM shapes for non-default types

 tools/include/linux/kbuild.h                       |  11 +
 tools/testing/selftests/kvm/.gitignore             |   3 +-
 tools/testing/selftests/kvm/Makefile.kvm           |  33 ++-
 tools/testing/selftests/kvm/include/kvm_util.h     |  27 +-
 .../testing/selftests/kvm/include/x86/processor.h  |  44 +++
 .../selftests/kvm/include/x86/processor_asm.h      |  12 +
 tools/testing/selftests/kvm/include/x86/sev.h      |   2 -
 .../selftests/kvm/include/x86/tdx/td_boot.h        |  82 ++++++
 tools/testing/selftests/kvm/include/x86/tdx/tdx.h  |  17 ++
 .../selftests/kvm/include/x86/tdx/tdx_util.h       |  82 ++++++
 tools/testing/selftests/kvm/include/x86/ucall.h    |   6 -
 tools/testing/selftests/kvm/lib/kvm_util.c         |  22 +-
 tools/testing/selftests/kvm/lib/ucall_common.c     |   8 +
 tools/testing/selftests/kvm/lib/x86/processor.c    | 125 +++++---
 tools/testing/selftests/kvm/lib/x86/sev.c          |  16 --
 tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S  |  61 ++++
 .../selftests/kvm/lib/x86/tdx/td_boot_offsets.c    |  21 ++
 tools/testing/selftests/kvm/lib/x86/tdx/tdx.S      |  37 +++
 tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c | 316 +++++++++++++++++++++
 tools/testing/selftests/kvm/lib/x86/ucall.c        |  33 +++
 tools/testing/selftests/kvm/x86/sev_smoke_test.c   |  40 +--
 tools/testing/selftests/kvm/x86/tdx_vm_test.c      |  33 +++
 22 files changed, 927 insertions(+), 104 deletions(-)

---
base-commit: 2145a419fb2a4cdc8eefc8497425d2827f46e660
change-id: 20260722-tdx-selftests-1da5587cf047

Best regards,

Signature · 1 line
Lisa Wang <[email protected]>
author
#1 lore raw

PATCH v14 1/22 KVM: selftests: Add macros to simplify creating VM shapes for non-default types

From: Sean Christopherson <[email protected]>

Add VM_TYPE() and __VM_SHAPE() macros to create a vm_shape structure given a type (and mode), and use the macros to define VM_SHAPE_{SEV,SEV_ES,SNP} shapes for x86's SEV family of VM shapes. Providing common infrastructure will avoid having to copy+paste vm_sev_create_with_one_vcpu() for TDX.

Use the new SEV+ shapes and drop vm_sev_create_with_one_vcpu().

Opportunistically move the existing VM_SHAPE() (now __VM_SHAPE()) macro below the definitions of VM_MODE_DEFAULT so that all of the SHAPE/TYPE macros are bundled together.

No functional change intended.

---
 tools/testing/selftests/kvm/include/kvm_util.h     | 27 ++++++++-------
 .../testing/selftests/kvm/include/x86/processor.h  |  4 +++
 tools/testing/selftests/kvm/include/x86/sev.h      |  2 --
 tools/testing/selftests/kvm/lib/x86/sev.c          | 16 ---------
 tools/testing/selftests/kvm/x86/sev_smoke_test.c   | 40 +++++++++++-----------
 5 files changed, 39 insertions(+), 50 deletions(-)
tools/testing/selftests/kvm/include/kvm_util.h +15 -12
index dc70c6da63fa..2a3923dad570 100644
@@ -221,18 +221,6 @@ struct vm_shape {
 
 kvm_static_assert(sizeof(struct vm_shape) == sizeof(u64));
 
-#define VM_TYPE_DEFAULT			0
-
-#define VM_SHAPE(__mode)			\
-({						\
-	struct vm_shape shape = {		\
-		.mode = (__mode),		\
-		.type = VM_TYPE_DEFAULT		\
-	};					\
-						\
-	shape;					\
-})
-
 extern enum vm_guest_mode vm_mode_default;
 
 #if defined(__aarch64__)
@@ -270,8 +258,23 @@ extern enum vm_guest_mode vm_mode_default;
 
 #endif
 
+#define VM_TYPE_DEFAULT			0
+
+#define __VM_SHAPE(__mode, __type)		\
+({						\
+	struct vm_shape shape = {		\
+		.mode = (__mode),		\
+		.type = (__type),		\
+	};					\
+						\
+	shape;					\
+})
+
+#define VM_SHAPE(__mode)	__VM_SHAPE(__mode, VM_TYPE_DEFAULT)
 #define VM_SHAPE_DEFAULT	VM_SHAPE(VM_MODE_DEFAULT)
 
+#define VM_TYPE(__type)		__VM_SHAPE(VM_MODE_DEFAULT, __type)
+
 #define MIN_PAGE_SIZE		(1U << MIN_PAGE_SHIFT)
 #define PTES_PER_MIN_PAGE	ptes_per_page(MIN_PAGE_SIZE)
 
tools/testing/selftests/kvm/include/x86/processor.h +4 -0
index 77f576ee7789..0aa6eecfcbde 100644
@@ -365,6 +365,10 @@ static inline unsigned int x86_model(unsigned int eax)
 	return ((eax >> 12) & 0xf0) | ((eax >> 4) & 0x0f);
 }
 
+#define VM_SHAPE_SEV		VM_TYPE(KVM_X86_SEV_VM)
+#define VM_SHAPE_SEV_ES		VM_TYPE(KVM_X86_SEV_ES_VM)
+#define VM_SHAPE_SNP		VM_TYPE(KVM_X86_SNP_VM)
+
 #define PHYSICAL_PAGE_MASK      GENMASK_ULL(51, 12)
 
 #define PAGE_SHIFT		12
tools/testing/selftests/kvm/include/x86/sev.h +0 -2
index 1af44c151d60..944c59dbe510 100644
@@ -53,8 +53,6 @@ void snp_vm_launch_start(struct kvm_vm *vm, u64 policy);
 void snp_vm_launch_update(struct kvm_vm *vm);
 void snp_vm_launch_finish(struct kvm_vm *vm);
 
-struct kvm_vm *vm_sev_create_with_one_vcpu(u32 type, void *guest_code,
-					   struct kvm_vcpu **cpu);
 void vm_sev_launch(struct kvm_vm *vm, u64 policy, u8 *measurement);
 
 kvm_static_assert(SEV_RET_SUCCESS == 0);
tools/testing/selftests/kvm/lib/x86/sev.c +0 -16
index 93f916903461..95d8520eea34 100644
@@ -158,22 +158,6 @@ void snp_vm_launch_finish(struct kvm_vm *vm)
 	vm_sev_ioctl(vm, KVM_SEV_SNP_LAUNCH_FINISH, &launch_finish);
 }
 
-struct kvm_vm *vm_sev_create_with_one_vcpu(u32 type, void *guest_code,
-					   struct kvm_vcpu **cpu)
-{
-	struct vm_shape shape = {
-		.mode = VM_MODE_DEFAULT,
-		.type = type,
-	};
-	struct kvm_vm *vm;
-	struct kvm_vcpu *cpus[1];
-
-	vm = __vm_create_with_vcpus(shape, 1, 0, guest_code, cpus);
-	*cpu = cpus[0];
-
-	return vm;
-}
-
 void vm_sev_launch(struct kvm_vm *vm, u64 policy, u8 *measurement)
 {
 	if (is_sev_snp_vm(vm)) {
tools/testing/selftests/kvm/x86/sev_smoke_test.c +20 -20
index 1a49ee391586..fe2c438882ae 100644
@@ -104,7 +104,7 @@ static void compare_xsave(u8 *from_host, u8 *from_guest)
 		abort();
 }
 
-static void test_sync_vmsa(u32 type, u64 policy)
+static void test_sync_vmsa(struct vm_shape shape, u64 policy)
 {
 	struct kvm_vcpu *vcpu;
 	struct kvm_vm *vm;
@@ -114,7 +114,7 @@ static void test_sync_vmsa(u32 type, u64 policy)
 	double x87val = M_PI;
 	struct kvm_xsave __attribute__((aligned(64))) xsave = { 0 };
 
-	vm = vm_sev_create_with_one_vcpu(type, guest_code_xsave, &vcpu);
+	vm = vm_create_shape_with_one_vcpu(shape, &vcpu, guest_code_xsave);
 	gva = vm_alloc_shared(vm, PAGE_SIZE, KVM_UTIL_MIN_VADDR,
 			      MEM_REGION_TEST_DATA);
 	hva = addr_gva2hva(vm, gva);
@@ -150,13 +150,13 @@ static void test_sync_vmsa(u32 type, u64 policy)
 	kvm_vm_free(vm);
 }
 
-static void test_sev(void *guest_code, u32 type, u64 policy)
+static void test_sev(void *guest_code, struct vm_shape shape, u64 policy)
 {
 	struct kvm_vcpu *vcpu;
 	struct kvm_vm *vm;
 	struct ucall uc;
 
-	vm = vm_sev_create_with_one_vcpu(type, guest_code, &vcpu);
+	vm = vm_create_shape_with_one_vcpu(shape, &vcpu, guest_code);
 
 	/* TODO: Validate the measurement is as expected. */
 	vm_sev_launch(vm, policy, NULL);
@@ -201,12 +201,12 @@ static void guest_shutdown_code(void)
 	__asm__ __volatile__("ud2");
 }
 
-static void test_sev_shutdown(u32 type, u64 policy)
+static void test_sev_shutdown(struct vm_shape shape, u64 policy)
 {
 	struct kvm_vcpu *vcpu;
 	struct kvm_vm *vm;
 
-	vm = vm_sev_create_with_one_vcpu(type, guest_shutdown_code, &vcpu);
+	vm = vm_create_shape_with_one_vcpu(shape, &vcpu, guest_shutdown_code);
 
 	vm_sev_launch(vm, policy, NULL);
 
@@ -218,28 +218,28 @@ static void test_sev_shutdown(u32 type, u64 policy)
 	kvm_vm_free(vm);
 }
 
-static void test_sev_smoke(void *guest, u32 type, u64 policy)
+static void test_sev_smoke(void *guest, struct vm_shape shape, u64 policy)
 {
 	const u64 xf_mask = XFEATURE_MASK_X87_AVX;
 
-	if (type == KVM_X86_SNP_VM)
-		test_sev(guest, type, policy | SNP_POLICY_DBG);
+	if (shape.type == KVM_X86_SNP_VM)
+		test_sev(guest, shape, policy | SNP_POLICY_DBG);
 	else
-		test_sev(guest, type, policy | SEV_POLICY_NO_DBG);
-	test_sev(guest, type, policy);
+		test_sev(guest, shape, policy | SEV_POLICY_NO_DBG);
+	test_sev(guest, shape, policy);
 
-	if (type == KVM_X86_SEV_VM)
+	if (shape.type == KVM_X86_SEV_VM)
 		return;
 
-	test_sev_shutdown(type, policy);
+	test_sev_shutdown(shape, policy);
 
 	if (kvm_has_cap(KVM_CAP_XCRS) &&
 	    (xgetbv(0) & kvm_cpu_supported_xcr0() & xf_mask) == xf_mask) {
-		test_sync_vmsa(type, policy);
-		if (type == KVM_X86_SNP_VM)
-			test_sync_vmsa(type, policy | SNP_POLICY_DBG);
+		test_sync_vmsa(shape, policy);
+		if (shape.type == KVM_X86_SNP_VM)
+			test_sync_vmsa(shape, policy | SNP_POLICY_DBG);
 		else
-			test_sync_vmsa(type, policy | SEV_POLICY_NO_DBG);
+			test_sync_vmsa(shape, policy | SEV_POLICY_NO_DBG);
 	}
 }
 
@@ -247,13 +247,13 @@ int main(int argc, char *argv[])
 {
 	TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_SEV));
 
-	test_sev_smoke(guest_sev_code, KVM_X86_SEV_VM, 0);
+	test_sev_smoke(guest_sev_code, VM_SHAPE_SEV, 0);
 
 	if (kvm_cpu_has(X86_FEATURE_SEV_ES))
-		test_sev_smoke(guest_sev_es_code, KVM_X86_SEV_ES_VM, SEV_POLICY_ES);
+		test_sev_smoke(guest_sev_es_code, VM_SHAPE_SEV_ES, SEV_POLICY_ES);
 
 	if (kvm_cpu_has(X86_FEATURE_SEV_SNP))
-		test_sev_smoke(guest_snp_code, KVM_X86_SNP_VM, snp_default_policy());
+		test_sev_smoke(guest_snp_code, VM_SHAPE_SNP, snp_default_policy());
 
 	return 0;
 }
Signature · 1 line
2.55.0.229.g6434b31f56-goog
#2 lore raw

Re: [PATCH v14 01/22] KVM: selftests: Add macros to simplify creating VM shapes for non-default types

Quoted 13 lines · Lisa Wang

From: Sean Christopherson <[email protected]>

Add VM_TYPE() and __VM_SHAPE() macros to create a vm_shape structure given a type (and mode), and use the macros to define VM_SHAPE_{SEV,SEV_ES,SNP} shapes for x86's SEV family of VM shapes. Providing common infrastructure will avoid having to copy+paste vm_sev_create_with_one_vcpu() for TDX.

Use the new SEV+ shapes and drop vm_sev_create_with_one_vcpu().

Opportunistically move the existing VM_SHAPE() (now __VM_SHAPE()) macro below the definitions of VM_MODE_DEFAULT so that all of the SHAPE/TYPE macros are bundled together.

Quoted 6 lines

No functional change intended.

Missing the submitter's SOB?

Quoted 8 lines
---
 tools/testing/selftests/kvm/include/kvm_util.h     | 27 ++++++++-------
 .../testing/selftests/kvm/include/x86/processor.h  |  4 +++
 tools/testing/selftests/kvm/include/x86/sev.h      |  2 --
 tools/testing/selftests/kvm/lib/x86/sev.c          | 16 ---------
 tools/testing/selftests/kvm/x86/sev_smoke_test.c   | 40 +++++++++++-----------
 5 files changed, 39 insertions(+), 50 deletions(-)
Reviewed-by on v14 1/22
#3 lore raw

Re: [PATCH v14 01/22] KVM: selftests: Add macros to simplify creating VM shapes for non-default types

In reply to Lisa Wang
Quoted 13 lines · Lisa Wang

From: Sean Christopherson <[email protected]>

Add VM_TYPE() and __VM_SHAPE() macros to create a vm_shape structure given a type (and mode), and use the macros to define VM_SHAPE_{SEV,SEV_ES,SNP} shapes for x86's SEV family of VM shapes. Providing common infrastructure will avoid having to copy+paste vm_sev_create_with_one_vcpu() for TDX.

Use the new SEV+ shapes and drop vm_sev_create_with_one_vcpu().

Opportunistically move the existing VM_SHAPE() (now __VM_SHAPE()) macro below the definitions of VM_MODE_DEFAULT so that all of the SHAPE/TYPE macros are bundled together.

Quoted 9 lines

No functional change intended.

[...snip...]

author
#4 lore raw

PATCH v14 2/22 KVM: selftests: Update kvm_init_vm_address_properties() for TDX

In reply to Lisa Wang

From: Isaku Yamahata <[email protected]>

Initialize the TDX S-bit and the GPA tag mask in kvm_init_vm_address_properties() for TDX VMs, similar to how the C-bit is initialized for SEV VMs.

The TDX S-bit is used to distinguish between shared and private guest physical addresses. Its position is determined by the guest physical address width, which is either 48 or 52 bits for current TDX implementations.

---
 tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h | 14 ++++++++++++++
 tools/testing/selftests/kvm/lib/x86/processor.c        | 12 ++++++++++--
 2 files changed, 24 insertions(+), 2 deletions(-)
tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h new file +14 -0
new file mode 100644
index 000000000000..f647e6ca6b34
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef SELFTESTS_TDX_TDX_UTIL_H
+#define SELFTESTS_TDX_TDX_UTIL_H
+
+#include <stdbool.h>
+
+#include "kvm_util.h"
+
+static inline bool is_tdx_vm(struct kvm_vm *vm)
+{
+	return vm->type == KVM_X86_TDX_VM;
+}
+
+#endif /* SELFTESTS_TDX_TDX_UTIL_H */
tools/testing/selftests/kvm/lib/x86/processor.c +10 -2
index b51467d70f6e..b68ad1dc7e02 100644
@@ -11,6 +11,7 @@
 #include "smm.h"
 #include "svm_util.h"
 #include "sev.h"
+#include "tdx/tdx_util.h"
 #include "vmx.h"
 
 #ifndef NUM_INTERRUPTS
@@ -1311,12 +1312,19 @@ void kvm_get_cpu_address_width(unsigned int *pa_bits, unsigned int *va_bits)
 
 void kvm_init_vm_address_properties(struct kvm_vm *vm)
 {
+	u32 gpa_bits = kvm_cpu_property(X86_PROPERTY_GUEST_MAX_PHY_ADDR);
+
+	vm->arch.sev_fd = -1;
+
 	if (is_sev_vm(vm)) {
 		vm->arch.sev_fd = open_sev_dev_path_or_exit();
 		vm->arch.c_bit = BIT_ULL(this_cpu_property(X86_PROPERTY_SEV_C_BIT));
 		vm->gpa_tag_mask = vm->arch.c_bit;
-	} else {
-		vm->arch.sev_fd = -1;
+	} else if (is_tdx_vm(vm)) {
+		TEST_ASSERT(gpa_bits == 48 || gpa_bits == 52,
+			    "TDX: bad X86_PROPERTY_GUEST_MAX_PHY_ADDR value: %u", gpa_bits);
+		vm->arch.s_bit = BIT_ULL(gpa_bits - 1);
+		vm->gpa_tag_mask = vm->arch.s_bit;
 	}
 }
 
Signature · 1 line
2.55.0.229.g6434b31f56-goog
#5 lore raw

Re: [PATCH v14 02/22] KVM: selftests: Update kvm_init_vm_address_properties() for TDX

Quoted 10 lines · Lisa Wang

From: Isaku Yamahata <[email protected]>

Initialize the TDX S-bit and the GPA tag mask in kvm_init_vm_address_properties() for TDX VMs, similar to how the C-bit is initialized for SEV VMs.

The TDX S-bit is used to distinguish between shared and private guest physical addresses. Its position is determined by the guest physical address width, which is either 48 or 52 bits for current TDX implementations.

Since S-bit=1 means shared and C-bit=1 means private, we can't have a single bit. I'd justify why a second field is needed. For "untagging" GPAs we could have a single field, but there are other usages?

Quoted 11 lines

---

Nit: these are not ordered correctly. I think KVM prefers the order in:
Documentation/process/maintainer-tip.rst

But I think at least the RBs can be grouped together. Also... can't really point fingers here, but that is a fair amount of of patch history.

#6 lore raw

Re: [PATCH v14 02/22] KVM: selftests: Update kvm_init_vm_address_properties() for TDX

Quoted 7 lines · Rick P
Quoted 6 lines · Lisa Wang

From: Isaku Yamahata <[email protected]>

Initialize the TDX S-bit and the GPA tag mask in kvm_init_vm_address_properties() for TDX VMs, similar to how the C-bit is initialized for SEV VMs.

Quoted 7 lines
Quoted 4 lines

The TDX S-bit is used to distinguish between shared and private guest physical addresses. Its position is determined by the guest physical address width, which is either 48 or 52 bits for current TDX implementations.

Since S-bit=1 means shared and C-bit=1 means private, we can't have a single bit. I'd justify why a second field is needed.

The s_bit field was introduced prior to this patch series, so I think the patch introducing the s_bit field should have justified the addition of this second field.

Did you mean that we should add something related to "Since S-bit=1 means shared and C-bit=1 means private, we can't have a single bit." in the commit message to reiterate/as a refresher?

Quoted 3 lines

For "untagging" GPAs we could
have a single field, but there are other usages?

I think this part in __virt_pg_map() requires separate fields, are you requesting to only retain one of the c_bit/s_bit vs gpa_tag_mask?

	if (vm_is_gpa_protected(vm, gpa))
		*pte |= PTE_C_BIT_MASK(mmu);
	else
		*pte |= PTE_S_BIT_MASK(mmu);

I think if there's any unification/simplification required among these fields, perhaps that can be left to another series.

Quoted 11 lines
Quoted 11 lines

---

Quoted 5 lines

Nit: these are not ordered correctly. I think KVM prefers the order in:
Documentation/process/maintainer-tip.rst

But I think at least the RBs can be grouped together. Also... can't really point fingers here, but that is a fair amount of of patch history.

I was also involved in some earlier revision of this series, tracing this history is not trivial...

For this series, given the long history and many handoffs, shall we help Lisa out by explicitly requesting to be credited on specific patches? Emailing either Lisa/Ackerley privately or on-list is fine :)

author
#7 lore raw

PATCH v14 3/22 KVM: selftests: Initialize the TDX VM

In reply to Lisa Wang

From: Sagi Shahar <[email protected]>

Add tdx_init_vm() to handle the mandatory VM-level initialization sequence required for Intel TDX.

For TDX, the guest's CPUID configuration must be "sealed" during KVM_TDX_INIT_VM before any vCPUs are created. This is necessary because the TDX hardware directly virtualizes CPUID and includes the configuration in the guest's initial security measurement.

The helper calculates the required CPUID values by filtering the host- supported bits (kvm_get_supported_cpuid) against the "directly configurable" bits reported by KVM_TDX_CAPABILITIES, ensuring compliance with the strict requirements of the TDH.MNG.INIT SEAMCALL.

---
 tools/testing/selftests/kvm/Makefile.kvm           |   1 +
 .../testing/selftests/kvm/include/x86/processor.h  |   2 +
 .../selftests/kvm/include/x86/tdx/tdx_util.h       |  35 ++++++
 tools/testing/selftests/kvm/lib/x86/processor.c    |  21 +++-
 tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c | 120 +++++++++++++++++++++
 5 files changed, 175 insertions(+), 4 deletions(-)
tools/testing/selftests/kvm/Makefile.kvm +1 -0
index e5769268936a..3f98d1c6488c 100644
@@ -27,6 +27,7 @@ LIBKVM_x86 += lib/x86/pmu.c
 LIBKVM_x86 += lib/x86/processor.c
 LIBKVM_x86 += lib/x86/sev.c
 LIBKVM_x86 += lib/x86/svm.c
+LIBKVM_x86 += lib/x86/tdx/tdx_util.c
 LIBKVM_x86 += lib/x86/ucall.c
 LIBKVM_x86 += lib/x86/vmx.c
 
tools/testing/selftests/kvm/include/x86/processor.h +2 -0
index 0aa6eecfcbde..76180dfaffea 100644
@@ -956,6 +956,8 @@ static inline void vcpu_xcrs_set(struct kvm_vcpu *vcpu, struct kvm_xcrs *xcrs)
 	vcpu_ioctl(vcpu, KVM_SET_XCRS, xcrs);
 }
 
+const struct kvm_cpuid_entry2 *__get_cpuid_entry(const struct kvm_cpuid2 *cpuid,
+						 u32 function, u32 index);
 const struct kvm_cpuid_entry2 *get_cpuid_entry(const struct kvm_cpuid2 *cpuid,
 					       u32 function, u32 index);
 const struct kvm_cpuid2 *kvm_get_supported_cpuid(void);
tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h +35 -0
index f647e6ca6b34..eb8602dce0bc 100644
@@ -11,4 +11,39 @@ static inline bool is_tdx_vm(struct kvm_vm *vm)
 	return vm->type == KVM_X86_TDX_VM;
 }
 
+/*
+ * TDX ioctls
+ * Use underscores to avoid collisions with struct member names.
+ */
+#define __tdx_vm_ioctl(vm, cmd, _flags, arg)				\
+({									\
+	u64 r;								\
+									\
+	union {								\
+		struct kvm_tdx_cmd c;					\
+		unsigned long raw;					\
+	} tdx_cmd = { .c = {						\
+		.id = (cmd),						\
+		.flags = (u32)(_flags),					\
+		.data = (u64)(arg),					\
+	} };								\
+									\
+	r = __vm_ioctl(vm, KVM_MEMORY_ENCRYPT_OP, &tdx_cmd.raw);	\
+	r ?: tdx_cmd.c.hw_error;					\
+})
+
+#define tdx_vm_ioctl(vm, cmd, flags, arg)				\
+({									\
+	u64 ret = __tdx_vm_ioctl(vm, cmd, flags, arg);			\
+									\
+	if (ret) {							\
+		TEST_ASSERT(!ret,					\
+			    "%s failed, rc: 0x%llx errno: %i (%s)",	\
+			    #cmd, (unsigned long long)ret,		\
+			    errno, strerror(errno));			\
+	}								\
+})
+
+void tdx_init_vm(struct kvm_vm *vm, u64 attributes);
+
 #endif /* SELFTESTS_TDX_TDX_UTIL_H */
tools/testing/selftests/kvm/lib/x86/processor.c +17 -4
index b68ad1dc7e02..7d23344854cc 100644
@@ -802,6 +802,9 @@ void kvm_arch_vm_post_create(struct kvm_vm *vm, unsigned int nr_vcpus)
 		vm_sev_ioctl(vm, KVM_SEV_INIT2, &init);
 	}
 
+	if (is_tdx_vm(vm))
+		tdx_init_vm(vm, 0);
+
 	r = __vm_ioctl(vm, KVM_GET_TSC_KHZ, NULL);
 	TEST_ASSERT(r > 0, "KVM_GET_TSC_KHZ did not provide a valid TSC frequency.");
 	guest_tsc_khz = r;
@@ -1328,8 +1331,8 @@ void kvm_init_vm_address_properties(struct kvm_vm *vm)
 	}
 }
 
-const struct kvm_cpuid_entry2 *get_cpuid_entry(const struct kvm_cpuid2 *cpuid,
-					       u32 function, u32 index)
+const struct kvm_cpuid_entry2 *__get_cpuid_entry(const struct kvm_cpuid2 *cpuid,
+						 u32 function, u32 index)
 {
 	int i;
 
@@ -1339,11 +1342,21 @@ const struct kvm_cpuid_entry2 *get_cpuid_entry(const struct kvm_cpuid2 *cpuid,
 			return &cpuid->entries[i];
 	}
 
-	TEST_FAIL("CPUID function 0x%x index 0x%x not found ", function, index);
-
 	return NULL;
 }
 
+const struct kvm_cpuid_entry2 *get_cpuid_entry(const struct kvm_cpuid2 *cpuid,
+					       u32 function, u32 index)
+{
+	const struct kvm_cpuid_entry2 *entry;
+
+	entry = __get_cpuid_entry(cpuid, function, index);
+	if (!entry)
+		TEST_FAIL("CPUID function 0x%x index 0x%x not found ", function, index);
+
+	return entry;
+}
+
 #define X86_HYPERCALL(inputs...)					\
 ({									\
 	u64 r;							\
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c new file +120 -0

120 lines not on this page · raw message

Signature · 1 line
2.55.0.229.g6434b31f56-goog
#8 lore raw

Re: [PATCH v14 03/22] KVM: selftests: Initialize the TDX VM

Quoted 14 lines · Lisa Wang

From: Sagi Shahar <[email protected]>

Add tdx_init_vm() to handle the mandatory VM-level initialization sequence required for Intel TDX.

For TDX, the guest's CPUID configuration must be "sealed" during KVM_TDX_INIT_VM before any vCPUs are created. This is necessary because the TDX hardware directly virtualizes CPUID and includes the configuration in the guest's initial security measurement.

The helper calculates the required CPUID values by filtering the host- supported bits (kvm_get_supported_cpuid) against the "directly configurable" bits reported by KVM_TDX_CAPABILITIES, ensuring compliance with the strict requirements of the TDH.MNG.INIT SEAMCALL.

<snip>

tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h static inline bool is_tdx_vm(struct kvm_vm *vm) · line 18

Quoted 5 lines
tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h +5 -0
+/*
+ * TDX ioctls
+ * Use underscores to avoid collisions with struct member names.
+ */
+#define __tdx_vm_ioctl(vm, cmd, _flags, arg)				\

sev uses the name __vm_sev_ioctl, I think we need to keep them consistent.

Quoted 4 lines
tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h +4 -0
+({									\
+	u64 r;								\
+									\
+	union {								\

tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h static inline bool is_tdx_vm(struct kvm_vm *vm) · line 32

Quoted 10 lines
tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h +10 -0
+		struct kvm_tdx_cmd c;					\
+		unsigned long raw;					\
+	} tdx_cmd = { .c = {						\
+		.id = (cmd),						\
+		.flags = (u32)(_flags),					\
+		.data = (u64)(arg),					\
+	} };								\
+									\
+	r = __vm_ioctl(vm, KVM_MEMORY_ENCRYPT_OP, &tdx_cmd.raw);	\
+	r ?: tdx_cmd.c.hw_error;					\

I know it takes the same handling from __vm_sev_ioctl(). But I think the handling for hw_error is not correct, at least for TDX (I didn't check for SEV).

the hw_error is the additional info, to tell the SEAMCALL return code, when the IOCTL fails. KVM requires hw_error to be in the input, and KVM puts the SEAMCALL return code into hw_error when the IOCTL fails due to SEAMCALL failure. That means, when r == 0, the hw_error is always 0.

I think we need to provide hw_error along with r to the caller so that caller can print them together.

tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h static inline bool is_tdx_vm(struct kvm_vm *vm) · line 43

Quoted 11 lines
tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h +11 -0
+})
+
+#define tdx_vm_ioctl(vm, cmd, flags, arg)				\
+({									\
+	u64 ret = __tdx_vm_ioctl(vm, cmd, flags, arg);			\
+									\
+	if (ret) {							\
+		TEST_ASSERT(!ret,					\
+			    "%s failed, rc: 0x%llx errno: %i (%s)",	\
+			    #cmd, (unsigned long long)ret,		\
+			    errno, strerror(errno));			\

The if() looks silly. Why add it? And why change it from __TEST_ASSERT_VM_VCPU_IOCTL() in the v13?

Considering the suggestion of hw_error above, I think we need to introduce the TEST_ASSERT_TDX_VM_VCPU_IOCTL() which accepts additional hw_error?

<snip>

tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c line 6

Quoted 12 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c new file +6 -0
new file mode 100644
index 000000000000..e1ffb67a106c
@@ -0,0 +1,120 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include "processor.h"
+#include "tdx/tdx_util.h"
+
+static struct kvm_tdx_capabilities *tdx_read_capabilities(struct kvm_vm *vm)

make it const, is better.

<snip>

Quoted 11 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c new file +11 -0
+
+void tdx_init_vm(struct kvm_vm *vm, u64 attributes)
+{
+	struct kvm_tdx_init_vm *init_vm;
+	const struct kvm_cpuid2 *tmp;
+	struct kvm_cpuid2 *cpuid;
+
+	tmp = kvm_get_supported_cpuid();
+
+	cpuid = allocate_kvm_cpuid2(tmp->nent);
+	memcpy(cpuid, tmp, kvm_cpuid2_size(tmp->nent));

tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c line 115

Quoted 10 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c new file +10 -0
+	tdx_filter_cpuid(vm, cpuid);
+
+	init_vm = calloc(1, sizeof(*init_vm) +
+			 sizeof(init_vm->cpuid.entries[0]) * cpuid->nent);
+	TEST_ASSERT(init_vm, "init_vm allocation failed");
+
+	memcpy(&init_vm->cpuid, cpuid, kvm_cpuid2_size(cpuid->nent));
+	free(cpuid);
+
+	init_vm->attributes = attributes;

Besides CPUID, it only allows attributes to be configure but leave XFAM as 0. I think the changelog needs to explain why we need to configure attributes.

The rest of the patch looks good to me.

Quoted 6 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c new file +5 -0
+
+	tdx_vm_ioctl(vm, KVM_TDX_INIT_VM, 0, init_vm);
+
+	free(init_vm);
+}
#9 lore raw

Re: [PATCH v14 03/22] KVM: selftests: Initialize the TDX VM

Quoted 7 lines · Xiaoyao Li

tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c line 115

Quoted 1 line
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c new file +1 -0
+	init_vm->attributes = attributes;

Besides CPUID, it only allows attributes to be configure but leave XFAM as 0. I think the changelog needs to explain why we need to configure attributes.

The rest of the patch looks good to me.

How about directly setting xfam to zero for clarity?

#10 lore raw

Re: [PATCH v14 03/22] KVM: selftests: Initialize the TDX VM

Quoted 10 lines · Rick P
Quoted 7 lines · Xiaoyao Li

tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c line 115

Quoted 1 line
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c new file +1 -0
+	init_vm->attributes = attributes;

Besides CPUID, it only allows attributes to be configure but leave XFAM as 0. I think the changelog needs to explain why we need to configure attributes.

The rest of the patch looks good to me.

How about directly setting xfam to zero for clarity?

Agreed. It's simple.

author
#11 lore raw

Re: [PATCH v14 03/22] KVM: selftests: Initialize the TDX VM

In reply to Xiaoyao Li
Quoted 4 lines · Xiaoyao Li
Quoted 2 lines

+ */
+#define __tdx_vm_ioctl(vm, cmd, _flags, arg) \

sev uses the name __vm_sev_ioctl, I think we need to keep them consistent.

While __vm_tdx_ioctl matches SEV, the TDX selftest follows a tdx_<scope>_* naming convention (1. TDX prefix, 2. Scope: VM or vCPU). I named it __tdx_vm_ioctl to keep the TDX codebase internally consistent.[1]

Do you think we should align with SEV's naming convention instead of sticking with the internal TDX pattern?

[1]: https://lore.kernel.org/kvm/[email protected]/

Quoted 19 lines
Quoted 4 lines
tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h +4 -0
+({									\
+	u64 r;								\
+									\
+	union {								\

tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h static inline bool is_tdx_vm(struct kvm_vm *vm) · line 32

Quoted 10 lines
tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h +10 -0
+		struct kvm_tdx_cmd c;					\
+		unsigned long raw;					\
+	} tdx_cmd = { .c = {						\
+		.id = (cmd),						\
+		.flags = (u32)(_flags),					\
+		.data = (u64)(arg),					\
+	} };								\
+									\
+	r = __vm_ioctl(vm, KVM_MEMORY_ENCRYPT_OP, &tdx_cmd.raw);	\
+	r ?: tdx_cmd.c.hw_error;					\

I know it takes the same handling from __vm_sev_ioctl(). But I think the handling for hw_error is not correct, at least for TDX (I didn't check for SEV).

Quoted 7 lines

the hw_error is the additional info, to tell the SEAMCALL return code, when the IOCTL fails. KVM requires hw_error to be in the input, and KVM puts the SEAMCALL return code into hw_error when the IOCTL fails due to SEAMCALL failure. That means, when r == 0, the hw_error is always 0.

I think we need to provide hw_error along with r to the caller so that caller can print them together.

I think the value of r is not important, because the ioctl failure is already captured in errno.

We only need to fix the return values for SEV and TDX and have TEST_ASSERT_* print formatted error logs with errno and hw_error.

- r ?: {tdx, sev}_cmd.c.hw_error;
+ r ? {tdx, sev}_cmd.c.hw_error : 0;

Quoted 15 lines

tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h static inline bool is_tdx_vm(struct kvm_vm *vm) · line 43

Quoted 11 lines
tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h +11 -0
+})
+
+#define tdx_vm_ioctl(vm, cmd, flags, arg)				\
+({									\
+	u64 ret = __tdx_vm_ioctl(vm, cmd, flags, arg);			\
+									\
+	if (ret) {							\
+		TEST_ASSERT(!ret,					\
+			    "%s failed, rc: 0x%llx errno: %i (%s)",	\
+			    #cmd, (unsigned long long)ret,		\
+			    errno, strerror(errno));			\

The if() looks silly. Why add it? And why change it from __TEST_ASSERT_VM_VCPU_IOCTL() in the v13?

Quoted 2 lines

Considering the suggestion of hw_error above, I think we need to introduce the TEST_ASSERT_TDX_VM_VCPU_IOCTL() which accepts additional hw_error?

The reason we could not use __TEST_ASSERT_VM_VCPU_IOCTL() directly[2] is because it formats ther return value as %i (32-bit), whereas __tdx_vm_ioctl might return a u64 hardware error code.

I agree with your suggestion to introduce a new TEST_ASSERT_TDX_VM_VCPU_IOCTL() macro to print out u64 hardware error code properly.

[2]: https://lore.kernel.org/all/[email protected]/

Quoted 15 lines

<snip>

tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c line 6

Quoted 12 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c new file +6 -0
new file mode 100644
index 000000000000..e1ffb67a106c
@@ -0,0 +1,120 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include "processor.h"
+#include "tdx/tdx_util.h"
+
+static struct kvm_tdx_capabilities *tdx_read_capabilities(struct kvm_vm *vm)

make it const, is better.

Thanks, noted.

Quoted 5 lines

tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c line 115

Quoted 1 line
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c new file +1 -0
+	init_vm->attributes = attributes;

Besides CPUID, it only allows attributes to be configure but leave XFAM as
0. I think the changelog needs to explain why we need to configure attributes.

Thanks, noted.

Quoted 1 line

The rest of the patch looks good to me.

#12 lore raw

Re: [PATCH v14 03/22] KVM: selftests: Initialize the TDX VM

Quoted 6 lines · Lisa Wang
Quoted 4 lines · Xiaoyao Li
Quoted 2 lines

+ */
+#define __tdx_vm_ioctl(vm, cmd, _flags, arg) \

sev uses the name __vm_sev_ioctl, I think we need to keep them consistent.

Quoted 9 lines

While __vm_tdx_ioctl matches SEV, the TDX selftest follows a tdx_<scope>_* naming convention (1. TDX prefix, 2. Scope: VM or vCPU). I named it __tdx_vm_ioctl to keep the TDX codebase internally consistent.[1]

Do you think we should align with SEV's naming convention instead of sticking with the internal TDX pattern?

[1]: https://lore.kernel.org/kvm/[email protected]/

Given that __vm_sev_ioctl and __tdx_vm_ioctl aren't likely to appear next to each other, I think it's better to have the tdx prefix earlier to keep the TDX code consistent. To move things along, I think we could continue as-is and not swap to align with sev.

(The sev functions actually look kind of inconsistent in sev.h, but that's a discussion for another series.)

Quoted 27 lines
Quoted 19 lines
Quoted 4 lines
tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h +4 -0
+({									\
+	u64 r;								\
+									\
+	union {								\

tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h static inline bool is_tdx_vm(struct kvm_vm *vm) · line 32

Quoted 10 lines
tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h +10 -0
+		struct kvm_tdx_cmd c;					\
+		unsigned long raw;					\
+	} tdx_cmd = { .c = {						\
+		.id = (cmd),						\
+		.flags = (u32)(_flags),					\
+		.data = (u64)(arg),					\
+	} };								\
+									\
+	r = __vm_ioctl(vm, KVM_MEMORY_ENCRYPT_OP, &tdx_cmd.raw);	\
+	r ?: tdx_cmd.c.hw_error;					\

I know it takes the same handling from __vm_sev_ioctl(). But I think the handling for hw_error is not correct, at least for TDX (I didn't check for SEV).

Quoted 7 lines

the hw_error is the additional info, to tell the SEAMCALL return code, when the IOCTL fails. KVM requires hw_error to be in the input, and KVM puts the SEAMCALL return code into hw_error when the IOCTL fails due to SEAMCALL failure. That means, when r == 0, the hw_error is always 0.

I think we need to provide hw_error along with r to the caller so that caller can print them together.

Quoted 9 lines

I think the value of r is not important, because the ioctl failure is already captured in errno.

We only need to fix the return values for SEV and TDX and have TEST_ASSERT_* print formatted error logs with errno and hw_error.

- r ?: {tdx, sev}_cmd.c.hw_error;
+ r ? {tdx, sev}_cmd.c.hw_error : 0;

I didn't look in detail, perhaps Lisa could look into these:

+ What are the possible values of r? Is it always going to be 1 on error?
+ Is r always positive or negative?
+ Is tdx_cmd.c.hw_error always positive or negative? It's probably not one of the standard Linux errors, right?

Perhaps squashing hw_error together with a retval conflates the two.

In the lower-level __tdx_vm_ioctl() we could pass a hw_error and have the macro set hw_error? That will allow the caller to print both.

Quoted 22 lines
Quoted 15 lines

tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h static inline bool is_tdx_vm(struct kvm_vm *vm) · line 43

Quoted 11 lines
tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h +11 -0
+})
+
+#define tdx_vm_ioctl(vm, cmd, flags, arg)				\
+({									\
+	u64 ret = __tdx_vm_ioctl(vm, cmd, flags, arg);			\
+									\
+	if (ret) {							\
+		TEST_ASSERT(!ret,					\
+			    "%s failed, rc: 0x%llx errno: %i (%s)",	\
+			    #cmd, (unsigned long long)ret,		\
+			    errno, strerror(errno));			\

The if() looks silly. Why add it? And why change it from __TEST_ASSERT_VM_VCPU_IOCTL() in the v13?

Quoted 2 lines

Considering the suggestion of hw_error above, I think we need to introduce the TEST_ASSERT_TDX_VM_VCPU_IOCTL() which accepts additional hw_error?

The reason we could not use __TEST_ASSERT_VM_VCPU_IOCTL() directly[2] is because it formats ther return value as %i (32-bit), whereas __tdx_vm_ioctl might return a u64 hardware error code.

Quoted 6 lines

I agree with your suggestion to introduce a new TEST_ASSERT_TDX_VM_VCPU_IOCTL() macro to print out u64 hardware error code properly.

[2]: https://lore.kernel.org/all/[email protected]/

Is tdx_vm_ioctl() the only place where TEST_ASSERT_TDX_VM_VCPU_IOCTL() is going to be used though? If so, maybe we should defer introducing TEST_ASSERT_TDX_VM_VCPU_IOCTL() till later.

I think the issue with if (ret) is just that TEST_ASSERT(!ret) already does that same check, and so we can drop the if (ret) part.

Quoted 28 lines
Quoted 15 lines

<snip>

tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c line 6

Quoted 12 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c new file +6 -0
new file mode 100644
index 000000000000..e1ffb67a106c
@@ -0,0 +1,120 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include "processor.h"
+#include "tdx/tdx_util.h"
+
+static struct kvm_tdx_capabilities *tdx_read_capabilities(struct kvm_vm *vm)

make it const, is better.

Thanks, noted.

Quoted 5 lines

tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c line 115

Quoted 1 line
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c new file +1 -0
+	init_vm->attributes = attributes;

Besides CPUID, it only allows attributes to be configure but leave XFAM as
0. I think the changelog needs to explain why we need to configure attributes.

Thanks, noted.

Quoted 1 line

The rest of the patch looks good to me.

#13 lore raw

Re: [PATCH v14 03/22] KVM: selftests: Initialize the TDX VM

Quoted 18 lines · Ackerley Tng
Quoted 6 lines · Lisa Wang
Quoted 4 lines · Xiaoyao Li
Quoted 2 lines

+ */
+#define __tdx_vm_ioctl(vm, cmd, _flags, arg) \

sev uses the name __vm_sev_ioctl, I think we need to keep them consistent.

Quoted 9 lines

While __vm_tdx_ioctl matches SEV, the TDX selftest follows a tdx_<scope>_* naming convention (1. TDX prefix, 2. Scope: VM or vCPU). I named it __tdx_vm_ioctl to keep the TDX codebase internally consistent.[1]

Do you think we should align with SEV's naming convention instead of sticking with the internal TDX pattern?

[1]: https://lore.kernel.org/kvm/[email protected]/

Quoted 7 lines

Given that __vm_sev_ioctl and __tdx_vm_ioctl aren't likely to appear next to each other, I think it's better to have the tdx prefix earlier to keep the TDX code consistent. To move things along, I think we could continue as-is and not swap to align with sev.

(The sev functions actually look kind of inconsistent in sev.h, but that's a discussion for another series.)

Yeah, either adjusting this patch to follow SEV or renaming existing SEV MACROs is OK.

Keep the patch as-is and leave the SEV work to future are OK.

Quoted 30 lines
Quoted 30 lines
Quoted 19 lines
Quoted 4 lines
tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h +4 -0
+({									\
+	u64 r;								\
+									\
+	union {								\

tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h static inline bool is_tdx_vm(struct kvm_vm *vm) · line 32

Quoted 10 lines
tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h +10 -0
+		struct kvm_tdx_cmd c;					\
+		unsigned long raw;					\
+	} tdx_cmd = { .c = {						\
+		.id = (cmd),						\
+		.flags = (u32)(_flags),					\
+		.data = (u64)(arg),					\
+	} };								\
+									\
+	r = __vm_ioctl(vm, KVM_MEMORY_ENCRYPT_OP, &tdx_cmd.raw);	\
+	r ?: tdx_cmd.c.hw_error;					\

I know it takes the same handling from __vm_sev_ioctl(). But I think the handling for hw_error is not correct, at least for TDX (I didn't check for SEV).

Quoted 7 lines

the hw_error is the additional info, to tell the SEAMCALL return code, when the IOCTL fails. KVM requires hw_error to be in the input, and KVM puts the SEAMCALL return code into hw_error when the IOCTL fails due to SEAMCALL failure. That means, when r == 0, the hw_error is always 0.

I think we need to provide hw_error along with r to the caller so that caller can print them together.

I think the value of r is not important, because the ioctl failure is already captured in errno.

Quoted 6 lines
Quoted 6 lines

We only need to fix the return values for SEV and TDX and have TEST_ASSERT_* print formatted error logs with errno and hw_error.

- r ?: {tdx, sev}_cmd.c.hw_error;
+ r ? {tdx, sev}_cmd.c.hw_error : 0;

No. It is not correct because hw_error can be 0 when r != 0.

Quoted 8 lines

I didn't look in detail, perhaps Lisa could look into these:

+ What are the possible values of r? Is it always going to be 1 on error?
+ Is r always positive or negative?
+ Is tdx_cmd.c.hw_error always positive or negative? It's probably not one of the standard Linux errors, right?

Quoted 4 lines

Perhaps squashing hw_error together with a retval conflates the two.

In the lower-level __tdx_vm_ioctl() we could pass a hw_error and have the macro set hw_error? That will allow the caller to print both.

yeah. This idea can work.

Quoted 15 lines
Quoted 15 lines
Quoted 15 lines

tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h static inline bool is_tdx_vm(struct kvm_vm *vm) · line 43

Quoted 11 lines
tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h +11 -0
+})
+
+#define tdx_vm_ioctl(vm, cmd, flags, arg)				\
+({									\
+	u64 ret = __tdx_vm_ioctl(vm, cmd, flags, arg);			\
+									\
+	if (ret) {							\
+		TEST_ASSERT(!ret,					\
+			    "%s failed, rc: 0x%llx errno: %i (%s)",	\
+			    #cmd, (unsigned long long)ret,		\
+			    errno, strerror(errno));			\

The if() looks silly. Why add it? And why change it from __TEST_ASSERT_VM_VCPU_IOCTL() in the v13?

Quoted 7 lines
Quoted 7 lines
Quoted 2 lines

Considering the suggestion of hw_error above, I think we need to introduce the TEST_ASSERT_TDX_VM_VCPU_IOCTL() which accepts additional hw_error?

The reason we could not use __TEST_ASSERT_VM_VCPU_IOCTL() directly[2] is because it formats ther return value as %i (32-bit), whereas __tdx_vm_ioctl might return a u64 hardware error code.

Since we cannot simply use hw_error to replace ret, there will be not 32bit vs 64bit issue. But ...

Quoted 3 lines
Quoted 3 lines

I agree with your suggestion to introduce a new TEST_ASSERT_TDX_VM_VCPU_IOCTL() macro to print out u64 hardware error code properly.

... if we want to print hw_error as well, we still need a new macro.

Quoted 6 lines
Quoted 2 lines

[2]: https://lore.kernel.org/all/[email protected]/

Is tdx_vm_ioctl() the only place where TEST_ASSERT_TDX_VM_VCPU_IOCTL() is going to be used though? If so, maybe we should defer introducing TEST_ASSERT_TDX_VM_VCPU_IOCTL() till later.

I think tdx_vcpu_ioctl() will use it as well?

Quoted 3 lines

I think the issue with if (ret) is just that TEST_ASSERT(!ret) already does that same check, and so we can drop the if (ret) part.

author
#14 lore raw

Re: [PATCH v14 03/22] KVM: selftests: Initialize the TDX VM

Quoted 14 lines · Xiaoyao Li
Quoted 4 lines
Quoted 4 lines

The reason we could not use __TEST_ASSERT_VM_VCPU_IOCTL() directly[2] is because it formats ther return value as %i (32-bit), whereas __tdx_vm_ioctl might return a u64 hardware error code.

Since we cannot simply use hw_error to replace ret, there will be not 32bit vs 64bit issue. But ...

Quoted 3 lines
Quoted 3 lines

I agree with your suggestion to introduce a new TEST_ASSERT_TDX_VM_VCPU_IOCTL() macro to print out u64 hardware error code properly.

... if we want to print hw_error as well, we still need a new macro.

Quoted 8 lines
Quoted 6 lines
Quoted 2 lines

[2]: https://lore.kernel.org/all/[email protected]/

Is tdx_vm_ioctl() the only place where TEST_ASSERT_TDX_VM_VCPU_IOCTL() is going to be used though? If so, maybe we should defer introducing TEST_ASSERT_TDX_VM_VCPU_IOCTL() till later.

I think tdx_vcpu_ioctl() will use it as well?

Thanks for replying.

I agree all of the other parts of your comments.

Just wanted to point out one detail: tdx_vm_ioctl() is the only place that actually needs to evaluate hw_error right now. Unlike the VM-scoped ioctls in the x86 kernel code, the functions dispatched via tdx_vcpu_unlocked_ioctl() (such as tdx_vcpu_init or tdx_vcpu_init_mem_region) do not currently return the TDX SEAMCALL error codes into the hw_error field.

Thus, I would prefer to inline TEST_ASSERT directly inside tdx_vm_ioctl() instead of introducing a new TEST_ASSERT_TDX_VM_VCPU_IOCTL() right now.

Lisa

Quoted 3 lines
Quoted 3 lines

I think the issue with if (ret) is just that TEST_ASSERT(!ret) already does that same check, and so we can drop the if (ret) part.

#15 lore raw

Re: [PATCH v14 03/22] KVM: selftests: Initialize the TDX VM

In reply to Lisa Wang
Quoted 9 lines · Lisa Wang

From: Sagi Shahar <[email protected]>

Add tdx_init_vm() to handle the mandatory VM-level initialization sequence required for Intel TDX.

For TDX, the guest's CPUID configuration must be "sealed" during KVM_TDX_INIT_VM before any vCPUs are created. This is necessary because the TDX hardware directly virtualizes CPUID and includes the configuration in the guest's initial security measurement.

"TDX hardware" should be TDX module. But I'm not sure what it is trying to say about directly virtualizes.

Also, it is not accurate to say that the CPUID configuration is included in the measurement? Is that right Peter? It's not in the report, but is it in the measurement?

Or maybe it doesn't really need to be in the log anyway.

Quoted 95 lines

The helper calculates the required CPUID values by filtering the host- supported bits (kvm_get_supported_cpuid) against the "directly configurable" bits reported by KVM_TDX_CAPABILITIES, ensuring compliance with the strict requirements of the TDH.MNG.INIT SEAMCALL.

---
 tools/testing/selftests/kvm/Makefile.kvm           |   1 +
 .../testing/selftests/kvm/include/x86/processor.h  |   2 +
 .../selftests/kvm/include/x86/tdx/tdx_util.h       |  35 ++++++
 tools/testing/selftests/kvm/lib/x86/processor.c    |  21 +++-
 tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c | 120 +++++++++++++++++++++
 5 files changed, 175 insertions(+), 4 deletions(-)
tools/testing/selftests/kvm/Makefile.kvm +1 -0
index e5769268936a..3f98d1c6488c 100644
@@ -27,6 +27,7 @@ LIBKVM_x86 += lib/x86/pmu.c
 LIBKVM_x86 += lib/x86/processor.c
 LIBKVM_x86 += lib/x86/sev.c
 LIBKVM_x86 += lib/x86/svm.c
+LIBKVM_x86 += lib/x86/tdx/tdx_util.c
 LIBKVM_x86 += lib/x86/ucall.c
 LIBKVM_x86 += lib/x86/vmx.c
 
tools/testing/selftests/kvm/include/x86/processor.h +2 -0
index 0aa6eecfcbde..76180dfaffea 100644
@@ -956,6 +956,8 @@ static inline void vcpu_xcrs_set(struct kvm_vcpu *vcpu, struct kvm_xcrs *xcrs)
 	vcpu_ioctl(vcpu, KVM_SET_XCRS, xcrs);
 }
 
+const struct kvm_cpuid_entry2 *__get_cpuid_entry(const struct kvm_cpuid2 *cpuid,
+						 u32 function, u32 index);
 const struct kvm_cpuid_entry2 *get_cpuid_entry(const struct kvm_cpuid2 *cpuid,
 					       u32 function, u32 index);
 const struct kvm_cpuid2 *kvm_get_supported_cpuid(void);
tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h +35 -0
index f647e6ca6b34..eb8602dce0bc 100644
@@ -11,4 +11,39 @@ static inline bool is_tdx_vm(struct kvm_vm *vm)
 	return vm->type == KVM_X86_TDX_VM;
 }
 
+/*
+ * TDX ioctls
+ * Use underscores to avoid collisions with struct member names.
+ */
+#define __tdx_vm_ioctl(vm, cmd, _flags, arg)				\
+({									\
+	u64 r;								\
+									\
+	union {								\
+		struct kvm_tdx_cmd c;					\
+		unsigned long raw;					\
+	} tdx_cmd = { .c = {						\
+		.id = (cmd),						\
+		.flags = (u32)(_flags),					\
+		.data = (u64)(arg),					\
+	} };								\
+									\
+	r = __vm_ioctl(vm, KVM_MEMORY_ENCRYPT_OP, &tdx_cmd.raw);	\
+	r ?: tdx_cmd.c.hw_error;					\
+})
+
+#define tdx_vm_ioctl(vm, cmd, flags, arg)				\
+({									\
+	u64 ret = __tdx_vm_ioctl(vm, cmd, flags, arg);			\
+									\
+	if (ret) {							\
+		TEST_ASSERT(!ret,					\
+			    "%s failed, rc: 0x%llx errno: %i (%s)",	\
+			    #cmd, (unsigned long long)ret,		\
+			    errno, strerror(errno));			\
+	}								\
+})
+
+void tdx_init_vm(struct kvm_vm *vm, u64 attributes);
+
 #endif /* SELFTESTS_TDX_TDX_UTIL_H */
tools/testing/selftests/kvm/lib/x86/processor.c +0 -0
index b68ad1dc7e02..7d23344854cc 100644

tools/testing/selftests/kvm/lib/x86/processor.c void kvm_arch_vm_post_create(struct kvm_vm *vm, unsigned int nr_vcpus) · line 806

Quoted 5 lines
tools/testing/selftests/kvm/lib/x86/processor.c +2 -0
@@ -802,6 +802,9 @@ void kvm_arch_vm_post_create(struct kvm_vm *vm, unsigned int nr_vcpus)
 		vm_sev_ioctl(vm, KVM_SEV_INIT2, &init);
 	}
 
+	if (is_tdx_vm(vm))
+		tdx_init_vm(vm, 0);

In this series tdx_init_vm() is only called with 0 for attributes. Do we need the arg or could we just set it to 0 internally like xfam?

Quoted 166 lines
tools/testing/selftests/kvm/lib/x86/processor.c +15 -4
+
 	r = __vm_ioctl(vm, KVM_GET_TSC_KHZ, NULL);
 	TEST_ASSERT(r > 0, "KVM_GET_TSC_KHZ did not provide a valid TSC frequency.");
 	guest_tsc_khz = r;
@@ -1328,8 +1331,8 @@ void kvm_init_vm_address_properties(struct kvm_vm *vm)
 	}
 }
 
-const struct kvm_cpuid_entry2 *get_cpuid_entry(const struct kvm_cpuid2 *cpuid,
-					       u32 function, u32 index)
+const struct kvm_cpuid_entry2 *__get_cpuid_entry(const struct kvm_cpuid2 *cpuid,
+						 u32 function, u32 index)
 {
 	int i;
 
@@ -1339,11 +1342,21 @@ const struct kvm_cpuid_entry2 *get_cpuid_entry(const struct kvm_cpuid2 *cpuid,
 			return &cpuid->entries[i];
 	}
 
-	TEST_FAIL("CPUID function 0x%x index 0x%x not found ", function, index);
-
 	return NULL;
 }
 
+const struct kvm_cpuid_entry2 *get_cpuid_entry(const struct kvm_cpuid2 *cpuid,
+					       u32 function, u32 index)
+{
+	const struct kvm_cpuid_entry2 *entry;
+
+	entry = __get_cpuid_entry(cpuid, function, index);
+	if (!entry)
+		TEST_FAIL("CPUID function 0x%x index 0x%x not found ", function, index);
+
+	return entry;
+}
+
 #define X86_HYPERCALL(inputs...)					\
 ({									\
 	u64 r;							\
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c new file +120 -0
new file mode 100644
index 000000000000..e1ffb67a106c
@@ -0,0 +1,120 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include "processor.h"
+#include "tdx/tdx_util.h"
+
+static struct kvm_tdx_capabilities *tdx_read_capabilities(struct kvm_vm *vm)
+{
+	static struct kvm_tdx_capabilities *tdx_cap;
+	int nr_cpuid_configs = 4;
+	int rc = -1;
+	int i;
+
+	if (tdx_cap)
+		return tdx_cap;
+
+	do {
+		nr_cpuid_configs *= 2;
+
+		tdx_cap = realloc(tdx_cap, sizeof(*tdx_cap) +
+					   (sizeof(struct kvm_cpuid_entry2) * nr_cpuid_configs));
+		TEST_ASSERT(tdx_cap,
+			    "Could not allocate memory for tdx capability nr_cpuid_configs %d\n",
+			    nr_cpuid_configs);
+
+		tdx_cap->cpuid.nent = nr_cpuid_configs;
+		rc = __tdx_vm_ioctl(vm, KVM_TDX_CAPABILITIES, 0, tdx_cap);
+	} while (rc < 0 && errno == E2BIG);
+
+	TEST_ASSERT(rc == 0, "KVM_TDX_CAPABILITIES failed: %d %d",
+		    rc, errno);
+
+	pr_debug("tdx_cap: supported_attrs: 0x%016llx\n"
+		 "tdx_cap: supported_xfam 0x%016llx\n",
+		 tdx_cap->supported_attrs, tdx_cap->supported_xfam);
+
+	for (i = 0; i < tdx_cap->cpuid.nent; i++) {
+		const struct kvm_cpuid_entry2 *config = &tdx_cap->cpuid.entries[i];
+
+		pr_debug("cpuid config[%d]: leaf 0x%x sub_leaf 0x%x eax 0x%08x ebx 0x%08x ecx 0x%08x edx 0x%08x\n",
+			 i, config->function, config->index,
+			 config->eax, config->ebx, config->ecx, config->edx);
+	}
+
+	return tdx_cap;
+}
+
+/*
+ * Filter CPUID based on TDX supported capabilities
+ *
+ * Input Args:
+ *   vm - Virtual Machine
+ *   cpuid_data - CPUID fields to filter
+ *
+ * Output Args: None
+ *
+ * Return: None
+ *
+ * For each CPUID leaf, filter out unsupported bits based on the capabilities
+ * reported by the TDX module
+ */
+static void tdx_filter_cpuid(struct kvm_vm *vm,
+			     struct kvm_cpuid2 *cpuid_data)
+{
+	struct kvm_tdx_capabilities *tdx_cap;
+	const struct kvm_cpuid_entry2 *config;
+	struct kvm_cpuid_entry2 *e;
+	int i;
+
+	tdx_cap = tdx_read_capabilities(vm);
+
+	i = 0;
+	while (i < cpuid_data->nent) {
+		e = cpuid_data->entries + i;
+		config = __get_cpuid_entry(&tdx_cap->cpuid, e->function, e->index);
+
+		if (!config) {
+			int left = cpuid_data->nent - i - 1;
+
+			if (left > 0)
+				memmove(cpuid_data->entries + i,
+					cpuid_data->entries + i + 1,
+					sizeof(*cpuid_data->entries) * left);
+			cpuid_data->nent--;
+			continue;
+		}
+
+		e->eax &= config->eax;
+		e->ebx &= config->ebx;
+		e->ecx &= config->ecx;
+		e->edx &= config->edx;
+
+		i++;
+	}
+}
+
+void tdx_init_vm(struct kvm_vm *vm, u64 attributes)
+{
+	struct kvm_tdx_init_vm *init_vm;
+	const struct kvm_cpuid2 *tmp;
+	struct kvm_cpuid2 *cpuid;
+
+	tmp = kvm_get_supported_cpuid();
+
+	cpuid = allocate_kvm_cpuid2(tmp->nent);
+	memcpy(cpuid, tmp, kvm_cpuid2_size(tmp->nent));
+	tdx_filter_cpuid(vm, cpuid);
+
+	init_vm = calloc(1, sizeof(*init_vm) +
+			 sizeof(init_vm->cpuid.entries[0]) * cpuid->nent);
+	TEST_ASSERT(init_vm, "init_vm allocation failed");
+
+	memcpy(&init_vm->cpuid, cpuid, kvm_cpuid2_size(cpuid->nent));
+	free(cpuid);
+
+	init_vm->attributes = attributes;
+
+	tdx_vm_ioctl(vm, KVM_TDX_INIT_VM, 0, init_vm);
+
+	free(init_vm);
+}
#16 lore raw

Re: [PATCH v14 03/22] KVM: selftests: Initialize the TDX VM

Quoted 11 lines · Rick P
Quoted 9 lines · Lisa Wang

From: Sagi Shahar <[email protected]>

Add tdx_init_vm() to handle the mandatory VM-level initialization sequence required for Intel TDX.

For TDX, the guest's CPUID configuration must be "sealed" during KVM_TDX_INIT_VM before any vCPUs are created. This is necessary because the TDX hardware directly virtualizes CPUID and includes the configuration in the guest's initial security measurement.

Quoted 6 lines

"TDX hardware" should be TDX module. But I'm not sure what it is trying to say about directly virtualizes.

Also, it is not accurate to say that the CPUID configuration is included in the measurement? Is that right Peter? It's not in the report, but is it in the measurement?

CPUID is not in the report or the TDX module's measurement. TD attributes and XFAM are in both the report and initial measurement, and some of the bits in there are related to CPUID (e.g. ATTRIBUTES.PKS or XFAM.MAXPA_VIRT). Probably better to make a distinction between these things.

Quoted 4 lines

Or maybe it doesn't really need to be in the log anyway.

Quoted 1 line
#17 lore raw

Re: [PATCH v14 03/22] KVM: selftests: Initialize the TDX VM

In reply to Lisa Wang
Quoted 15 lines · Lisa Wang

From: Sagi Shahar <[email protected]>

Add tdx_init_vm() to handle the mandatory VM-level initialization sequence required for Intel TDX.

For TDX, the guest's CPUID configuration must be "sealed" during KVM_TDX_INIT_VM before any vCPUs are created. This is necessary because the TDX hardware directly virtualizes CPUID and includes the configuration in the guest's initial security measurement.

The helper calculates the required CPUID values by filtering the host- supported bits (kvm_get_supported_cpuid) against the "directly configurable" bits reported by KVM_TDX_CAPABILITIES, ensuring compliance with the strict requirements of the TDH.MNG.INIT SEAMCALL.

[ ... ]

Quoted 10 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c new file +10 -0
+
+/*
+ * Filter CPUID based on TDX supported capabilities
+ *
+ * Input Args:
+ *   vm - Virtual Machine
+ *   cpuid_data - CPUID fields to filter
+ *
+ * Output Args: None
+ *

tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c line 65

Quoted 10 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c new file +10 -0
+ * Return: None
+ *
+ * For each CPUID leaf, filter out unsupported bits based on the capabilities
+ * reported by the TDX module
+ */
+static void tdx_filter_cpuid(struct kvm_vm *vm,
+			     struct kvm_cpuid2 *cpuid_data)
+{
+	struct kvm_tdx_capabilities *tdx_cap;
+	const struct kvm_cpuid_entry2 *config;

Nit: reverse fir tree order i.e. declaring "config" first?

Quoted 3 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c new file +3 -0
+	struct kvm_cpuid_entry2 *e;
+	int i;
+
author
#18 lore raw

PATCH v14 4/22 KVM: selftests: TDX: Use KVM_TDX_CAPABILITIES to validate TDs' attribute configuration

In reply to Lisa Wang

From: Isaku Yamahata <[email protected]>

Make sure that all the attributes enabled by the test are reported as supported by both the TDX module and KVM. KVM filters out the attributes not supported by itself.

This also exercises the KVM_TDX_CAPABILITIES ioctl.

---
 tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c | 12 ++++++++++++
 1 file changed, 12 insertions(+)
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +12 -0
index e1ffb67a106c..c01a26567b73 100644
@@ -93,6 +93,16 @@ static void tdx_filter_cpuid(struct kvm_vm *vm,
 	}
 }
 
+static void tdx_check_attributes(struct kvm_vm *vm, u64 attributes)
+{
+	struct kvm_tdx_capabilities *tdx_cap;
+
+	tdx_cap = tdx_read_capabilities(vm);
+
+	/* Make sure all the attributes are reported as supported */
+	TEST_ASSERT_EQ(attributes & tdx_cap->supported_attrs, attributes);
+}
+
 void tdx_init_vm(struct kvm_vm *vm, u64 attributes)
 {
 	struct kvm_tdx_init_vm *init_vm;
@@ -112,6 +122,8 @@ void tdx_init_vm(struct kvm_vm *vm, u64 attributes)
 	memcpy(&init_vm->cpuid, cpuid, kvm_cpuid2_size(cpuid->nent));
 	free(cpuid);
 
+	tdx_check_attributes(vm, attributes);
+
 	init_vm->attributes = attributes;
 
 	tdx_vm_ioctl(vm, KVM_TDX_INIT_VM, 0, init_vm);
Signature · 1 line
2.55.0.229.g6434b31f56-goog
#19 lore raw

Re: [PATCH v14 04/22] KVM: selftests: TDX: Use KVM_TDX_CAPABILITIES to validate TDs' attribute configuration

tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c static void tdx_filter_cpuid(struct kvm_vm *vm, · line 103

Quoted 8 lines · Lisa Wang
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +8 -0
+static void tdx_check_attributes(struct kvm_vm *vm, u64 attributes)
+{
+	struct kvm_tdx_capabilities *tdx_cap;
+
+	tdx_cap = tdx_read_capabilities(vm);
+
+	/* Make sure all the attributes are reported as supported */
+	TEST_ASSERT_EQ(attributes & tdx_cap->supported_attrs, attributes);

I think this patch actually does nothing because attributes is passed in as 0? So 0 & supported == 0. It does exercise KVM_TDX_CAPABILITIES as claimed, but so does tdx_filter_cpuid(). So for a minimal support, we could probably drop this patch.

Quoted 14 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +2 -0
+}
+

 void tdx_init_vm(struct kvm_vm *vm, u64 attributes) {
  struct kvm_tdx_init_vm *init_vm;

tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +0 -0
@@ -112,6 +122,8 @@ void tdx_init_vm(struct kvm_vm *vm, u64 attributes)
  	memcpy(&init_vm->cpuid, cpuid, kvm_cpuid2_size(cpuid->nent));
  	free(cpuid);

tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +2 -0
+	tdx_check_attributes(vm, attributes);
+

  init_vm->attributes = attributes;

  tdx_vm_ioctl(vm, KVM_TDX_INIT_VM, 0, init_vm);

#20 lore raw

Re: [PATCH v14 04/22] KVM: selftests: TDX: Use KVM_TDX_CAPABILITIES to validate TDs' attribute configuration

Quoted 15 lines · Rick P

tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c static void tdx_filter_cpuid(struct kvm_vm *vm, · line 103

Quoted 8 lines · Lisa Wang
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +8 -0
+static void tdx_check_attributes(struct kvm_vm *vm, u64 attributes)
+{
+	struct kvm_tdx_capabilities *tdx_cap;
+
+	tdx_cap = tdx_read_capabilities(vm);
+
+	/* Make sure all the attributes are reported as supported */
+	TEST_ASSERT_EQ(attributes & tdx_cap->supported_attrs, attributes);

I think this patch actually does nothing because attributes is passed in as 0? So 0 & supported == 0. It does exercise KVM_TDX_CAPABILITIES as claimed, but so does tdx_filter_cpuid(). So for a minimal support, we could probably drop this patch.

For more context, Isaku suggested this in [1], but after all the changes tdx_read_capabilities() is already used in the tdx_init_vm() so I think we're good to drop this patch.

[1] https://lore.kernel.org/all/[email protected]/

Quoted 14 lines
Quoted 14 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +2 -0
+}
+

 void tdx_init_vm(struct kvm_vm *vm, u64 attributes) {
  struct kvm_tdx_init_vm *init_vm;

tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +2 -0
@@ -112,6 +122,8 @@ void tdx_init_vm(struct kvm_vm *vm, u64 attributes)
  	memcpy(&init_vm->cpuid, cpuid, kvm_cpuid2_size(cpuid->nent));
  	free(cpuid);
 
+	tdx_check_attributes(vm, attributes);
+
  	init_vm->attributes = attributes;
 
  	tdx_vm_ioctl(vm, KVM_TDX_INIT_VM, 0, init_vm);
author
#21 lore raw

PATCH v14 5/22 KVM: selftests: Expose segment definitions to assembly files

In reply to Lisa Wang

From: Sagi Shahar <[email protected]>

Move kernel segment definitions to a separate file which can be included from assembly files.

---
 tools/testing/selftests/kvm/include/x86/processor_asm.h | 12 ++++++++++++
 tools/testing/selftests/kvm/lib/x86/processor.c         |  5 +----
 2 files changed, 13 insertions(+), 4 deletions(-)
tools/testing/selftests/kvm/include/x86/processor_asm.h new file +12 -0
new file mode 100644
index 000000000000..713b6bc0aeb7
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Used for storing defines used by both c and assembly code.
+ */
+#ifndef SELFTEST_KVM_PROCESSOR_ASM_H
+#define SELFTEST_KVM_PROCESSOR_ASM_H
+
+#define KERNEL_CS	0x8
+#define KERNEL_DS	0x10
+#define KERNEL_TSS	0x18
+
+#endif  /* SELFTEST_KVM_PROCESSOR_ASM_H */
tools/testing/selftests/kvm/lib/x86/processor.c +1 -4
index 7d23344854cc..97acc5aaf7b7 100644
@@ -8,6 +8,7 @@
 #include "kvm_util.h"
 #include "pmu.h"
 #include "processor.h"
+#include "processor_asm.h"
 #include "smm.h"
 #include "svm_util.h"
 #include "sev.h"
@@ -18,10 +19,6 @@
 #define NUM_INTERRUPTS 256
 #endif
 
-#define KERNEL_CS	0x8
-#define KERNEL_DS	0x10
-#define KERNEL_TSS	0x18
-
 gva_t exception_handlers;
 bool host_cpu_is_amd;
 bool host_cpu_is_intel;
Signature · 1 line
2.55.0.229.g6434b31f56-goog
#22 lore raw

Re: [PATCH v14 05/22] KVM: selftests: Expose segment definitions to assembly files

Quoted 4 lines · Lisa Wang

From: Sagi Shahar <[email protected]>

Move kernel segment definitions to a separate file which can be included from assembly files.

Log nit, it's not just a move, it's extracting them to a new file.

Quoted 6 lines
Reviewed-by on v14 5/22
#23 lore raw

Re: [PATCH v14 05/22] KVM: selftests: Expose segment definitions to assembly files

In reply to Lisa Wang
Quoted 58 lines · Lisa Wang

From: Sagi Shahar <[email protected]>

Move kernel segment definitions to a separate file which can be included from assembly files.

---
 tools/testing/selftests/kvm/include/x86/processor_asm.h | 12 ++++++++++++
 tools/testing/selftests/kvm/lib/x86/processor.c         |  5 +----
 2 files changed, 13 insertions(+), 4 deletions(-)
tools/testing/selftests/kvm/include/x86/processor_asm.h new file +12 -0
new file mode 100644
index 000000000000..713b6bc0aeb7
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Used for storing defines used by both c and assembly code.
+ */
+#ifndef SELFTEST_KVM_PROCESSOR_ASM_H
+#define SELFTEST_KVM_PROCESSOR_ASM_H
+
+#define KERNEL_CS	0x8
+#define KERNEL_DS	0x10
+#define KERNEL_TSS	0x18
+
+#endif  /* SELFTEST_KVM_PROCESSOR_ASM_H */
tools/testing/selftests/kvm/lib/x86/processor.c +1 -5
index 7d23344854cc..97acc5aaf7b7 100644
@@ -8,6 +8,7 @@
 #include "kvm_util.h"
 #include "pmu.h"
 #include "processor.h"
+#include "processor_asm.h"
 #include "smm.h"
 #include "svm_util.h"
 #include "sev.h"
@@ -18,10 +19,6 @@
 #define NUM_INTERRUPTS 256
 #endif
 
-#define KERNEL_CS	0x8
-#define KERNEL_DS	0x10
-#define KERNEL_TSS	0x18
-
 gva_t exception_handlers;
 bool host_cpu_is_amd;
 bool host_cpu_is_intel;
 
--
Quoted 1 line

2.55.0.229.g6434b31f56-goog

author
#24 lore raw

PATCH v14 6/22 tools: include: Add kbuild.h for assembly structure offsets

In reply to Lisa Wang

From: Sagi Shahar <[email protected]>

Add the Kbuild macros needed to enable the filechk_offsets mechanism to generate C header files containing structure member offset information.

Tools depending on assembly code that operate on structures have to hardcode the offsets of structure members. The Kbuild infrastructure can instead generate C header files with these offsets automatically, allowing them to be included in assembly code as symbolic constants.

For example, the TDX guest boot code requires access to parameters passed in the C structure (struct td_boot_parameters). This header provides the macros needed to extract these offsets from C code and expose them to assembly, ensuring the two remain synchronized.

---
 tools/include/linux/kbuild.h | 11 +++++++++++
 1 file changed, 11 insertions(+)
tools/include/linux/kbuild.h new file +11 -0
new file mode 100644
index 000000000000..957fd55cd159
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __TOOLS_LINUX_KBUILD_H
+#define __TOOLS_LINUX_KBUILD_H
+
+#define DEFINE(sym, val) \
+	asm volatile("\n.ascii \"->" #sym " %0 " #val "\"" : : "i" (val))
+
+#define OFFSET(sym, str, mem) \
+	DEFINE(sym, __builtin_offsetof(struct str, mem))
+
+#endif /* __TOOLS_LINUX_KBUILD_H */
Signature · 1 line
2.55.0.229.g6434b31f56-goog
Reviewed-by on v14 6/22
#25 lore raw

Re: [PATCH v14 06/22] tools: include: Add kbuild.h for assembly structure offsets

Quoted 42 lines · Lisa Wang

From: Sagi Shahar <[email protected]>

Add the Kbuild macros needed to enable the filechk_offsets mechanism to generate C header files containing structure member offset information.

Tools depending on assembly code that operate on structures have to hardcode the offsets of structure members. The Kbuild infrastructure can instead generate C header files with these offsets automatically, allowing them to be included in assembly code as symbolic constants.

For example, the TDX guest boot code requires access to parameters passed in the C structure (struct td_boot_parameters). This header provides the macros needed to extract these offsets from C code and expose them to assembly, ensuring the two remain synchronized.

---
 tools/include/linux/kbuild.h | 11 +++++++++++
 1 file changed, 11 insertions(+)
tools/include/linux/kbuild.h new file +11 -1
new file mode 100644
index 000000000000..957fd55cd159
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __TOOLS_LINUX_KBUILD_H
+#define __TOOLS_LINUX_KBUILD_H
+
+#define DEFINE(sym, val) \
+	asm volatile("\n.ascii \"->" #sym " %0 " #val "\"" : : "i" (val))
+
+#define OFFSET(sym, str, mem) \
+	DEFINE(sym, __builtin_offsetof(struct str, mem))
+
+#endif /* __TOOLS_LINUX_KBUILD_H */
 
--
Quoted 1 line

2.55.0.229.g6434b31f56-goog

author
#26 lore raw

PATCH v14 7/22 KVM: selftests: Introduce structures for TDX guest boot parameters

In reply to Lisa Wang

From: Sagi Shahar <[email protected]>

Introduce td_boot_parameters and td_per_vcpu_parameters, and export their offsets to assembly via the kbuild infrastructure.

TDX guest registers are private and must be initialized by guest-side assembly. These structures allow the assembly code to retrieve boot parameters and index into per-vCPU data based on the vCPU ID, while keeping host and guest definitions synchronized.

Use kbuild.h to expose the offsets into the structs from c code to assembly code.

---
 tools/testing/selftests/kvm/.gitignore             |  3 +-
 tools/testing/selftests/kvm/Makefile.kvm           | 29 +++++++++-
 .../selftests/kvm/include/x86/tdx/td_boot.h        | 66 ++++++++++++++++++++++
 .../selftests/kvm/lib/x86/tdx/td_boot_offsets.c    | 21 +++++++
 4 files changed, 116 insertions(+), 3 deletions(-)
tools/testing/selftests/kvm/.gitignore +2 -1
index 1d41a046a7bf..eef6055242b2 100644
@@ -9,4 +9,5 @@
 !config
 !settings
 !Makefile
-!Makefile.kvm
\ No newline at end of file
+!Makefile.kvm
+include/x86/**/*_offsets.h
\ No newline at end of file
tools/testing/selftests/kvm/Makefile.kvm +27 -2
index 3f98d1c6488c..397afebbb34b 100644
@@ -19,6 +19,8 @@ LIBKVM += lib/userfaultfd_util.c
 
 LIBKVM_STRING += lib/string_override.c
 
+LIBKVM_ASM_DEFS += lib/x86/tdx/td_boot_offsets.c
+
 LIBKVM_x86 += lib/x86/apic.c
 LIBKVM_x86 += lib/x86/handlers.S
 LIBKVM_x86 += lib/x86/hyperv.c
@@ -261,6 +263,10 @@ OVERRIDE_TARGETS = 1
 include ../lib.mk
 include ../cgroup/lib/libcgroup.mk
 
+# Enable Kbuild tools.
+include $(top_srcdir)/scripts/Kbuild.include
+include $(top_srcdir)/scripts/Makefile.lib
+
 INSTALL_HDR_PATH = $(top_srcdir)/usr
 LINUX_HDR_PATH = $(INSTALL_HDR_PATH)/include/
 LINUX_TOOL_INCLUDE = $(top_srcdir)/tools/include
@@ -273,15 +279,24 @@ CFLAGS += -Wall -Wstrict-prototypes -Wuninitialized -O2 -g -std=gnu99 \
 	-fno-stack-protector -fno-PIE -fno-strict-aliasing \
 	-I$(LINUX_TOOL_INCLUDE) -I$(LINUX_TOOL_ARCH_INCLUDE) \
 	-I$(LINUX_HDR_PATH) -Iinclude -I$(<D) -Iinclude/$(ARCH) \
-	-I ../rseq -I.. $(EXTRA_CFLAGS) $(KHDR_INCLUDES)
+	-I ../rseq -I.. -I$(OUTPUT)/include/$(ARCH) $(EXTRA_CFLAGS) $(KHDR_INCLUDES)
 ifeq ($(ARCH),s390)
 	CFLAGS += -march=z10
 endif
+
 ifeq ($(ARCH),x86)
+
 ifeq ($(shell echo "void foo(void) { }" | $(CC) -march=x86-64-v2 -x c - -c -o /dev/null 2>/dev/null; echo "$$?"),0)
 	CFLAGS += -march=x86-64-v2
 endif
+
+KVM_GEN_HDRS := $(patsubst lib/x86/%.c, $(OUTPUT)/include/x86/%.h, $(filter lib/x86/%, $(LIBKVM_ASM_DEFS)))
+$(shell mkdir -p $(sort $(dir $(KVM_GEN_HDRS))))
+$(KVM_GEN_HDRS): GUARD = $(shell echo $(*F) | tr a-z A-Z | tr '.' '_')
+$(KVM_GEN_HDRS): $(OUTPUT)/include/x86/%.h: $(OUTPUT)/lib/x86/%.s FORCE
+	$(call filechk,offsets,__$(GUARD)_H__)
 endif
+
 ifeq ($(ARCH),arm64)
 tools_dir := $(top_srcdir)/tools
 arm64_tools_dir := $(tools_dir)/arch/arm64/tools/
@@ -314,6 +329,7 @@ LIBKVM_S := $(filter %.S,$(LIBKVM))
 LIBKVM_C_OBJ := $(patsubst %.c, $(OUTPUT)/%.o, $(LIBKVM_C))
 LIBKVM_S_OBJ := $(patsubst %.S, $(OUTPUT)/%.o, $(LIBKVM_S))
 LIBKVM_STRING_OBJ := $(patsubst %.c, $(OUTPUT)/%.o, $(LIBKVM_STRING))
+LIBKVM_ASM_DEFS_OBJ += $(patsubst %.c, $(OUTPUT)/%.s, $(LIBKVM_ASM_DEFS))
 LIBKVM_OBJS = $(LIBKVM_C_OBJ) $(LIBKVM_S_OBJ) $(LIBKVM_STRING_OBJ) $(LIBCGROUP_O)
 SPLIT_TEST_GEN_PROGS := $(patsubst %, $(OUTPUT)/%, $(SPLIT_TESTS))
 SPLIT_TEST_GEN_OBJ := $(patsubst %, $(OUTPUT)/$(ARCH)/%.o, $(SPLIT_TESTS))
@@ -339,7 +355,9 @@ $(SPLIT_TEST_GEN_OBJ): $(OUTPUT)/$(ARCH)/%.o: $(ARCH)/%.c
 	$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@
 
 EXTRA_CLEAN += $(GEN_HDRS) \
+	       $(KVM_GEN_HDRS) \
 	       $(LIBKVM_OBJS) \
+	       $(LIBKVM_ASM_DEFS_OBJ) \
 	       $(SPLIT_TEST_GEN_OBJ) \
 	       $(TEST_DEP_FILES) \
 	       $(TEST_GEN_OBJ) \
@@ -351,6 +369,9 @@ $(LIBKVM_C_OBJ): $(OUTPUT)/%.o: %.c $(GEN_HDRS)
 $(LIBKVM_S_OBJ): $(OUTPUT)/%.o: %.S $(GEN_HDRS)
 	$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@
 
+$(LIBKVM_ASM_DEFS_OBJ): $(OUTPUT)/%.s: %.c FORCE
+	$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -S $< -o $@
+
 # Compile the string overrides as freestanding to prevent the compiler from
 # generating self-referential code, e.g. without "freestanding" the compiler may
 # "optimize" memcmp() by invoking memcmp(), thus causing infinite recursion.
@@ -359,11 +380,15 @@ $(LIBKVM_STRING_OBJ): $(OUTPUT)/%.o: %.c
 
 $(shell mkdir -p $(sort $(dir $(TEST_GEN_PROGS))))
 $(SPLIT_TEST_GEN_OBJ): $(GEN_HDRS)
+$(LIBKVM_OBJS): $(KVM_GEN_HDRS)
 $(TEST_GEN_PROGS): $(LIBKVM_OBJS)
 $(TEST_GEN_PROGS_EXTENDED): $(LIBKVM_OBJS)
 $(TEST_GEN_OBJ): $(GEN_HDRS)
 
-cscope: include_paths = $(LINUX_TOOL_INCLUDE) $(LINUX_HDR_PATH) include lib ..
+FORCE:
+
+cscope: include_paths = $(LINUX_TOOL_INCLUDE) $(LINUX_HDR_PATH) include lib .. \
+			$(wildcard $(sort $(dir $(KVM_GEN_HDRS))))
 cscope:
 	$(RM) cscope.*
 	(find $(include_paths) -name '*.h' \
tools/testing/selftests/kvm/include/x86/tdx/td_boot.h new file +66 -0

66 lines not on this page · raw message

tools/testing/selftests/kvm/lib/x86/tdx/td_boot_offsets.c new file +21 -0
new file mode 100644
index 000000000000..7f76a3585b99
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: GPL-2.0
+#define COMPILE_OFFSETS
+
+#include <linux/kbuild.h>
+
+#include "tdx/td_boot.h"
+
+static void __attribute__((used)) common(void)
+{
+	OFFSET(TD_BOOT_PARAMETERS_CR0, td_boot_parameters, cr0);
+	OFFSET(TD_BOOT_PARAMETERS_CR3, td_boot_parameters, cr3);
+	OFFSET(TD_BOOT_PARAMETERS_CR4, td_boot_parameters, cr4);
+	OFFSET(TD_BOOT_PARAMETERS_GDT, td_boot_parameters, gdtr);
+	OFFSET(TD_BOOT_PARAMETERS_IDT, td_boot_parameters, idtr);
+	OFFSET(TD_BOOT_PARAMETERS_PER_VCPU, td_boot_parameters, per_vcpu);
+	OFFSET(TD_PER_VCPU_PARAMETERS_ESP_GVA, td_per_vcpu_parameters, esp_gva);
+	OFFSET(TD_PER_VCPU_PARAMETERS_GUEST_CODE, td_per_vcpu_parameters,
+	       guest_code);
+	DEFINE(SIZEOF_TD_PER_VCPU_PARAMETERS,
+	       sizeof(struct td_per_vcpu_parameters));
+}
Signature · 1 line
2.55.0.229.g6434b31f56-goog
author
#27 lore raw

PATCH v14 8/22 KVM: selftests: Add TDX boot code

In reply to Lisa Wang

From: Erdem Aktas <[email protected]>

Add code to boot a TDX test VM. Since TDX registers are inaccessible to KVM, the boot code loads the relevant values from memory into the registers before jumping to the guest code.

---
 tools/testing/selftests/kvm/Makefile.kvm           |  1 +
 .../selftests/kvm/include/x86/tdx/td_boot.h        | 22 ++++++--
 tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S  | 61 ++++++++++++++++++++++
 3 files changed, 81 insertions(+), 3 deletions(-)
tools/testing/selftests/kvm/Makefile.kvm +1 -0
index 397afebbb34b..645d9aac61db 100644
@@ -32,6 +32,7 @@ LIBKVM_x86 += lib/x86/svm.c
 LIBKVM_x86 += lib/x86/tdx/tdx_util.c
 LIBKVM_x86 += lib/x86/ucall.c
 LIBKVM_x86 += lib/x86/vmx.c
+LIBKVM_x86 += lib/x86/tdx/td_boot.S
 
 LIBKVM_arm64 += lib/arm64/gic.c
 LIBKVM_arm64 += lib/arm64/gic_v3.c
tools/testing/selftests/kvm/include/x86/tdx/td_boot.h +19 -3
index bf2282931d49..89cf6c3485be 100644
@@ -2,9 +2,6 @@
 #ifndef SELFTEST_TDX_TD_BOOT_H
 #define SELFTEST_TDX_TD_BOOT_H
 
-#include <linux/compiler.h>
-#include <linux/types.h>
-
 /*
  * Layout for boot section (not to scale)
  *
@@ -24,7 +21,19 @@
  * |                           |
  * |                           |
  * |___________________________|____ 0x0_ffff_0000: TD_BOOT_PARAMETERS_GPA
+ *
+ * TD_BOOT_PARAMETERS_GPA is arbitrarily chosen to
+ *
+ * + be within the 4GB address space
+ * + provide enough contiguous memory for the struct td_boot_parameters such
+ *   that there is one struct td_per_vcpu_parameters for KVM_MAX_VCPUS
  */
+#define TD_BOOT_PARAMETERS_GPA 0xffff0000
+
+#if !defined(__ASSEMBLY__) && !defined(__ASSEMBLER__)
+
+#include <linux/compiler.h>
+#include <linux/types.h>
 
 /*
  * The exact memory layout for LGDT or LIDT instructions.
@@ -63,4 +72,11 @@ struct td_boot_parameters {
 	struct td_per_vcpu_parameters per_vcpu[];
 };
 
+void td_boot(void);
+void td_boot_code_end(void);
+
+#define TD_BOOT_CODE_SIZE (td_boot_code_end - td_boot)
+
+#endif /* !defined(__ASSEMBLY__) && !defined(__ASSEMBLER__) */
+
 #endif /* SELFTEST_TDX_TD_BOOT_H */
tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S new file +61 -0

61 lines not on this page · raw message

Signature · 1 line
2.55.0.229.g6434b31f56-goog
Reviewed-by on v14 8/22
#28 lore raw

Re: [PATCH v14 08/22] KVM: selftests: Add TDX boot code

Quoted 6 lines · Lisa Wang

From: Erdem Aktas <[email protected]>

Add code to boot a TDX test VM. Since TDX registers are inaccessible to KVM, the boot code loads the relevant values from memory into the registers before jumping to the guest code.

Quoted 7 lines
Quoted 132 lines
---
  tools/testing/selftests/kvm/Makefile.kvm           |  1 +
  .../selftests/kvm/include/x86/tdx/td_boot.h        | 22 ++++++--
  tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S  | 61 ++++++++++++++++++++++
  3 files changed, 81 insertions(+), 3 deletions(-)
tools/testing/selftests/kvm/Makefile.kvm +1 -0
index 397afebbb34b..645d9aac61db 100644
@@ -32,6 +32,7 @@ LIBKVM_x86 += lib/x86/svm.c
  LIBKVM_x86 += lib/x86/tdx/tdx_util.c
  LIBKVM_x86 += lib/x86/ucall.c
  LIBKVM_x86 += lib/x86/vmx.c
+LIBKVM_x86 += lib/x86/tdx/td_boot.S
 
  LIBKVM_arm64 += lib/arm64/gic.c
  LIBKVM_arm64 += lib/arm64/gic_v3.c
tools/testing/selftests/kvm/include/x86/tdx/td_boot.h +19 -3
index bf2282931d49..89cf6c3485be 100644
@@ -2,9 +2,6 @@
  #ifndef SELFTEST_TDX_TD_BOOT_H
  #define SELFTEST_TDX_TD_BOOT_H
 
-#include <linux/compiler.h>
-#include <linux/types.h>
-
  /*
   * Layout for boot section (not to scale)
   *
@@ -24,7 +21,19 @@
   * |                           |
   * |                           |
   * |___________________________|____ 0x0_ffff_0000: TD_BOOT_PARAMETERS_GPA
+ *
+ * TD_BOOT_PARAMETERS_GPA is arbitrarily chosen to
+ *
+ * + be within the 4GB address space
+ * + provide enough contiguous memory for the struct td_boot_parameters such
+ *   that there is one struct td_per_vcpu_parameters for KVM_MAX_VCPUS
   */
+#define TD_BOOT_PARAMETERS_GPA 0xffff0000
+
+#if !defined(__ASSEMBLY__) && !defined(__ASSEMBLER__)
+
+#include <linux/compiler.h>
+#include <linux/types.h>
 
  /*
   * The exact memory layout for LGDT or LIDT instructions.
@@ -63,4 +72,11 @@ struct td_boot_parameters {
  	struct td_per_vcpu_parameters per_vcpu[];
  };
 
+void td_boot(void);
+void td_boot_code_end(void);
+
+#define TD_BOOT_CODE_SIZE (td_boot_code_end - td_boot)
+
+#endif /* !defined(__ASSEMBLY__) && !defined(__ASSEMBLER__) */
+
  #endif /* SELFTEST_TDX_TD_BOOT_H */
tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S new file +61 -0
new file mode 100644
index 000000000000..bca9a4c3d8f8
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#include "tdx/td_boot.h"
+#include "tdx/td_boot_offsets.h"
+#include "processor_asm.h"
+
+.code32
+
+.globl td_boot
+td_boot:
+	/*
+	 * In this procedure, edi is used as a temporary register.
+	 * Paging is turned off.
+	 */
+	cli
+
+	movl $TD_BOOT_PARAMETERS_GPA, %ebx
+
+	/*
+	 * Find the address of struct td_per_vcpu_parameters for this
+	 * vCPU based on esi (TDX spec: initialized with vCPU id). Put
+	 * struct address into register for indirect addressing.
+	 */
+	movl $SIZEOF_TD_PER_VCPU_PARAMETERS, %eax
+	mul %esi
+	leal TD_BOOT_PARAMETERS_PER_VCPU(%ebx), %edi
+	addl %edi, %eax
+
+	/* Setup stack. */
+	movl TD_PER_VCPU_PARAMETERS_ESP_GVA(%eax), %esp
+
+	/* Setup GDT. */
+	leal TD_BOOT_PARAMETERS_GDT(%ebx), %edi
+	lgdt (%edi)
+
+	/* Setup IDT. */
+	leal TD_BOOT_PARAMETERS_IDT(%ebx), %edi
+	lidt (%edi)
+
+	/*
+	 * Set up control registers (There are no instructions to mov from
+	 * memory to control registers, hence use edi as a scratch register).
+	 */
+	movl TD_BOOT_PARAMETERS_CR4(%ebx), %edi
+	movl %edi, %cr4
+	movl TD_BOOT_PARAMETERS_CR3(%ebx), %edi
+	movl %edi, %cr3
+	movl TD_BOOT_PARAMETERS_CR0(%ebx), %edi
+	movl %edi, %cr0
+
+	/* Switching to 64bit mode after ljmp and then jump to guest code */
+	ljmp $(KERNEL_CS),$1f
+1:
+	jmp *TD_PER_VCPU_PARAMETERS_GUEST_CODE(%eax)
+
+/* Leave marker so size of td_boot code can be computed. */
+.globl td_boot_code_end
+td_boot_code_end:
+
+/* Disable executable stack. */
+.section .note.GNU-stack,"",%progbits
#29 lore raw

Re: [PATCH v14 08/22] KVM: selftests: Add TDX boot code

In reply to Lisa Wang
Quoted 14 lines · Lisa Wang

From: Erdem Aktas <[email protected]>

Add code to boot a TDX test VM. Since TDX registers are inaccessible to KVM, the boot code loads the relevant values from memory into the registers before jumping to the guest code.

---

[ ... ]

Quoted 18 lines
tools/testing/selftests/kvm/include/x86/tdx/td_boot.h +0 -3
index bf2282931d49..89cf6c3485be 100644
@@ -2,9 +2,6 @@
 #ifndef SELFTEST_TDX_TD_BOOT_H
 #define SELFTEST_TDX_TD_BOOT_H
 
-#include <linux/compiler.h>
-#include <linux/types.h>
-
 /*
  * Layout for boot section (not to scale)
  *
@@ -24,7 +21,19 @@
  * |                           |
  * |                           |
  * |___________________________|____ 0x0_ffff_0000: TD_BOOT_PARAMETERS_GPA

tools/testing/selftests/kvm/include/x86/tdx/td_boot.h line 33

Quoted 10 lines
tools/testing/selftests/kvm/include/x86/tdx/td_boot.h +9 -0
+ *
+ * TD_BOOT_PARAMETERS_GPA is arbitrarily chosen to
+ *
+ * + be within the 4GB address space
+ * + provide enough contiguous memory for the struct td_boot_parameters such
+ *   that there is one struct td_per_vcpu_parameters for KVM_MAX_VCPUS
  */
+#define TD_BOOT_PARAMETERS_GPA 0xffff0000
+
+#if !defined(__ASSEMBLY__) && !defined(__ASSEMBLER__)

Is the "__ASSEMBLY__" check really needed here? I don't think the toolchain uses -D__ASSEMBLY__... Also, I think __ASSEMBLY__ is slowly being removed from the tree [1][2].

[1] https://lore.kernel.org/all/[email protected]/
[2] https://lore.kernel.org/all/[email protected]/

tools/testing/selftests/kvm/include/x86/tdx/td_boot.h struct td_boot_parameters { · line 76

Quoted 12 lines
tools/testing/selftests/kvm/include/x86/tdx/td_boot.h +5 -0
+
+#include <linux/compiler.h>
+#include <linux/types.h>
 
 /*
  * The exact memory layout for LGDT or LIDT instructions.
@@ -63,4 +72,11 @@ struct td_boot_parameters {
 	struct td_per_vcpu_parameters per_vcpu[];
 };
 
+void td_boot(void);
+void td_boot_code_end(void);

This looks a bit off to me. td_boot_code_end is just a symbol and not a function at all. Maybe something like this?

  extern u8 td_boot_code_start[], td_boot_code_end[];
Quoted 14 lines
tools/testing/selftests/kvm/include/x86/tdx/td_boot.h +5 -0
+
+#define TD_BOOT_CODE_SIZE (td_boot_code_end - td_boot)
+
+#endif /* !defined(__ASSEMBLY__) && !defined(__ASSEMBLER__) */
+
 #endif /* SELFTEST_TDX_TD_BOOT_H */
tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S new file +2 -0
new file mode 100644
index 000000000000..bca9a4c3d8f8
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+

tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S line 12

Quoted 10 lines
tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S new file +10 -0
+#include "tdx/td_boot.h"
+#include "tdx/td_boot_offsets.h"
+#include "processor_asm.h"
+
+.code32
+
+.globl td_boot
+td_boot:
+	/*
+	 * In this procedure, edi is used as a temporary register.

Hmm, "mul %esi" clobbers edx as well. Not sure if that needs to be called out. Perhaps documenting the registers that need to be preserved thoughout this procedure (eax, ebx) is more useful. A comment below already mentions that edi is a scratch register.

tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S line 22

Quoted 10 lines
tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S new file +10 -0
+	 * Paging is turned off.
+	 */
+	cli
+
+	movl $TD_BOOT_PARAMETERS_GPA, %ebx
+
+	/*
+	 * Find the address of struct td_per_vcpu_parameters for this
+	 * vCPU based on esi (TDX spec: initialized with vCPU id). Put
+	 * struct address into register for indirect addressing.
                               ^^^^^^^^ maybe just say eax?
Quoted 6 lines
tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S new file +6 -0
+	 */
+	movl $SIZEOF_TD_PER_VCPU_PARAMETERS, %eax
+	mul %esi
+	leal TD_BOOT_PARAMETERS_PER_VCPU(%ebx), %edi
+	addl %edi, %eax
+
author
#30 lore raw

PATCH v14 9/22 KVM: selftests: Expose functions to get default sregs values

In reply to Lisa Wang

From: Sagi Shahar <[email protected]>

TDX can't set sregs values directly using KVM_SET_SREGS. Expose the default values of certain sregs used by TDX VMs so they can be set manually.

---
 .../testing/selftests/kvm/include/x86/processor.h  | 35 ++++++++++++++++++++++
 tools/testing/selftests/kvm/lib/x86/processor.c    | 20 ++++---------
 2 files changed, 41 insertions(+), 14 deletions(-)
tools/testing/selftests/kvm/include/x86/processor.h +35 -0
index 76180dfaffea..7e00c9414595 100644
@@ -29,6 +29,10 @@ extern u64 guest_tsc_khz;
 #define MAX_NR_CPUID_ENTRIES 100
 #endif
 
+#ifndef NUM_INTERRUPTS
+#define NUM_INTERRUPTS 256
+#endif
+
 #define NONCANONICAL 0xaaaaaaaaaaaaaaaaull
 
 /* Forced emulation prefix, used to invoke the emulator unconditionally. */
@@ -1564,4 +1568,35 @@ u64 *tdp_get_pte(struct kvm_vm *vm, u64 l2_gpa);
 
 bool sys_clocksource_is_based_on_tsc(void);
 
+static inline u16 kvm_get_default_idt_limit(void)
+{
+	return NUM_INTERRUPTS * sizeof(struct idt_entry) - 1;
+}
+
+static inline u16 kvm_get_default_gdt_limit(void)
+{
+	return getpagesize() - 1;
+}
+
+static inline u64 kvm_get_default_cr0(void)
+{
+	return X86_CR0_PE | X86_CR0_NE | X86_CR0_PG;
+}
+
+static inline u64 kvm_get_default_cr4(int pgtable_levels)
+{
+	u64 cr4 = X86_CR4_PAE | X86_CR4_OSFXSR;
+
+	if (kvm_cpu_has(X86_FEATURE_XSAVE))
+		cr4 |= X86_CR4_OSXSAVE;
+	if (pgtable_levels == 5)
+		cr4 |= X86_CR4_LA57;
+	return cr4;
+}
+
+static inline u64 kvm_get_default_efer(void)
+{
+	return EFER_LME | EFER_LMA | EFER_NX;
+}
+
 #endif /* SELFTEST_KVM_PROCESSOR_H */
tools/testing/selftests/kvm/lib/x86/processor.c +6 -14
index 97acc5aaf7b7..ad75e19bb3a2 100644
@@ -15,10 +15,6 @@
 #include "tdx/tdx_util.h"
 #include "vmx.h"
 
-#ifndef NUM_INTERRUPTS
-#define NUM_INTERRUPTS 256
-#endif
-
 gva_t exception_handlers;
 bool host_cpu_is_amd;
 bool host_cpu_is_intel;
@@ -647,17 +643,13 @@ static void vcpu_init_sregs(struct kvm_vm *vm, struct kvm_vcpu *vcpu)
 	vcpu_sregs_get(vcpu, &sregs);
 
 	sregs.idt.base = vm->arch.idt;
-	sregs.idt.limit = NUM_INTERRUPTS * sizeof(struct idt_entry) - 1;
+	sregs.idt.limit = kvm_get_default_idt_limit();
 	sregs.gdt.base = vm->arch.gdt;
-	sregs.gdt.limit = getpagesize() - 1;
-
-	sregs.cr0 = X86_CR0_PE | X86_CR0_NE | X86_CR0_PG;
-	sregs.cr4 |= X86_CR4_PAE | X86_CR4_OSFXSR;
-	if (kvm_cpu_has(X86_FEATURE_XSAVE))
-		sregs.cr4 |= X86_CR4_OSXSAVE;
-	if (vm->mmu.pgtable_levels == 5)
-		sregs.cr4 |= X86_CR4_LA57;
-	sregs.efer |= (EFER_LME | EFER_LMA | EFER_NX);
+	sregs.gdt.limit = kvm_get_default_gdt_limit();
+
+	sregs.cr0 = kvm_get_default_cr0();
+	sregs.cr4 |= kvm_get_default_cr4(vm->mmu.pgtable_levels);
+	sregs.efer |= kvm_get_default_efer();
 
 	kvm_seg_set_unusable(&sregs.ldt);
 	kvm_seg_set_kernel_code_64bit(&sregs.cs);
Signature · 1 line
2.55.0.229.g6434b31f56-goog
Reviewed-by on v14 9/22
#31 lore raw

Re: [PATCH v14 09/22] KVM: selftests: Expose functions to get default sregs values

Quoted 10 lines · Lisa Wang

From: Sagi Shahar <[email protected]>

TDX can't set sregs values directly using KVM_SET_SREGS. Expose the default values of certain sregs used by TDX VMs so they can be set manually.

Quoted 96 lines
---
  .../testing/selftests/kvm/include/x86/processor.h  | 35 ++++++++++++++++++++++
  tools/testing/selftests/kvm/lib/x86/processor.c    | 20 ++++---------
  2 files changed, 41 insertions(+), 14 deletions(-)
tools/testing/selftests/kvm/include/x86/processor.h +35 -0
index 76180dfaffea..7e00c9414595 100644
@@ -29,6 +29,10 @@ extern u64 guest_tsc_khz;
  #define MAX_NR_CPUID_ENTRIES 100
  #endif
 
+#ifndef NUM_INTERRUPTS
+#define NUM_INTERRUPTS 256
+#endif
+
  #define NONCANONICAL 0xaaaaaaaaaaaaaaaaull
 
  /* Forced emulation prefix, used to invoke the emulator unconditionally. */
@@ -1564,4 +1568,35 @@ u64 *tdp_get_pte(struct kvm_vm *vm, u64 l2_gpa);
 
  bool sys_clocksource_is_based_on_tsc(void);
 
+static inline u16 kvm_get_default_idt_limit(void)
+{
+	return NUM_INTERRUPTS * sizeof(struct idt_entry) - 1;
+}
+
+static inline u16 kvm_get_default_gdt_limit(void)
+{
+	return getpagesize() - 1;
+}
+
+static inline u64 kvm_get_default_cr0(void)
+{
+	return X86_CR0_PE | X86_CR0_NE | X86_CR0_PG;
+}
+
+static inline u64 kvm_get_default_cr4(int pgtable_levels)
+{
+	u64 cr4 = X86_CR4_PAE | X86_CR4_OSFXSR;
+
+	if (kvm_cpu_has(X86_FEATURE_XSAVE))
+		cr4 |= X86_CR4_OSXSAVE;
+	if (pgtable_levels == 5)
+		cr4 |= X86_CR4_LA57;
+	return cr4;
+}
+
+static inline u64 kvm_get_default_efer(void)
+{
+	return EFER_LME | EFER_LMA | EFER_NX;
+}
+
  #endif /* SELFTEST_KVM_PROCESSOR_H */
tools/testing/selftests/kvm/lib/x86/processor.c +6 -14
index 97acc5aaf7b7..ad75e19bb3a2 100644
@@ -15,10 +15,6 @@
  #include "tdx/tdx_util.h"
  #include "vmx.h"
 
-#ifndef NUM_INTERRUPTS
-#define NUM_INTERRUPTS 256
-#endif
-
  gva_t exception_handlers;
  bool host_cpu_is_amd;
  bool host_cpu_is_intel;
@@ -647,17 +643,13 @@ static void vcpu_init_sregs(struct kvm_vm *vm, struct kvm_vcpu *vcpu)
  	vcpu_sregs_get(vcpu, &sregs);
 
  	sregs.idt.base = vm->arch.idt;
-	sregs.idt.limit = NUM_INTERRUPTS * sizeof(struct idt_entry) - 1;
+	sregs.idt.limit = kvm_get_default_idt_limit();
  	sregs.gdt.base = vm->arch.gdt;
-	sregs.gdt.limit = getpagesize() - 1;
-
-	sregs.cr0 = X86_CR0_PE | X86_CR0_NE | X86_CR0_PG;
-	sregs.cr4 |= X86_CR4_PAE | X86_CR4_OSFXSR;
-	if (kvm_cpu_has(X86_FEATURE_XSAVE))
-		sregs.cr4 |= X86_CR4_OSXSAVE;
-	if (vm->mmu.pgtable_levels == 5)
-		sregs.cr4 |= X86_CR4_LA57;
-	sregs.efer |= (EFER_LME | EFER_LMA | EFER_NX);
+	sregs.gdt.limit = kvm_get_default_gdt_limit();
+
+	sregs.cr0 = kvm_get_default_cr0();
+	sregs.cr4 |= kvm_get_default_cr4(vm->mmu.pgtable_levels);
+	sregs.efer |= kvm_get_default_efer();
 
  	kvm_seg_set_unusable(&sregs.ldt);
  	kvm_seg_set_kernel_code_64bit(&sregs.cs);
#32 lore raw

Re: [PATCH v14 09/22] KVM: selftests: Expose functions to get default sregs values

In reply to Lisa Wang

tools/testing/selftests/kvm/include/x86/processor.h extern u64 guest_tsc_khz; · line 35

Quoted 5 lines · Lisa Wang

tools/testing/selftests/kvm/include/x86/processor.h +4 -0
+#ifndef NUM_INTERRUPTS
+#define NUM_INTERRUPTS 256
+#endif
+

Is this necessary for this patch? I see why to extract kvm_get_default_idt_limit() because now it's used twice. But why extract this define too?

#33 lore raw

Re: [PATCH v14 09/22] KVM: selftests: Expose functions to get default sregs values

Quoted 10 lines · Rick P

tools/testing/selftests/kvm/include/x86/processor.h extern u64 guest_tsc_khz; · line 35

Quoted 5 lines · Lisa Wang
tools/testing/selftests/kvm/include/x86/processor.h +4 -0
+#ifndef NUM_INTERRUPTS
+#define NUM_INTERRUPTS 256
+#endif
+

Is this necessary for this patch? I see why to extract kvm_get_default_idt_limit() because now it's used twice. But why extract this define too?

kvm_get_default_idt_limit() uses NUM_INTERRUPTS, and kvm_get_default_idt_limit() is extracted as a helper from .c file to .h file. Shouldn't the define of NUM_INTERRUPTS be moved together?

#34 lore raw

Re: [PATCH v14 09/22] KVM: selftests: Expose functions to get default sregs values

Quoted 8 lines · Xiaoyao Li
Quoted 4 lines

Is this necessary for this patch? I see why to extract kvm_get_default_idt_limit() because now it's used twice. But why extract this
define too?

kvm_get_default_idt_limit() uses NUM_INTERRUPTS, and kvm_get_default_idt_limit() is extracted as a helper from .c file to .h file. Shouldn't the define of NUM_INTERRUPTS be moved together?

Err. yes. My bad. Trying to do too many things at once.

author
#35 lore raw

PATCH v14 10/22 KVM: selftests: Set up TDX boot code region

In reply to Lisa Wang

From: Sagi Shahar <[email protected]>

Add memory for TDX boot code in a separate memslot.

Use virt_map() to get identity map in this memory region to allow for seamless transition from paging disabled to paging enabled code.

Copy the boot code into the memory region and set up the reset vector at this point. While it's possible to separate the memory allocation and boot code initialization into separate functions, having all the calculations for memory size and offsets in one place simplifies the code and avoids duplications.

Handcode the reset vector as suggested by Sean Christopherson.

---
 .../selftests/kvm/include/x86/tdx/tdx_util.h       |  1 +
 tools/testing/selftests/kvm/lib/x86/processor.c    |  4 +-
 tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c | 46 ++++++++++++++++++++++
 3 files changed, 50 insertions(+), 1 deletion(-)
tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h +1 -0
index eb8602dce0bc..642ffa010da3 100644
@@ -45,5 +45,6 @@ static inline bool is_tdx_vm(struct kvm_vm *vm)
 })
 
 void tdx_init_vm(struct kvm_vm *vm, u64 attributes);
+void tdx_vm_setup_boot_code_region(struct kvm_vm *vm);
 
 #endif /* SELFTESTS_TDX_TDX_UTIL_H */
tools/testing/selftests/kvm/lib/x86/processor.c +3 -1
index ad75e19bb3a2..f93953777ca1 100644
@@ -791,8 +791,10 @@ void kvm_arch_vm_post_create(struct kvm_vm *vm, unsigned int nr_vcpus)
 		vm_sev_ioctl(vm, KVM_SEV_INIT2, &init);
 	}
 
-	if (is_tdx_vm(vm))
+	if (is_tdx_vm(vm)) {
 		tdx_init_vm(vm, 0);
+		tdx_vm_setup_boot_code_region(vm);
+	}
 
 	r = __vm_ioctl(vm, KVM_GET_TSC_KHZ, NULL);
 	TEST_ASSERT(r > 0, "KVM_GET_TSC_KHZ did not provide a valid TSC frequency.");
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +46 -0

54 lines not on this page · raw message

Signature · 1 line
2.55.0.229.g6434b31f56-goog
Reviewed-by on v14 10/22
#36 lore raw

Re: [PATCH v14 10/22] KVM: selftests: Set up TDX boot code region

Quoted 13 lines · Lisa Wang

From: Sagi Shahar <[email protected]>

Add memory for TDX boot code in a separate memslot.

Use virt_map() to get identity map in this memory region to allow for seamless transition from paging disabled to paging enabled code.

Copy the boot code into the memory region and set up the reset vector at this point. While it's possible to separate the memory allocation and boot code initialization into separate functions, having all the calculations for memory size and offsets in one place simplifies the code and avoids duplications.

Quoted 8 lines

Handcode the reset vector as suggested by Sean Christopherson.

#37 lore raw

Re: [PATCH v14 10/22] KVM: selftests: Set up TDX boot code region

In reply to Lisa Wang
Quoted 27 lines · Lisa Wang

From: Sagi Shahar <[email protected]>

Add memory for TDX boot code in a separate memslot.

Use virt_map() to get identity map in this memory region to allow for seamless transition from paging disabled to paging enabled code.

Copy the boot code into the memory region and set up the reset vector at this point. While it's possible to separate the memory allocation and boot code initialization into separate functions, having all the calculations for memory size and offsets in one place simplifies the code and avoids duplications.

Handcode the reset vector as suggested by Sean Christopherson.

---
 .../selftests/kvm/include/x86/tdx/tdx_util.h       |  1 +
 tools/testing/selftests/kvm/lib/x86/processor.c    |  4 +-
 tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c | 46 ++++++++++++++++++++++
 3 files changed, 50 insertions(+), 1 deletion(-)

[ ... ]

Quoted 28 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +28 -0
+
+void tdx_vm_setup_boot_code_region(struct kvm_vm *vm)
+{
+	size_t total_code_size = TD_BOOT_CODE_SIZE + X86_RESET_VECTOR_SIZE;
+	gpa_t boot_code_gpa = X86_RESET_VECTOR - TD_BOOT_CODE_SIZE;
+	gpa_t alloc_gpa = round_down(boot_code_gpa, PAGE_SIZE);
+	size_t nr_pages = DIV_ROUND_UP(total_code_size, PAGE_SIZE);
+	const u64 gmem_flags = 0;
+	gpa_t gpa;
+	u8 *hva;
+
+	vm_mem_add(vm, VM_MEM_SRC_SHMEM, alloc_gpa, TD_BOOT_CODE_SLOT,
+		   nr_pages, KVM_MEM_GUEST_MEMFD, -1, 0, gmem_flags);
+
+	gpa = vm_phy_pages_alloc(vm, nr_pages, alloc_gpa, TD_BOOT_CODE_SLOT);
+	TEST_ASSERT(gpa == alloc_gpa, "Failed vm_phy_pages_alloc\n");
+
+	virt_map(vm, alloc_gpa, alloc_gpa, nr_pages);
+	hva = addr_gpa2hva(vm, boot_code_gpa);
+	memcpy(hva, td_boot, TD_BOOT_CODE_SIZE);
+
+	hva += TD_BOOT_CODE_SIZE;
+	TEST_ASSERT(hva == addr_gpa2hva(vm, X86_RESET_VECTOR),
+		    "Expected RESET vector at hva 0x%lx, got %lx",
+		    (unsigned long)addr_gpa2hva(vm, X86_RESET_VECTOR), (unsigned long)hva);
+
+	/*
+	 * Handcode "JMP rel8" at the RESET vector to jump back to the TD boot

tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c line 49

Quoted 10 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +10 -0
+	 * code, as there are only 16 bytes at the RESET vector before RIP will
+	 * wrap back to zero. Insert a trailing int3 so that the vCPU crashes in
+	 * case the JMP somehow falls through. Note! The target address is
+	 * relative to the end of the instruction!
+	 */
+	TEST_ASSERT(TD_BOOT_CODE_SIZE + 2 <= 128,
+		    "TD boot code not addressable by 'JMP rel8'");
+	hva[0] = 0xeb;
+	hva[1] = 256 - 2 - TD_BOOT_CODE_SIZE;
+	hva[2] = 0xcc;

This handcoding has persisted for several versions (since v9) so I only want to comment gently... The current code looks correct but I think this could be simpler. But feel free to keep the current implementation.

I can see handcoding "JMP rel8" generates a jump instruction predictably. But having to calculate and sanity check the boot code offset by hand seems quite painful. And the math is quite tricky. I was thinking the assembler could do a lot more heavy lifting here.

A nice property of this boot code is the fact that it's executed in 32-bit mode from the get go. So there's no need for 16-bit programming and "JMP rel32" is readily available. I.e. a "jmp td_boot" in td_boot.S should suffice and the assembler screams if td_boot isn't reachable. And the int3's after the jump can probably go away as well since the jump is now very, very likely to succeed.

There shouldn't be a need to "pad reset_vector to its full size of 16 bytes" as stated in v8 [1]. The exported "reset_vector" symbol in v8, plus the boot code start & end markers, should be enough to help tdx_vm_setup_boot_code_region() put this boot blob in the right place.

[1] https://lore.kernel.org/all/[email protected]/

Quoted 10 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +2 -0
+}
+
 static struct kvm_tdx_capabilities *tdx_read_capabilities(struct kvm_vm *vm)
 {
 	static struct kvm_tdx_capabilities *tdx_cap;
Signature · 2 lines
-- 
2.55.0.229.g6434b31f56-goog
#38 lore raw

Re: [PATCH v14 10/22] KVM: selftests: Set up TDX boot code region

Quoted 55 lines · Peter Fang
Quoted 27 lines · Lisa Wang
(quoted diff) +27 -0
+void tdx_vm_setup_boot_code_region(struct kvm_vm *vm)
+{
+	size_t total_code_size = TD_BOOT_CODE_SIZE + X86_RESET_VECTOR_SIZE;
+	gpa_t boot_code_gpa = X86_RESET_VECTOR - TD_BOOT_CODE_SIZE;
+	gpa_t alloc_gpa = round_down(boot_code_gpa, PAGE_SIZE);
+	size_t nr_pages = DIV_ROUND_UP(total_code_size, PAGE_SIZE);
+	const u64 gmem_flags = 0;
+	gpa_t gpa;
+	u8 *hva;
+
+	vm_mem_add(vm, VM_MEM_SRC_SHMEM, alloc_gpa, TD_BOOT_CODE_SLOT,
+		   nr_pages, KVM_MEM_GUEST_MEMFD, -1, 0, gmem_flags);
+
+	gpa = vm_phy_pages_alloc(vm, nr_pages, alloc_gpa, TD_BOOT_CODE_SLOT);
+	TEST_ASSERT(gpa == alloc_gpa, "Failed vm_phy_pages_alloc\n");
+
+	virt_map(vm, alloc_gpa, alloc_gpa, nr_pages);
+	hva = addr_gpa2hva(vm, boot_code_gpa);
+	memcpy(hva, td_boot, TD_BOOT_CODE_SIZE);
+
+	hva += TD_BOOT_CODE_SIZE;
+	TEST_ASSERT(hva == addr_gpa2hva(vm, X86_RESET_VECTOR),
+		    "Expected RESET vector at hva 0x%lx, got %lx",
+		    (unsigned long)addr_gpa2hva(vm, X86_RESET_VECTOR), (unsigned long)hva);
+
+	/*
+	 * Handcode "JMP rel8" at the RESET vector to jump back to the TD boot

Review

Quoted 10 lines
(quoted diff) +10 -0
+	 * code, as there are only 16 bytes at the RESET vector before RIP will
+	 * wrap back to zero. Insert a trailing int3 so that the vCPU crashes in
+	 * case the JMP somehow falls through. Note! The target address is
+	 * relative to the end of the instruction!
+	 */
+	TEST_ASSERT(TD_BOOT_CODE_SIZE + 2 <= 128,
+		    "TD boot code not addressable by 'JMP rel8'");
+	hva[0] = 0xeb;
+	hva[1] = 256 - 2 - TD_BOOT_CODE_SIZE;
+	hva[2] = 0xcc;

This handcoding has persisted for several versions (since v9) so I only want to comment gently... The current code looks correct but I think this could be simpler. But feel free to keep the current implementation.

I can see handcoding "JMP rel8" generates a jump instruction predictably. But having to calculate and sanity check the boot code offset by hand seems quite painful. And the math is quite tricky. I was thinking the assembler could do a lot more heavy lifting here.

A nice property of this boot code is the fact that it's executed in 32-bit mode from the get go. So there's no need for 16-bit programming and "JMP rel32" is readily available. I.e. a "jmp td_boot" in td_boot.S should suffice and the assembler screams if td_boot isn't reachable. And the int3's after the jump can probably go away as well since the jump is now very, very likely to succeed.

Quoted 4 lines

There shouldn't be a need to "pad reset_vector to its full size of 16 bytes" as stated in v8 [1]. The exported "reset_vector" symbol in v8, plus the boot code start & end markers, should be enough to help tdx_vm_setup_boot_code_region() put this boot blob in the right place.

Ya, looking at this with fresh eyes, AFAICT there's no reason to handcode anything, it's just basic arithmetic.

Side topic, this series doesn't compile for me, so the below isn't even properly compile-tested (I hacked in arbitrary literals to get past the undefined references).

/usr/bin/x86_64-linux-gnu-ld.bfd: tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:26:(.text+0xf): undefined reference to TD_BOOT_PARAMETERS_PER_VCPU' /usr/bin/x86_64-linux-gnu-ld.bfd: tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:30:(.text+0x17): undefined reference to TD_PER_VCPU_PARAMETERS_ESP_GVA' /usr/bin/x86_64-linux-gnu-ld.bfd: tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:33:(.text+0x1d): undefined reference to TD_BOOT_PARAMETERS_GDT' /usr/bin/x86_64-linux-gnu-ld.bfd: tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:37:(.text+0x26): undefined reference to TD_BOOT_PARAMETERS_IDT' /usr/bin/x86_64-linux-gnu-ld.bfd: tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:44:(.text+0x2f): undefined reference to TD_BOOT_PARAMETERS_CR4' /usr/bin/x86_64-linux-gnu-ld.bfd: tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:46:(.text+0x38): undefined reference to TD_BOOT_PARAMETERS_CR3' /usr/bin/x86_64-linux-gnu-ld.bfd: tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:48:(.text+0x41): undefined reference to TD_BOOT_PARAMETERS_CR0' /usr/bin/x86_64-linux-gnu-ld.bfd: tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:54:(.text+0x51): undefined reference to TD_PER_VCPU_PARAMETERS_GUEST_CODE'

tools/testing/selftests/kvm/include/x86/tdx/td_boot.h +1 -2
index 89cf6c3485be..439d6f10489e 100644
@@ -73,10 +73,9 @@ struct td_boot_parameters {
 };
 
 void td_boot(void);
+void td_boot_reset_vector_trampoline(void);
 void td_boot_code_end(void);
 
-#define TD_BOOT_CODE_SIZE (td_boot_code_end - td_boot)
-
 #endif /* !defined(__ASSEMBLY__) && !defined(__ASSEMBLER__) */
 
 #endif /* SELFTEST_TDX_TD_BOOT_H */
tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S +6 -0
index bca9a4c3d8f8..2882fb2ea43e 100644
@@ -52,6 +52,12 @@ td_boot:
 	ljmp $(KERNEL_CS),$1f
 1:
 	jmp *TD_PER_VCPU_PARAMETERS_GUEST_CODE(%eax)
+	int3
+
+.globl td_boot_reset_vector_trampoline
+td_boot_reset_vector_trampoline:
+	jmp td_boot
+	int3
 
 /* Leave marker so size of td_boot code can be computed. */
 .globl td_boot_code_end
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +6 -18
index 831b0e5160df..170354dcdb66 100644
@@ -15,10 +15,11 @@
 
 void tdx_vm_setup_boot_code_region(struct kvm_vm *vm)
 {
-	size_t total_code_size = TD_BOOT_CODE_SIZE + X86_RESET_VECTOR_SIZE;
-	gpa_t boot_code_gpa = X86_RESET_VECTOR - TD_BOOT_CODE_SIZE;
+	const size_t total_size = td_boot_code_end - td_boot;
+	const size_t boot_code_size = td_boot_reset_vector_trampoline - td_boot;
+	const gpa_t boot_code_gpa = X86_RESET_VECTOR - boot_code_size;
 	gpa_t alloc_gpa = round_down(boot_code_gpa, PAGE_SIZE);
-	size_t nr_pages = DIV_ROUND_UP(total_code_size, PAGE_SIZE);
+	size_t nr_pages = DIV_ROUND_UP(total_size, PAGE_SIZE);
 	u64 gmem_flags = 0;
 	gpa_t gpa;
 	u8 *hva;
@@ -33,25 +34,12 @@ void tdx_vm_setup_boot_code_region(struct kvm_vm *vm)
 
 	virt_map(vm, alloc_gpa, alloc_gpa, nr_pages);
 	hva = addr_gpa2hva(vm, boot_code_gpa);
-	memcpy(hva, td_boot, TD_BOOT_CODE_SIZE);
+	memcpy(hva, td_boot, total_size);
 
-	hva += TD_BOOT_CODE_SIZE;
+	hva += boot_code_size;
 	TEST_ASSERT(hva == addr_gpa2hva(vm, X86_RESET_VECTOR),
 		    "Expected RESET vector at hva 0x%lx, got %lx",
 		    (unsigned long)addr_gpa2hva(vm, X86_RESET_VECTOR), (unsigned long)hva);
-
-	/*
-	 * Handcode "JMP rel8" at the RESET vector to jump back to the TD boot
-	 * code, as there are only 16 bytes at the RESET vector before RIP will
-	 * wrap back to zero. Insert a trailing int3 so that the vCPU crashes in
-	 * case the JMP somehow falls through. Note! The target address is
-	 * relative to the end of the instruction!
-	 */
-	TEST_ASSERT(TD_BOOT_CODE_SIZE + 2 <= 128,
-		    "TD boot code not addressable by 'JMP rel8'");
-	hva[0] = 0xeb;
-	hva[1] = 256 - 2 - TD_BOOT_CODE_SIZE;
-	hva[2] = 0xcc;
 }
 
 void tdx_vm_setup_boot_parameters_region(struct kvm_vm *vm, u32 nr_runnable_vcpus)
#39 lore raw

Re: [PATCH v14 10/22] KVM: selftests: Set up TDX boot code region

tools/testing/selftests/kvm/include/x86/tdx/td_boot.h

Quoted 21 lines · Sean Christopherson

Ya, looking at this with fresh eyes, AFAICT there's no reason to handcode anything, it's just basic arithmetic.

Side topic, this series doesn't compile for me, so the below isn't even properly compile-tested (I hacked in arbitrary literals to get past the undefined references).

/usr/bin/x86_64-linux-gnu-ld.bfd: tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:26:(.text+0xf): undefined reference to TD_BOOT_PARAMETERS_PER_VCPU' /usr/bin/x86_64-linux-gnu-ld.bfd: tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:30:(.text+0x17): undefined reference to TD_PER_VCPU_PARAMETERS_ESP_GVA' /usr/bin/x86_64-linux-gnu-ld.bfd: tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:33:(.text+0x1d): undefined reference to TD_BOOT_PARAMETERS_GDT' /usr/bin/x86_64-linux-gnu-ld.bfd: tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:37:(.text+0x26): undefined reference to TD_BOOT_PARAMETERS_IDT' /usr/bin/x86_64-linux-gnu-ld.bfd: tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:44:(.text+0x2f): undefined reference to TD_BOOT_PARAMETERS_CR4' /usr/bin/x86_64-linux-gnu-ld.bfd: tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:46:(.text+0x38): undefined reference to TD_BOOT_PARAMETERS_CR3' /usr/bin/x86_64-linux-gnu-ld.bfd: tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:48:(.text+0x41): undefined reference to TD_BOOT_PARAMETERS_CR0' /usr/bin/x86_64-linux-gnu-ld.bfd: tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:54:(.text+0x51): undefined reference to TD_PER_VCPU_PARAMETERS_GUEST_CODE'

tools/testing/selftests/kvm/include/x86/tdx/td_boot.h +0 -0
index 89cf6c3485be..439d6f10489e 100644

Thanks Sean! This diff is pretty much what I had in mind. Just one comment below...

Quoted 6 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +0 -0
index 831b0e5160df..170354dcdb66 100644
@@ -15,10 +15,11 @@
 

tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c line 22

Quoted 10 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +4 -3
 void tdx_vm_setup_boot_code_region(struct kvm_vm *vm)
 {
-	size_t total_code_size = TD_BOOT_CODE_SIZE + X86_RESET_VECTOR_SIZE;
-	gpa_t boot_code_gpa = X86_RESET_VECTOR - TD_BOOT_CODE_SIZE;
+	const size_t total_size = td_boot_code_end - td_boot;
+	const size_t boot_code_size = td_boot_reset_vector_trampoline - td_boot;
+	const gpa_t boot_code_gpa = X86_RESET_VECTOR - boot_code_size;
 	gpa_t alloc_gpa = round_down(boot_code_gpa, PAGE_SIZE);
-	size_t nr_pages = DIV_ROUND_UP(total_code_size, PAGE_SIZE);
+	size_t nr_pages = DIV_ROUND_UP(total_size, PAGE_SIZE);

td_boot_code_end and td_boot could both be non-page-aligned, so instead this can be:

        size_t nr_pages = (SZ_4G - alloc_gpa) / PAGE_SIZE;
Quoted 3 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +0 -0
 	u64 gmem_flags = 0;
 	gpa_t gpa;
 	u8 *hva;
author
#40 lore raw

Re: [PATCH v14 10/22] KVM: selftests: Set up TDX boot code region

In reply to Sean Christopherson
Quoted 12 lines · Sean Christopherson
Quoted 5 lines

There shouldn't be a need to "pad reset_vector to its full size of 16 bytes" as stated in v8 [1]. The exported "reset_vector" symbol in v8, plus the boot code start & end markers, should be enough to help tdx_vm_setup_boot_code_region() put this boot blob in the right place.

Ya, looking at this with fresh eyes, AFAICT there's no reason to handcode anything, it's just basic arithmetic.

Side topic, this series doesn't compile for me, so the below isn't even properly compile-tested (I hacked in arbitrary literals to get past the undefined references).

Quoted 8 lines

/usr/bin/x86_64-linux-gnu-ld.bfd: tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:26:(.text+0xf): undefined reference to TD_BOOT_PARAMETERS_PER_VCPU' /usr/bin/x86_64-linux-gnu-ld.bfd: tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:30:(.text+0x17): undefined reference to TD_PER_VCPU_PARAMETERS_ESP_GVA' /usr/bin/x86_64-linux-gnu-ld.bfd: tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:33:(.text+0x1d): undefined reference to TD_BOOT_PARAMETERS_GDT' /usr/bin/x86_64-linux-gnu-ld.bfd: tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:37:(.text+0x26): undefined reference to TD_BOOT_PARAMETERS_IDT' /usr/bin/x86_64-linux-gnu-ld.bfd: tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:44:(.text+0x2f): undefined reference to TD_BOOT_PARAMETERS_CR4' /usr/bin/x86_64-linux-gnu-ld.bfd: tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:46:(.text+0x38): undefined reference to TD_BOOT_PARAMETERS_CR3' /usr/bin/x86_64-linux-gnu-ld.bfd: tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:48:(.text+0x41): undefined reference to TD_BOOT_PARAMETERS_CR0' /usr/bin/x86_64-linux-gnu-ld.bfd: tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:54:(.text+0x51): undefined reference to TD_PER_VCPU_PARAMETERS_GUEST_CODE'

The patch series posted here on the mailing list is correct, but I made a one-line mistake when pushing the code to the GitHub branch. I have just force-pushed the fix to the branch. It should compile correctly now. Please let me know if you run into any further compilation issues.

I will test this change on my side. Thanks!

Lisa

author
#41 lore raw

PATCH v14 11/22 KVM: selftests: Set up TDX boot parameters region

In reply to Lisa Wang

From: Sagi Shahar <[email protected]>

Allocate memory for TDX boot parameters and define the utility functions necessary to fill this memory with the boot parameters.

---
 .../selftests/kvm/include/x86/tdx/tdx_util.h       |  2 +
 tools/testing/selftests/kvm/lib/x86/processor.c    |  2 +
 tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c | 51 ++++++++++++++++++++++
 3 files changed, 55 insertions(+)
tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h +2 -0
index 642ffa010da3..f5003cbaa106 100644
@@ -46,5 +46,7 @@ static inline bool is_tdx_vm(struct kvm_vm *vm)
 
 void tdx_init_vm(struct kvm_vm *vm, u64 attributes);
 void tdx_vm_setup_boot_code_region(struct kvm_vm *vm);
+void tdx_vm_setup_boot_parameters_region(struct kvm_vm *vm, u32 nr_runnable_vcpus);
+void tdx_vm_load_common_boot_parameters(struct kvm_vm *vm);
 
 #endif /* SELFTESTS_TDX_TDX_UTIL_H */
tools/testing/selftests/kvm/lib/x86/processor.c +2 -0
index f93953777ca1..748c6041f8bf 100644
@@ -794,6 +794,8 @@ void kvm_arch_vm_post_create(struct kvm_vm *vm, unsigned int nr_vcpus)
 	if (is_tdx_vm(vm)) {
 		tdx_init_vm(vm, 0);
 		tdx_vm_setup_boot_code_region(vm);
+		tdx_vm_setup_boot_parameters_region(vm, nr_vcpus);
+		tdx_vm_load_common_boot_parameters(vm);
 	}
 
 	r = __vm_ioctl(vm, KVM_GET_TSC_KHZ, NULL);
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +51 -0

63 lines not on this page · raw message

Signature · 1 line
2.55.0.229.g6434b31f56-goog
Reviewed-by on v14 11/22
#42 lore raw

Re: [PATCH v14 11/22] KVM: selftests: Set up TDX boot parameters region

Quoted 9 lines · Lisa Wang

From: Sagi Shahar <[email protected]>

Allocate memory for TDX boot parameters and define the utility functions necessary to fill this memory with the boot parameters.

2 nits below. I think you can keep the code as-is unless maintainers ask you to change.

Quoted 44 lines
---
  .../selftests/kvm/include/x86/tdx/tdx_util.h       |  2 +
  tools/testing/selftests/kvm/lib/x86/processor.c    |  2 +
  tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c | 51 ++++++++++++++++++++++
  3 files changed, 55 insertions(+)
tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h +2 -0
index 642ffa010da3..f5003cbaa106 100644
@@ -46,5 +46,7 @@ static inline bool is_tdx_vm(struct kvm_vm *vm)
 
  void tdx_init_vm(struct kvm_vm *vm, u64 attributes);
  void tdx_vm_setup_boot_code_region(struct kvm_vm *vm);
+void tdx_vm_setup_boot_parameters_region(struct kvm_vm *vm, u32 nr_runnable_vcpus);
+void tdx_vm_load_common_boot_parameters(struct kvm_vm *vm);
 
  #endif /* SELFTESTS_TDX_TDX_UTIL_H */
tools/testing/selftests/kvm/lib/x86/processor.c +2 -0
index f93953777ca1..748c6041f8bf 100644
@@ -794,6 +794,8 @@ void kvm_arch_vm_post_create(struct kvm_vm *vm, unsigned int nr_vcpus)
  	if (is_tdx_vm(vm)) {
  		tdx_init_vm(vm, 0);
  		tdx_vm_setup_boot_code_region(vm);
+		tdx_vm_setup_boot_parameters_region(vm, nr_vcpus);
+		tdx_vm_load_common_boot_parameters(vm);
  	}
 
  	r = __vm_ioctl(vm, KVM_GET_TSC_KHZ, NULL);
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +1 -0
index d64d153d19e2..2ce8c297f7ed 100644
@@ -6,6 +6,7 @@
 
  /* Arbitrarily selected to avoid overlaps with anything else */
  #define TD_BOOT_CODE_SLOT	20
+#define TD_BOOT_PARAMETERS_SLOT	21
 
  #define X86_RESET_VECTOR	0xfffffff0ul
  #define X86_RESET_VECTOR_SIZE	16

tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c void tdx_vm_setup_boot_code_region(struct kvm_vm *vm) · line 57

Quoted 8 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +5 -0
@@ -49,6 +50,56 @@ void tdx_vm_setup_boot_code_region(struct kvm_vm *vm)
  	hva[2] = 0xcc;
  }
 
+void tdx_vm_setup_boot_parameters_region(struct kvm_vm *vm, u32 nr_runnable_vcpus)
+{
+	size_t boot_params_size =
+		sizeof(struct td_boot_parameters) +
+		nr_runnable_vcpus * sizeof(struct td_per_vcpu_parameters);

Nit: I'm not sure if the style is acceptable to selftest or not. From KVM coding style, it's not common.

Quoted 7 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +7 -0
+	int npages = DIV_ROUND_UP(boot_params_size, PAGE_SIZE);
+	const u64 gmem_flags = 0;
+	gpa_t gpa;
+
+	vm_mem_add(vm, VM_MEM_SRC_SHMEM, TD_BOOT_PARAMETERS_GPA,
+		   TD_BOOT_PARAMETERS_SLOT, npages,
+		   KVM_MEM_GUEST_MEMFD, -1, 0, gmem_flags);

tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c void tdx_vm_setup_boot_code_region(struct kvm_vm *vm) · line 74

Quoted 10 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +10 -0
+	gpa = vm_phy_pages_alloc(vm, npages, TD_BOOT_PARAMETERS_GPA, TD_BOOT_PARAMETERS_SLOT);
+	TEST_ASSERT(gpa == TD_BOOT_PARAMETERS_GPA, "Failed vm_phy_pages_alloc\n");
+
+	virt_map(vm, TD_BOOT_PARAMETERS_GPA, TD_BOOT_PARAMETERS_GPA, npages);
+}
+
+void tdx_vm_load_common_boot_parameters(struct kvm_vm *vm)
+{
+	struct td_boot_parameters *params =
+		addr_gpa2hva(vm, TD_BOOT_PARAMETERS_GPA);

Nit: I think it's OK to make it just one line.

Quoted 32 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +28 -0
+	u32 cr4;
+
+	cr4 = kvm_get_default_cr4(vm->mmu.pgtable_levels);
+
+	/* TDX spec 11.6.2: CR4 bit MCE is fixed to 1 */
+	cr4 |= X86_CR4_MCE;
+
+	/* TDX spec 11.6.2: CR4 bit VMXE and SMXE are fixed to 0 */
+	cr4 &= ~(X86_CR4_VMXE | X86_CR4_SMXE);
+
+	/* Set parameters! */
+	params->cr0 = kvm_get_default_cr0();
+	TEST_ASSERT(vm->mmu.pgd < (1ULL << 32),
+		    "PGD must be within 32-bit address space for 32-bit boot code");
+	params->cr3 = vm->mmu.pgd;
+	params->cr4 = cr4;
+	params->idtr.base = vm->arch.idt;
+	params->idtr.limit = kvm_get_default_idt_limit();
+	params->gdtr.base = vm->arch.gdt;
+	params->gdtr.limit = kvm_get_default_gdt_limit();
+
+	TEST_ASSERT(params->cr0 != 0, "cr0 should not be 0");
+	TEST_ASSERT(params->cr3 != 0, "cr3 should not be 0");
+	TEST_ASSERT(params->cr4 != 0, "cr4 should not be 0");
+	TEST_ASSERT(params->gdtr.base != 0, "gdt base address should not be 0");
+	TEST_ASSERT(params->idtr.base != 0, "idt base address should not be 0");
+}
+
  static struct kvm_tdx_capabilities *tdx_read_capabilities(struct kvm_vm *vm)
  {
  	static struct kvm_tdx_capabilities *tdx_cap;
#43 lore raw

Re: [PATCH v14 11/22] KVM: selftests: Set up TDX boot parameters region

In reply to Lisa Wang
Quoted 4 lines · Lisa Wang

From: Sagi Shahar <[email protected]>

Allocate memory for TDX boot parameters and define the utility functions necessary to fill this memory with the boot parameters.

The code change looks good to me.

But I think it's better to describe how the parameters are initialized based on the HVA when VM_MEM_SRC_SHMEM is used alongside KVM_MEM_GUEST_MEMFD. Same for the patch 10.

Quoted 106 lines
---
 .../selftests/kvm/include/x86/tdx/tdx_util.h       |  2 +
 tools/testing/selftests/kvm/lib/x86/processor.c    |  2 +
 tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c | 51 ++++++++++++++++++++++
 3 files changed, 55 insertions(+)
tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h +2 -0
index 642ffa010da3..f5003cbaa106 100644
@@ -46,5 +46,7 @@ static inline bool is_tdx_vm(struct kvm_vm *vm)
 
 void tdx_init_vm(struct kvm_vm *vm, u64 attributes);
 void tdx_vm_setup_boot_code_region(struct kvm_vm *vm);
+void tdx_vm_setup_boot_parameters_region(struct kvm_vm *vm, u32 nr_runnable_vcpus);
+void tdx_vm_load_common_boot_parameters(struct kvm_vm *vm);
 
 #endif /* SELFTESTS_TDX_TDX_UTIL_H */
tools/testing/selftests/kvm/lib/x86/processor.c +2 -0
index f93953777ca1..748c6041f8bf 100644
@@ -794,6 +794,8 @@ void kvm_arch_vm_post_create(struct kvm_vm *vm, unsigned int nr_vcpus)
 	if (is_tdx_vm(vm)) {
 		tdx_init_vm(vm, 0);
 		tdx_vm_setup_boot_code_region(vm);
+		tdx_vm_setup_boot_parameters_region(vm, nr_vcpus);
+		tdx_vm_load_common_boot_parameters(vm);
 	}
 
 	r = __vm_ioctl(vm, KVM_GET_TSC_KHZ, NULL);
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +51 -0
index d64d153d19e2..2ce8c297f7ed 100644
@@ -6,6 +6,7 @@
 
 /* Arbitrarily selected to avoid overlaps with anything else */
 #define TD_BOOT_CODE_SLOT	20
+#define TD_BOOT_PARAMETERS_SLOT	21
 
 #define X86_RESET_VECTOR	0xfffffff0ul
 #define X86_RESET_VECTOR_SIZE	16
@@ -49,6 +50,56 @@ void tdx_vm_setup_boot_code_region(struct kvm_vm *vm)
 	hva[2] = 0xcc;
 }
 
+void tdx_vm_setup_boot_parameters_region(struct kvm_vm *vm, u32 nr_runnable_vcpus)
+{
+	size_t boot_params_size =
+		sizeof(struct td_boot_parameters) +
+		nr_runnable_vcpus * sizeof(struct td_per_vcpu_parameters);
+	int npages = DIV_ROUND_UP(boot_params_size, PAGE_SIZE);
+	const u64 gmem_flags = 0;
+	gpa_t gpa;
+
+	vm_mem_add(vm, VM_MEM_SRC_SHMEM, TD_BOOT_PARAMETERS_GPA,
+		   TD_BOOT_PARAMETERS_SLOT, npages,
+		   KVM_MEM_GUEST_MEMFD, -1, 0, gmem_flags);
+	gpa = vm_phy_pages_alloc(vm, npages, TD_BOOT_PARAMETERS_GPA, TD_BOOT_PARAMETERS_SLOT);
+	TEST_ASSERT(gpa == TD_BOOT_PARAMETERS_GPA, "Failed vm_phy_pages_alloc\n");
+
+	virt_map(vm, TD_BOOT_PARAMETERS_GPA, TD_BOOT_PARAMETERS_GPA, npages);
+}
+
+void tdx_vm_load_common_boot_parameters(struct kvm_vm *vm)
+{
+	struct td_boot_parameters *params =
+		addr_gpa2hva(vm, TD_BOOT_PARAMETERS_GPA);
+	u32 cr4;
+
+	cr4 = kvm_get_default_cr4(vm->mmu.pgtable_levels);
+
+	/* TDX spec 11.6.2: CR4 bit MCE is fixed to 1 */
+	cr4 |= X86_CR4_MCE;
+
+	/* TDX spec 11.6.2: CR4 bit VMXE and SMXE are fixed to 0 */
+	cr4 &= ~(X86_CR4_VMXE | X86_CR4_SMXE);
+
+	/* Set parameters! */
+	params->cr0 = kvm_get_default_cr0();
+	TEST_ASSERT(vm->mmu.pgd < (1ULL << 32),
+		    "PGD must be within 32-bit address space for 32-bit boot code");
+	params->cr3 = vm->mmu.pgd;
+	params->cr4 = cr4;
+	params->idtr.base = vm->arch.idt;
+	params->idtr.limit = kvm_get_default_idt_limit();
+	params->gdtr.base = vm->arch.gdt;
+	params->gdtr.limit = kvm_get_default_gdt_limit();
+
+	TEST_ASSERT(params->cr0 != 0, "cr0 should not be 0");
+	TEST_ASSERT(params->cr3 != 0, "cr3 should not be 0");
+	TEST_ASSERT(params->cr4 != 0, "cr4 should not be 0");
+	TEST_ASSERT(params->gdtr.base != 0, "gdt base address should not be 0");
+	TEST_ASSERT(params->idtr.base != 0, "idt base address should not be 0");
+}
+
 static struct kvm_tdx_capabilities *tdx_read_capabilities(struct kvm_vm *vm)
 {
 	static struct kvm_tdx_capabilities *tdx_cap;
#44 lore raw

Re: [PATCH v14 11/22] KVM: selftests: Set up TDX boot parameters region

In reply to Lisa Wang
Quoted 15 lines · Lisa Wang

From: Sagi Shahar <[email protected]>

Allocate memory for TDX boot parameters and define the utility functions necessary to fill this memory with the boot parameters.

---
 .../selftests/kvm/include/x86/tdx/tdx_util.h       |  2 +
 tools/testing/selftests/kvm/lib/x86/processor.c    |  2 +
 tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c | 51 ++++++++++++++++++++++
 3 files changed, 55 insertions(+)

[ ... ]

tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c void tdx_vm_setup_boot_code_region(struct kvm_vm *vm) · line 82

Quoted 13 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +13 -0
+
+void tdx_vm_load_common_boot_parameters(struct kvm_vm *vm)
+{
+	struct td_boot_parameters *params =
+		addr_gpa2hva(vm, TD_BOOT_PARAMETERS_GPA);
+	u32 cr4;
+
+	cr4 = kvm_get_default_cr4(vm->mmu.pgtable_levels);
+
+	/* TDX spec 11.6.2: CR4 bit MCE is fixed to 1 */
+	cr4 |= X86_CR4_MCE;
+
+	/* TDX spec 11.6.2: CR4 bit VMXE and SMXE are fixed to 0 */

This is now out of date (11.9.2 as of today)...
I wonder if it's better to just say something like: "Per the TDX spec, ..."

tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c void tdx_vm_setup_boot_code_region(struct kvm_vm *vm) · line 93

Quoted 11 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +11 -0
+	cr4 &= ~(X86_CR4_VMXE | X86_CR4_SMXE);
+
+	/* Set parameters! */
+	params->cr0 = kvm_get_default_cr0();
+	TEST_ASSERT(vm->mmu.pgd < (1ULL << 32),
+		    "PGD must be within 32-bit address space for 32-bit boot code");
+	params->cr3 = vm->mmu.pgd;
+	params->cr4 = cr4;
+	params->idtr.base = vm->arch.idt;
+	params->idtr.limit = kvm_get_default_idt_limit();
+	params->gdtr.base = vm->arch.gdt;
        vm->arch.{idt,gdt} must also be 32-bit addressable here, just
        like vm->mmu.pgd.

tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c void tdx_vm_setup_boot_code_region(struct kvm_vm *vm) · line 100

Quoted 7 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +7 -0
+	params->gdtr.limit = kvm_get_default_gdt_limit();
+
+	TEST_ASSERT(params->cr0 != 0, "cr0 should not be 0");
+	TEST_ASSERT(params->cr3 != 0, "cr3 should not be 0");
+	TEST_ASSERT(params->cr4 != 0, "cr4 should not be 0");
+	TEST_ASSERT(params->gdtr.base != 0, "gdt base address should not be 0");
+	TEST_ASSERT(params->idtr.base != 0, "idt base address should not be 0");

Making sure that cr0/cr4 are simply non-zero feels kind of odd to me. And cr3/gdtr/idtr all came from memory allocators with non-zero minimum addresses.

Quoted 10 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +2 -0
+}
+
 static struct kvm_tdx_capabilities *tdx_read_capabilities(struct kvm_vm *vm)
 {
 	static struct kvm_tdx_capabilities *tdx_cap;
Signature · 2 lines
-- 
2.55.0.229.g6434b31f56-goog
#45 lore raw

Re: [PATCH v14 11/22] KVM: selftests: Set up TDX boot parameters region

Quoted 28 lines · Peter Fang
Quoted 15 lines · Lisa Wang

From: Sagi Shahar <[email protected]>

Allocate memory for TDX boot parameters and define the utility functions necessary to fill this memory with the boot parameters.

---
 .../selftests/kvm/include/x86/tdx/tdx_util.h       |  2 +
 tools/testing/selftests/kvm/lib/x86/processor.c    |  2 +
 tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c | 51 ++++++++++++++++++++++
 3 files changed, 55 insertions(+)

[ ... ]

Quoted 9 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +9 -0
+
+void tdx_vm_load_common_boot_parameters(struct kvm_vm *vm)
+{
+	struct td_boot_parameters *params =
+		addr_gpa2hva(vm, TD_BOOT_PARAMETERS_GPA);
+	u32 cr4;
+
+	cr4 = kvm_get_default_cr4(vm->mmu.pgtable_levels);
+
Quoted 9 lines

tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c void tdx_vm_setup_boot_code_region(struct kvm_vm *vm) · line 82

Quoted 4 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +4 -0
+	/* TDX spec 11.6.2: CR4 bit MCE is fixed to 1 */
+	cr4 |= X86_CR4_MCE;
+
+	/* TDX spec 11.6.2: CR4 bit VMXE and SMXE are fixed to 0 */

This is now out of date (11.9.2 as of today)...
I wonder if it's better to just say something like: "Per the TDX spec, ..."

Makes sense, thanks!

Quoted 6 lines
Quoted 6 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +6 -0
+	cr4 &= ~(X86_CR4_VMXE | X86_CR4_SMXE);
+
+	/* Set parameters! */
+	params->cr0 = kvm_get_default_cr0();
+	TEST_ASSERT(vm->mmu.pgd < (1ULL << 32),
+		    "PGD must be within 32-bit address space for 32-bit boot code");
Quoted 9 lines

tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c void tdx_vm_setup_boot_code_region(struct kvm_vm *vm) · line 93

Quoted 5 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +5 -0
+	params->cr3 = vm->mmu.pgd;
+	params->cr4 = cr4;
+	params->idtr.base = vm->arch.idt;
+	params->idtr.limit = kvm_get_default_idt_limit();
+	params->gdtr.base = vm->arch.gdt;
        vm->arch.{idt,gdt} must also be 32-bit addressable here, just
        like vm->mmu.pgd.

Something like this?

    TEST_ASSERT(params->gdtr.base < (1ULL << 32),
            "gdt must be within 32-bit address space for 32-bit boot code");
Quoted 12 lines

tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c void tdx_vm_setup_boot_code_region(struct kvm_vm *vm) · line 100

Quoted 7 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +7 -0
+	params->gdtr.limit = kvm_get_default_gdt_limit();
+
+	TEST_ASSERT(params->cr0 != 0, "cr0 should not be 0");
+	TEST_ASSERT(params->cr3 != 0, "cr3 should not be 0");
+	TEST_ASSERT(params->cr4 != 0, "cr4 should not be 0");
+	TEST_ASSERT(params->gdtr.base != 0, "gdt base address should not be 0");
+	TEST_ASSERT(params->idtr.base != 0, "idt base address should not be 0");

Making sure that cr0/cr4 are simply non-zero feels kind of odd to me. And cr3/gdtr/idtr all came from memory allocators with non-zero minimum addresses.

I guess I intended these to be sanity checks that the caller didn't leave these unset (defaulting to 0), for an early warning to the selftest writer if something went wrong. I'd prefer to keep something that would retain this early warning for the writer.

What would you replace this with, to provide the early warning, or would you rather just remove it?

Quoted 10 lines
Quoted 10 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +2 -1
+}
+
 static struct kvm_tdx_capabilities *tdx_read_capabilities(struct kvm_vm *vm)
 {
 	static struct kvm_tdx_capabilities *tdx_cap;
 
--

2.55.0.229.g6434b31f56-goog

#46 lore raw

Re: [PATCH v14 11/22] KVM: selftests: Set up TDX boot parameters region

Quoted 17 lines · Ackerley Tng
Quoted 16 lines

tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c void tdx_vm_setup_boot_code_region(struct kvm_vm *vm) · line 93

Quoted 12 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +11 -0
+	cr4 &= ~(X86_CR4_VMXE | X86_CR4_SMXE);
+
+	/* Set parameters! */
+	params->cr0 = kvm_get_default_cr0();
+	TEST_ASSERT(vm->mmu.pgd < (1ULL << 32),
+		    "PGD must be within 32-bit address space for 32-bit boot code");
+	params->cr3 = vm->mmu.pgd;
+	params->cr4 = cr4;
+	params->idtr.base = vm->arch.idt;
+	params->idtr.limit = kvm_get_default_idt_limit();
+	params->gdtr.base = vm->arch.gdt;
        vm->arch.{idt,gdt} must also be 32-bit addressable here, just
        like vm->mmu.pgd.
Quoted 4 lines

Something like this?

    TEST_ASSERT(params->gdtr.base < (1ULL << 32),
            "gdt must be within 32-bit address space for 32-bit boot code");

Yep, looks good to me.

Quoted 15 lines
Quoted 14 lines

tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c void tdx_vm_setup_boot_code_region(struct kvm_vm *vm) · line 100

Quoted 9 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +7 -0
+	params->gdtr.limit = kvm_get_default_gdt_limit();
+
+	TEST_ASSERT(params->cr0 != 0, "cr0 should not be 0");
+	TEST_ASSERT(params->cr3 != 0, "cr3 should not be 0");
+	TEST_ASSERT(params->cr4 != 0, "cr4 should not be 0");
+	TEST_ASSERT(params->gdtr.base != 0, "gdt base address should not be 0");
+	TEST_ASSERT(params->idtr.base != 0, "idt base address should not be 0");

Making sure that cr0/cr4 are simply non-zero feels kind of odd to me. And cr3/gdtr/idtr all came from memory allocators with non-zero minimum addresses.

Quoted 7 lines

I guess I intended these to be sanity checks that the caller didn't leave these unset (defaulting to 0), for an early warning to the selftest writer if something went wrong. I'd prefer to keep something that would retain this early warning for the writer.

What would you replace this with, to provide the early warning, or would you rather just remove it?

Hmm... In earlier versions this was:

        params->cr0 = sregs->cr0;
        params->cr4 = sregs->cr4;

        TEST_ASSERT(params->cr0 != 0, "cr0 should not be 0");
        TEST_ASSERT(params->cr4 != 0, "cr4 should not be 0");

Which made more sense because perhaps @sregs could be sanity checked. But now the assignments are all in the same function:

        cr4 = kvm_get_default_cr4(vm->mmu.pgtable_levels);
        cr4 |= X86_CR4_MCE;
        cr4 &= ~(X86_CR4_VMXE | X86_CR4_SMXE);

        params->cr0 = kvm_get_default_cr0();
        params->cr4 = cr4;

        TEST_ASSERT(params->cr0 != 0, "cr0 should not be 0");
        TEST_ASSERT(params->cr4 != 0, "cr4 should not be 0");

This makes much less sense to me. It looks like sanity checking kvm_get_default_{cr0,cr4}().

So I'd remove the two asserts for cr0/cr4. Keeping the ones for cr3/gdtr/idtr is probably fine.

Quoted 1 line
author
#47 lore raw

PATCH v14 12/22 KVM: selftests: Require guest_memfd for TDX VMs

In reply to Lisa Wang

Force guest_memfd for TDX VMs.

KVM supports guest_memfd for TDX private memory. Without in-place conversion supports, all pages default attributes are private. Thus, set page to "shared" for shared pages.

---
 tools/testing/selftests/kvm/lib/kvm_util.c     | 2 +-
 tools/testing/selftests/kvm/lib/ucall_common.c | 8 ++++++++
 2 files changed, 9 insertions(+), 1 deletion(-)
tools/testing/selftests/kvm/lib/kvm_util.c +1 -1
index 0b9d2d383bfa..4a48c4b42095 100644
@@ -472,7 +472,7 @@ void kvm_set_files_rlimit(u32 nr_vcpus)
 static bool is_guest_memfd_required(struct vm_shape shape)
 {
 #ifdef __x86_64__
-	return shape.type == KVM_X86_SNP_VM;
+	return (shape.type == KVM_X86_SNP_VM || shape.type == KVM_X86_TDX_VM);
 #else
 	return false;
 #endif
tools/testing/selftests/kvm/lib/ucall_common.c +8 -0
index 029ce21f9f2f..c03dfc763172 100644
@@ -27,6 +27,7 @@ static struct ucall_header *ucall_pool;
 
 void ucall_init(struct kvm_vm *vm, gpa_t mmio_gpa)
 {
+	struct userspace_mem_region *region;
 	struct ucall_header *hdr;
 	struct ucall *uc;
 	gva_t gva;
@@ -37,6 +38,13 @@ void ucall_init(struct kvm_vm *vm, gpa_t mmio_gpa)
 	hdr = (struct ucall_header *)addr_gva2hva(vm, gva);
 	memset(hdr, 0, sizeof(*hdr));
 
+	region = vm_get_mem_region(vm, MEM_REGION_DATA);
+	if (region->region.flags & KVM_MEM_GUEST_MEMFD) {
+		size_t aligned_sz = align_up(sizeof(*hdr), vm->page_size);
+
+		vm_mem_set_shared(vm, addr_gva2gpa(vm, gva), aligned_sz);
+	}
+
 	for (i = 0; i < KVM_MAX_VCPUS; ++i) {
 		uc = &hdr->ucalls[i];
 		uc->hva = uc;
Signature · 1 line
2.55.0.229.g6434b31f56-goog
Reviewed-by on v14 12/22
#48 lore raw

Re: [PATCH v14 12/22] KVM: selftests: Require guest_memfd for TDX VMs

Quoted 7 lines · Lisa Wang

Force guest_memfd for TDX VMs.

KVM supports guest_memfd for TDX private memory. Without in-place conversion supports, all pages default attributes are private. Thus, set page to "shared" for shared pages.

One nit below.

Quoted 10 lines
---
 tools/testing/selftests/kvm/lib/kvm_util.c     | 2 +-
 tools/testing/selftests/kvm/lib/ucall_common.c | 8 ++++++++
 2 files changed, 9 insertions(+), 1 deletion(-)
tools/testing/selftests/kvm/lib/kvm_util.c +0 -0
index 0b9d2d383bfa..4a48c4b42095 100644

tools/testing/selftests/kvm/lib/kvm_util.c void kvm_set_files_rlimit(u32 nr_vcpus) · line 475

Quoted 5 lines
tools/testing/selftests/kvm/lib/kvm_util.c +1 -1
@@ -472,7 +472,7 @@ void kvm_set_files_rlimit(u32 nr_vcpus)
 static bool is_guest_memfd_required(struct vm_shape shape)
 {
 #ifdef __x86_64__
-	return shape.type == KVM_X86_SNP_VM;
+	return (shape.type == KVM_X86_SNP_VM || shape.type == KVM_X86_TDX_VM);

Nit:
The parentheses can be dropped.

Quoted 30 lines
tools/testing/selftests/kvm/lib/kvm_util.c +0 -0
 #else
 	return false;
 #endif
tools/testing/selftests/kvm/lib/ucall_common.c +8 -0
index 029ce21f9f2f..c03dfc763172 100644
@@ -27,6 +27,7 @@ static struct ucall_header *ucall_pool;
 
 void ucall_init(struct kvm_vm *vm, gpa_t mmio_gpa)
 {
+	struct userspace_mem_region *region;
 	struct ucall_header *hdr;
 	struct ucall *uc;
 	gva_t gva;
@@ -37,6 +38,13 @@ void ucall_init(struct kvm_vm *vm, gpa_t mmio_gpa)
 	hdr = (struct ucall_header *)addr_gva2hva(vm, gva);
 	memset(hdr, 0, sizeof(*hdr));
 
+	region = vm_get_mem_region(vm, MEM_REGION_DATA);
+	if (region->region.flags & KVM_MEM_GUEST_MEMFD) {
+		size_t aligned_sz = align_up(sizeof(*hdr), vm->page_size);
+
+		vm_mem_set_shared(vm, addr_gva2gpa(vm, gva), aligned_sz);
+	}
+
 	for (i = 0; i < KVM_MAX_VCPUS; ++i) {
 		uc = &hdr->ucalls[i];
 		uc->hva = uc;
#49 lore raw

Re: [PATCH v14 12/22] KVM: selftests: Require guest_memfd for TDX VMs

In reply to Lisa Wang
Quoted 17 lines · Lisa Wang

Force guest_memfd for TDX VMs.

KVM supports guest_memfd for TDX private memory. Without in-place conversion supports, all pages default attributes are private. Thus, set page to "shared" for shared pages.

---
  tools/testing/selftests/kvm/lib/kvm_util.c     | 2 +-
  tools/testing/selftests/kvm/lib/ucall_common.c | 8 ++++++++
  2 files changed, 9 insertions(+), 1 deletion(-)
tools/testing/selftests/kvm/lib/kvm_util.c +0 -0
index 0b9d2d383bfa..4a48c4b42095 100644

tools/testing/selftests/kvm/lib/kvm_util.c void kvm_set_files_rlimit(u32 nr_vcpus) · line 475

Quoted 5 lines
tools/testing/selftests/kvm/lib/kvm_util.c +1 -1
@@ -472,7 +472,7 @@ void kvm_set_files_rlimit(u32 nr_vcpus)
  static bool is_guest_memfd_required(struct vm_shape shape)
  {
  #ifdef __x86_64__
-	return shape.type == KVM_X86_SNP_VM;
+	return (shape.type == KVM_X86_SNP_VM || shape.type == KVM_X86_TDX_VM);

Instead of updating is_guest_memfd_required(), I'm thinking how about just replacing is_guest_memfd_required() with vm_arch_has_protected_memory()?

Quoted 16 lines
tools/testing/selftests/kvm/lib/kvm_util.c +0 -0
  #else
  	return false;
  #endif
tools/testing/selftests/kvm/lib/ucall_common.c +1 -0
index 029ce21f9f2f..c03dfc763172 100644
@@ -27,6 +27,7 @@ static struct ucall_header *ucall_pool;
 
  void ucall_init(struct kvm_vm *vm, gpa_t mmio_gpa)
  {
+	struct userspace_mem_region *region;
  	struct ucall_header *hdr;
  	struct ucall *uc;
  	gva_t gva;

tools/testing/selftests/kvm/lib/ucall_common.c void ucall_init(struct kvm_vm *vm, gpa_t mmio_gpa) · line 46

Quoted 9 lines
tools/testing/selftests/kvm/lib/ucall_common.c +6 -0
@@ -37,6 +38,13 @@ void ucall_init(struct kvm_vm *vm, gpa_t mmio_gpa)
  	hdr = (struct ucall_header *)addr_gva2hva(vm, gva);
  	memset(hdr, 0, sizeof(*hdr));
 
+	region = vm_get_mem_region(vm, MEM_REGION_DATA);
+	if (region->region.flags & KVM_MEM_GUEST_MEMFD) {
+		size_t aligned_sz = align_up(sizeof(*hdr), vm->page_size);
+
+		vm_mem_set_shared(vm, addr_gva2gpa(vm, gva), aligned_sz);
+	}

this diff about the ucall needs to be a separate patch. What's the reason to add it to this patch in this version?

Quoted 4 lines
tools/testing/selftests/kvm/lib/ucall_common.c +0 -0
  	for (i = 0; i < KVM_MAX_VCPUS; ++i) {
  		uc = &hdr->ucalls[i];
  		uc->hva = uc;
author
#50 lore raw

Re: [PATCH v14 12/22] KVM: selftests: Require guest_memfd for TDX VMs

Quoted 8 lines · Xiaoyao Li

tools/testing/selftests/kvm/lib/kvm_util.c void kvm_set_files_rlimit(u32 nr_vcpus) · line 475

Quoted 5 lines
  static bool is_guest_memfd_required(struct vm_shape shape)
  {
  #ifdef __x86_64__
tools/testing/selftests/kvm/lib/kvm_util.c +1 -1
-	return shape.type == KVM_X86_SNP_VM;
+	return (shape.type == KVM_X86_SNP_VM || shape.type == KVM_X86_TDX_VM);

Instead of updating is_guest_memfd_required(), I'm thinking how about just replacing is_guest_memfd_required() with vm_arch_has_protected_memory()?

vm_arch_has_protected_memory() returns true for SEV, but an SEV VM is expected not to use guest_memfd. If we replace is_guest_memfd_required() with vm_arch_has_protected_memory(), we need an extra test to ensure SEV VMs work well without guest_memfd.

Thus, I prefer keeping is_guest_memfd_required().

Quoted 11 lines
Quoted 11 lines
tools/testing/selftests/kvm/lib/ucall_common.c +2 -0
@@ -27,6 +27,7 @@ static struct ucall_header *ucall_pool;
  void ucall_init(struct kvm_vm *vm, gpa_t mmio_gpa)
  {
+	struct userspace_mem_region *region;
  	struct ucall_header *hdr;
  	struct ucall *uc;
  	gva_t gva;
@@ -37,6 +38,13 @@ void ucall_init(struct kvm_vm *vm, gpa_t mmio_gpa)
  	hdr = (struct ucall_header *)addr_gva2hva(vm, gva);
  	memset(hdr, 0, sizeof(*hdr));
+	region = vm_get_mem_region(vm, MEM_REGION_DATA);
Quoted 8 lines

tools/testing/selftests/kvm/lib/ucall_common.c void ucall_init(struct kvm_vm *vm, gpa_t mmio_gpa) · line 46

Quoted 5 lines
tools/testing/selftests/kvm/lib/ucall_common.c +5 -0
+	if (region->region.flags & KVM_MEM_GUEST_MEMFD) {
+		size_t aligned_sz = align_up(sizeof(*hdr), vm->page_size);
+
+		vm_mem_set_shared(vm, addr_gva2gpa(vm, gva), aligned_sz);
+	}

this diff about the ucall needs to be a separate patch. What's the reason to add it to this patch in this version?

Thanks. Will separate in the next version.

Quoted 5 lines
Quoted 4 lines
tools/testing/selftests/kvm/lib/ucall_common.c +0 -0
  	for (i = 0; i < KVM_MAX_VCPUS; ++i) {
  		uc = &hdr->ucalls[i];
  		uc->hva = uc;
 
author
#51 lore raw

PATCH v14 13/22 KVM: selftests: Support guest_memfd in-place conversion

In reply to Lisa Wang

Set the memory region as shared if guest_memfd in-place conversion is support, so that the KVM selftest framework functions can populate mmap()-ed guest_memfd memory the same way memory from other memory providers are populated.

For CoCo VMs, pages that need to be private are explicitly set to private before executing the VM.

---
 tools/testing/selftests/kvm/lib/kvm_util.c         | 20 ++++++++++++++------
 tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c |  8 ++++++--
 2 files changed, 20 insertions(+), 8 deletions(-)
tools/testing/selftests/kvm/lib/kvm_util.c +14 -6
index 4a48c4b42095..e547ea341ff7 100644
@@ -484,8 +484,10 @@ struct kvm_vm *__vm_create(struct vm_shape shape, u32 nr_runnable_vcpus,
 	u64 nr_pages = vm_nr_pages_required(shape.mode, nr_runnable_vcpus,
 						 nr_extra_pages);
 	struct userspace_mem_region *slot0;
+	u64 gmem_flags = 0;
 	struct kvm_vm *vm;
-	int i, flags;
+	int flags = 0;
+	int i;
 
 	kvm_set_files_rlimit(nr_runnable_vcpus);
 
@@ -495,14 +497,20 @@ struct kvm_vm *__vm_create(struct vm_shape shape, u32 nr_runnable_vcpus,
 	vm = ____vm_create(shape);
 
 	/*
-	 * Force GUEST_MEMFD for the primary memory region if necessary, e.g.
-	 * for CoCo VMs that require GUEST_MEMFD backed private memory.
+	 * Force GUEST_MEMFD for the primary memory region if necessary, and
+	 * initialize it as shared if in-place conversion is support, so the
+	 * selftest framework can populate it exactly like other memory
+	 * providers.
 	 */
-	flags = 0;
-	if (is_guest_memfd_required(shape))
+	if (is_guest_memfd_required(shape)) {
 		flags |= KVM_MEM_GUEST_MEMFD;
+		if (kvm_has_gmem_attributes)
+			gmem_flags |= GUEST_MEMFD_FLAG_INIT_SHARED |
+				      GUEST_MEMFD_FLAG_MMAP;
+	}
 
-	vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, 0, 0, nr_pages, flags);
+	vm_mem_add(vm, VM_MEM_SRC_ANONYMOUS, 0, 0, nr_pages, flags,
+		   -1, 0, gmem_flags);
 	for (i = 0; i < NR_MEM_REGIONS; i++)
 		vm->memslots[i] = 0;
 
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +6 -2
index 2ce8c297f7ed..5bbe51ad0f66 100644
@@ -17,10 +17,12 @@ void tdx_vm_setup_boot_code_region(struct kvm_vm *vm)
 	gpa_t boot_code_gpa = X86_RESET_VECTOR - TD_BOOT_CODE_SIZE;
 	gpa_t alloc_gpa = round_down(boot_code_gpa, PAGE_SIZE);
 	size_t nr_pages = DIV_ROUND_UP(total_code_size, PAGE_SIZE);
-	const u64 gmem_flags = 0;
+	u64 gmem_flags = 0;
 	gpa_t gpa;
 	u8 *hva;
 
+	if (kvm_has_gmem_attributes)
+		gmem_flags |= GUEST_MEMFD_FLAG_INIT_SHARED | GUEST_MEMFD_FLAG_MMAP;
 	vm_mem_add(vm, VM_MEM_SRC_SHMEM, alloc_gpa, TD_BOOT_CODE_SLOT,
 		   nr_pages, KVM_MEM_GUEST_MEMFD, -1, 0, gmem_flags);
 
@@ -56,9 +58,11 @@ void tdx_vm_setup_boot_parameters_region(struct kvm_vm *vm, u32 nr_runnable_vcpu
 		sizeof(struct td_boot_parameters) +
 		nr_runnable_vcpus * sizeof(struct td_per_vcpu_parameters);
 	int npages = DIV_ROUND_UP(boot_params_size, PAGE_SIZE);
-	const u64 gmem_flags = 0;
+	u64 gmem_flags = 0;
 	gpa_t gpa;
 
+	if (kvm_has_gmem_attributes)
+		gmem_flags |= GUEST_MEMFD_FLAG_INIT_SHARED | GUEST_MEMFD_FLAG_MMAP;
 	vm_mem_add(vm, VM_MEM_SRC_SHMEM, TD_BOOT_PARAMETERS_GPA,
 		   TD_BOOT_PARAMETERS_SLOT, npages,
 		   KVM_MEM_GUEST_MEMFD, -1, 0, gmem_flags);
Signature · 1 line
2.55.0.229.g6434b31f56-goog
#52 lore raw

Re: [PATCH v14 13/22] KVM: selftests: Support guest_memfd in-place conversion

Quoted 31 lines · Lisa Wang
tools/testing/selftests/kvm/lib/kvm_util.c +8 -5
index 4a48c4b42095..e547ea341ff7 100644
@@ -484,8 +484,10 @@ struct kvm_vm *__vm_create(struct vm_shape shape, u32 nr_runnable_vcpus,
  	u64 nr_pages = vm_nr_pages_required(shape.mode, nr_runnable_vcpus,
  						 nr_extra_pages);
  	struct userspace_mem_region *slot0;
+	u64 gmem_flags = 0;
  	struct kvm_vm *vm;
-	int i, flags;
+	int flags = 0;
+	int i;
 
  	kvm_set_files_rlimit(nr_runnable_vcpus);
 
@@ -495,14 +497,20 @@ struct kvm_vm *__vm_create(struct vm_shape shape, u32 nr_runnable_vcpus,
  	vm = ____vm_create(shape);
 
  	/*
-	 * Force GUEST_MEMFD for the primary memory region if necessary, e.g.
-	 * for CoCo VMs that require GUEST_MEMFD backed private memory.
+	 * Force GUEST_MEMFD for the primary memory region if necessary, and
+	 * initialize it as shared if in-place conversion is support, so the
+	 * selftest framework can populate it exactly like other memory
+	 * providers.
  	 */
-	flags = 0;
-	if (is_guest_memfd_required(shape))
+	if (is_guest_memfd_required(shape)) {
  		flags |= KVM_MEM_GUEST_MEMFD;

tools/testing/selftests/kvm/lib/kvm_util.c struct kvm_vm *__vm_create(struct vm_shape shape, u32 nr_runnable_vcpus, · line 515

Quoted 10 lines
tools/testing/selftests/kvm/lib/kvm_util.c +6 -1
+		if (kvm_has_gmem_attributes)
+			gmem_flags |= GUEST_MEMFD_FLAG_INIT_SHARED |
+				      GUEST_MEMFD_FLAG_MMAP;
+	}
 
-	vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, 0, 0, nr_pages, flags);
+	vm_mem_add(vm, VM_MEM_SRC_ANONYMOUS, 0, 0, nr_pages, flags,
+		   -1, 0, gmem_flags);
  	for (i = 0; i < NR_MEM_REGIONS; i++)
  		vm->memslots[i] = 0;

IMO, this diff belongs to the gmem in-place series.

#53 lore raw

Re: [PATCH v14 13/22] KVM: selftests: Support guest_memfd in-place conversion

Quoted 13 lines · Xiaoyao Li

tools/testing/selftests/kvm/lib/kvm_util.c struct kvm_vm *__vm_create(struct vm_shape shape, u32 nr_runnable_vcpus, · line 489

Quoted 12 lines · Lisa Wang
tools/testing/selftests/kvm/lib/kvm_util.c +2 -1
index 4a48c4b42095..e547ea341ff7 100644
@@ -484,8 +484,10 @@ struct kvm_vm *__vm_create(struct vm_shape shape, u32 nr_runnable_vcpus,
  	u64 nr_pages = vm_nr_pages_required(shape.mode, nr_runnable_vcpus,
  						 nr_extra_pages);
  	struct userspace_mem_region *slot0;
+	u64 gmem_flags = 0;
  	struct kvm_vm *vm;
-	int i, flags;
+	int flags = 0;

Don't make unnecessary changes. It's not just a "nice to have" thing, e.g. in this case this results in extra conflicts with other changes.

Quoted 4 lines
Quoted 4 lines
tools/testing/selftests/kvm/lib/kvm_util.c +1 -0
+	int i;
  	kvm_set_files_rlimit(nr_runnable_vcpus);
@@ -495,14 +497,20 @@ struct kvm_vm *__vm_create(struct vm_shape shape, u32 nr_runnable_vcpus,
  	vm = ____vm_create(shape);
Quoted 8 lines

tools/testing/selftests/kvm/lib/kvm_util.c struct kvm_vm *__vm_create(struct vm_shape shape, u32 nr_runnable_vcpus, · line 503

Quoted 8 lines
tools/testing/selftests/kvm/lib/kvm_util.c +4 -2
  	/*
-	 * Force GUEST_MEMFD for the primary memory region if necessary, e.g.
-	 * for CoCo VMs that require GUEST_MEMFD backed private memory.
+	 * Force GUEST_MEMFD for the primary memory region if necessary, and
+	 * initialize it as shared if in-place conversion is support, so the
+	 * selftest framework can populate it exactly like other memory
+	 * providers.
  	 */

Leave the existing comment as is. The new comment should be anchored to the code below.

Quoted 7 lines
Quoted 7 lines
tools/testing/selftests/kvm/lib/kvm_util.c +4 -2
-	flags = 0;
-	if (is_guest_memfd_required(shape))
+	if (is_guest_memfd_required(shape)) {
  		flags |= KVM_MEM_GUEST_MEMFD;
+		if (kvm_has_gmem_attributes)
+			gmem_flags |= GUEST_MEMFD_FLAG_INIT_SHARED |
+				      GUEST_MEMFD_FLAG_MMAP;
Quoted 8 lines

tools/testing/selftests/kvm/lib/kvm_util.c struct kvm_vm *__vm_create(struct vm_shape shape, u32 nr_runnable_vcpus, · line 513

Quoted 6 lines
tools/testing/selftests/kvm/lib/kvm_util.c +3 -1
+	}
-	vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, 0, 0, nr_pages, flags);
+	vm_mem_add(vm, VM_MEM_SRC_ANONYMOUS, 0, 0, nr_pages, flags,
+		   -1, 0, gmem_flags);
  	for (i = 0; i < NR_MEM_REGIONS; i++)
  		vm->memslots[i] = 0;

IMO, this diff belongs to the gmem in-place series.

Yep, you're 100% right. I pushed this to "coco" for now...

testing/selftests/kvm/lib/kvm_util.c +13 -2
index 3f559f207408..0c67d964a9aa 100644
@@ -494,6 +494,7 @@ struct kvm_vm *__vm_create(struct vm_shape shape, u32 nr_runnable_vcpus,
        u64 nr_pages = vm_nr_pages_required(shape.mode, nr_runnable_vcpus,
                                                 nr_extra_pages);
        struct userspace_mem_region *slot0;
+       u64 gmem_flags = 0;
        struct kvm_vm *vm;
        int i, flags;
 
@@ -509,10 +510,20 @@ struct kvm_vm *__vm_create(struct vm_shape shape, u32 nr_runnable_vcpus,
         * for CoCo VMs that require GUEST_MEMFD backed private memory.
         */
        flags = 0;
-       if (is_guest_memfd_required(shape))
+       if (is_guest_memfd_required(shape)) {
                flags |= KVM_MEM_GUEST_MEMFD;
 
-       vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, 0, 0, nr_pages, flags);
+               /*
+                * Allow mmap() and initialize the gmem instance as SHARED if
+                * in-place conversions are supported, so that the memory can
+                * be populated/initialized like any other memory region.
+                */
+               if (kvm_has_gmem_attributes)
+                       gmem_flags |= GUEST_MEMFD_FLAG_INIT_SHARED |
+                                     GUEST_MEMFD_FLAG_MMAP;
+       }
+
+       vm_mem_add(vm, VM_MEM_SRC_ANONYMOUS, 0, 0, nr_pages, flags, -1, 0, gmem_flags);
        for (i = 0; i < NR_MEM_REGIONS; i++)
                vm->memslots[i] = 0;
 

but the entire in-place conversion series may need to be dropped because there's a potential deadlock, and I don't see an obviously non-awful way to fix it.

[ 2260.466972] ======================================================
[ 2260.473150] WARNING: possible circular locking dependency detected
[ 2260.479329] 7.3.0-smp--ce115a0096358-seanjc-soink #75 Tainted: G     U     O       
[ 2260.486981] ------------------------------------------------------
[ 2260.493161] sev_snp_vmsa_pu/24505 is trying to acquire lock:
[ 2260.498821] ffffffffc01f7900 (sev_cmd_mutex){+.+.}-{4:4}, at: sev_issue_cmd_external_user+0x39/0x60 [ccp]
[ 2260.508387] 
               but task is already holding lock:
[ 2260.514213] ffff93c9dd1cf070 (mapping.invalidate_lock#4){++++}-{4:4}, at: kvm_gmem_populate+0x18c/0x300 [kvm]
[ 2260.524275] 
               which lock already depends on the new lock.
[ 2260.532446] 
               the existing dependency chain (in reverse order) is:
[ 2260.539917] 
               -> #2 (mapping.invalidate_lock#4){++++}-{4:4}:
[ 2260.546878]        down_read+0x3d/0x160
[ 2260.550716]        kvm_gmem_fault_user_mapping+0x43/0x180 [kvm]
[ 2260.556663]        __do_fault+0x3f/0x110
[ 2260.560588]        do_pte_missing+0x515/0x1000
[ 2260.565035]        handle_mm_fault+0x7f2/0x1290
[ 2260.569567]        do_user_addr_fault+0x47d/0x6c0
[ 2260.574271]        exc_page_fault+0x67/0xa0
[ 2260.578458]        asm_exc_page_fault+0x26/0x30
[ 2260.582991] 
               -> #1 (&mm->mmap_lock){++++}-{4:4}:
[ 2260.588997]        __might_fault+0x59/0x80
[ 2260.593096]        _copy_to_user+0x23/0x60
[ 2260.597197]        sev_ioctl_do_platform_status+0x50/0x80 [ccp]
[ 2260.603116]        sev_ioctl+0x220/0x2b0 [ccp]
[ 2260.607560]        __se_sys_ioctl+0x6d/0xb0
[ 2260.611749]        do_syscall_64+0xfe/0x460
[ 2260.615933]        entry_SYSCALL_64_after_hwframe+0x4b/0x53
[ 2260.621506] 
               -> #0 (sev_cmd_mutex){+.+.}-{4:4}:
[ 2260.627426]        __lock_acquire+0x160d/0x2940
[ 2260.631957]        lock_acquire+0xf1/0x300
[ 2260.636050]        __mutex_lock+0x8f/0xdf0
[ 2260.640147]        sev_issue_cmd_external_user+0x39/0x60 [ccp]
[ 2260.645982]        sev_gmem_post_populate+0x189/0x230 [kvm_amd]
[ 2260.651901]        kvm_gmem_populate+0x1ed/0x300 [kvm]
[ 2260.657066]        snp_launch_update+0x1a7/0x250 [kvm_amd]
[ 2260.662551]        sev_mem_enc_ioctl+0x371/0x3d0 [kvm_amd]
[ 2260.668036]        kvm_vm_ioctl+0x52c/0x580 [kvm]
[ 2260.672761]        __se_sys_ioctl+0x6d/0xb0
[ 2260.676946]        do_syscall_64+0xfe/0x460
[ 2260.681133]        entry_SYSCALL_64_after_hwframe+0x4b/0x53
[ 2260.686706] 
               other info that might help us debug this:
[ 2260.694695] Chain exists of:
                 sev_cmd_mutex --> &mm->mmap_lock --> mapping.invalidate_lock#4
[ 2260.706077]  Possible unsafe locking scenario:
[ 2260.711993]        CPU0                    CPU1
[ 2260.716520]        ----                    ----
[ 2260.721052]   lock(mapping.invalidate_lock#4);
[ 2260.725498]                                lock(&mm->mmap_lock);
[ 2260.731503]                                lock(mapping.invalidate_lock#4);
[ 2260.738463]   lock(sev_cmd_mutex);
[ 2260.741868] 
                *** DEADLOCK ***
[ 2260.747778] locks held by sev_snp_vmsa_pu/24505: 3, last CPU#35:
[ 2260.753785]  #0: ffff93c9d54bd3a8 (&kvm->lock){+.+.}-{4:4}, at: sev_mem_enc_ioctl+0x73/0x3d0 [kvm_amd]
[ 2260.763083]  #1: ffff93c9d54bc0a0 (&kvm->slots_lock){+.+.}-{4:4}, at: snp_launch_update+0x145/0x250 [kvm_amd]
[ 2260.772991]  #2: ffff93c9dd1cf070 (mapping.invalidate_lock#4){++++}-{4:4}, at: kvm_gmem_populate+0x18c/0x300 [kvm]
[ 2260.783356] 
               stack backtrace:
[ 2260.787718] CPU: 35 UID: 0 PID: 24505 Comm: sev_snp_vmsa_pu Tainted: G     U     O        7.3.0-smp--ce115a0096358-seanjc-soink #75 PREEMPT 
[ 2260.800310] Tainted: [U]=USER, [O]=OOT_MODULE
[ 2260.804668] Hardware name: Google, Inc.                                                       Arcadia_IT_80/Arcadia_IT_80, BIOS 34.86.0-102 01/25/2026
[ 2260.818127] Call Trace:
[ 2260.820579]  <TASK>
[ 2260.822687]  dump_stack_lvl+0x54/0x70
[ 2260.826351]  print_circular_bug+0x2e4/0x300
[ 2260.830537]  check_noncircular+0xf5/0x120
[ 2260.834553]  __lock_acquire+0x160d/0x2940
[ 2260.838565]  ? __lock_acquire+0x65e/0x2940
[ 2260.842664]  ? rcu_is_watching+0x11/0x40
[ 2260.846587]  ? trace_sched_exit_tp+0x25/0xb0
[ 2260.850861]  lock_acquire+0xf1/0x300
[ 2260.854440]  ? sev_issue_cmd_external_user+0x39/0x60 [ccp]
[ 2260.859928]  ? lock_is_held_type+0xc7/0x120
[ 2260.864112]  ? sev_issue_cmd_external_user+0x39/0x60 [ccp]
[ 2260.869599]  __mutex_lock+0x8f/0xdf0
[ 2260.873178]  ? sev_issue_cmd_external_user+0x39/0x60 [ccp]
[ 2260.878663]  ? pfn_valid+0x89/0x220
[ 2260.882155]  ? pfn_valid+0x1fb/0x220
[ 2260.885736]  ? lookup_address+0x49/0x70
[ 2260.889577]  sev_issue_cmd_external_user+0x39/0x60 [ccp]
[ 2260.894890]  sev_gmem_post_populate+0x189/0x230 [kvm_amd]
[ 2260.900289]  kvm_gmem_populate+0x1ed/0x300 [kvm]
[ 2260.904938]  ? snp_bind_asid+0xd0/0xd0 [kvm_amd]
[ 2260.909561]  snp_launch_update+0x1a7/0x250 [kvm_amd]
[ 2260.914528]  sev_mem_enc_ioctl+0x371/0x3d0 [kvm_amd]
[ 2260.919493]  kvm_vm_ioctl+0x52c/0x580 [kvm]
[ 2260.923706]  ? kvm_gmem_ioctl+0x192/0x1e0 [kvm]
[ 2260.928266]  __se_sys_ioctl+0x6d/0xb0
[ 2260.931930]  do_syscall_64+0xfe/0x460
[ 2260.935595]  ? entry_SYSCALL_64_after_hwframe+0x4b/0x53
[ 2260.940822]  ? trace_irq_disable+0x25/0xc0
[ 2260.944921]  entry_SYSCALL_64_after_hwframe+0x4b/0x53
[ 2260.949972] RIP: 0033:0x474bdb
[ 2260.953031] Code: 00 48 89 44 24 18 31 c0 48 8d 44 24 60 c7 04 24 10 00 00 00 48 89 44 24 08 48 8d 44 24 20 48 89 44 24 10 b8 10 00 00 00 0f 05 <89> c2 3d 00 f0 ff ff 77 1c 48 8b 44 24 18 64 48 2b 04 25 28 00 00
[ 2260.971770] RSP: 002b:00007ffd188ac3f0 EFLAGS: 00000246 ORIG_RAX: 0000000000000010
[ 2260.979337] RAX: ffffffffffffffda RBX: 0000000028691b30 RCX: 0000000000474bdb
[ 2260.986468] RDX: 00007ffd188ac4a0 RSI: 00000000c008aeba RDI: 0000000000000007
[ 2260.993600] RBP: 0000000000135000 R08: 00000000004d50b8 R09: 00000000004e1b78
[ 2261.000734] R10: 0000000000000002 R11: 0000000000000246 R12: 00007ffd188ac4c0
[ 2261.007867] R13: 0000000000137000 R14: 0000000028693db0 R15: 0000000000137000
[ 2261.015001]  </TASK>
#54 lore raw

Re: [PATCH v14 13/22] KVM: selftests: Support guest_memfd in-place conversion

+Ashish, Tom and Mike, dropped a bunch of others that probably don't care about the CCP/PSP driver.

Quoted 55 lines · Sean Christopherson
Quoted 16 lines · Xiaoyao Li

Review

Quoted 13 lines · Lisa Wang
(quoted diff) +7 -3
-	flags = 0;
-	if (is_guest_memfd_required(shape))
+	if (is_guest_memfd_required(shape)) {
  		flags |= KVM_MEM_GUEST_MEMFD;
+		if (kvm_has_gmem_attributes)
+			gmem_flags |= GUEST_MEMFD_FLAG_INIT_SHARED |
+				      GUEST_MEMFD_FLAG_MMAP;
+	}
-	vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, 0, 0, nr_pages, flags);
+	vm_mem_add(vm, VM_MEM_SRC_ANONYMOUS, 0, 0, nr_pages, flags,
+		   -1, 0, gmem_flags);
  	for (i = 0; i < NR_MEM_REGIONS; i++)
  		vm->memslots[i] = 0;

IMO, this diff belongs to the gmem in-place series.

Yep, you're 100% right. I pushed this to "coco" for now...

testing/selftests/kvm/lib/kvm_util.c +13 -2
index 3f559f207408..0c67d964a9aa 100644
@@ -494,6 +494,7 @@ struct kvm_vm *__vm_create(struct vm_shape shape, u32 nr_runnable_vcpus,
        u64 nr_pages = vm_nr_pages_required(shape.mode, nr_runnable_vcpus,
                                                 nr_extra_pages);
        struct userspace_mem_region *slot0;
+       u64 gmem_flags = 0;
        struct kvm_vm *vm;
        int i, flags;
 
@@ -509,10 +510,20 @@ struct kvm_vm *__vm_create(struct vm_shape shape, u32 nr_runnable_vcpus,
         * for CoCo VMs that require GUEST_MEMFD backed private memory.
         */
        flags = 0;
-       if (is_guest_memfd_required(shape))
+       if (is_guest_memfd_required(shape)) {
                flags |= KVM_MEM_GUEST_MEMFD;
 
-       vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, 0, 0, nr_pages, flags);
+               /*
+                * Allow mmap() and initialize the gmem instance as SHARED if
+                * in-place conversions are supported, so that the memory can
+                * be populated/initialized like any other memory region.
+                */
+               if (kvm_has_gmem_attributes)
+                       gmem_flags |= GUEST_MEMFD_FLAG_INIT_SHARED |
+                                     GUEST_MEMFD_FLAG_MMAP;
+       }
+
+       vm_mem_add(vm, VM_MEM_SRC_ANONYMOUS, 0, 0, nr_pages, flags, -1, 0, gmem_flags);
        for (i = 0; i < NR_MEM_REGIONS; i++)
                vm->memslots[i] = 0;
 
Quoted 2 lines

but the entire in-place conversion series may need to be dropped because there's a potential deadlock, and I don't see an obviously non-awful way to fix it.

The underlying problem is that drivers/crypto/ccp/sev-dev.c does memory allocations and copy_{to,from}_user() while holding sev_cmd_mutex in a variety of locations.

I genuinely think the right fix is to not do that, i.e. to perform invasive surgery on the driver so that it holds sev_cmd_mutex only for the operations that really truly need its protection. Because taking page faults while holding a *system*-wide lock is asking for trouble. Deadlock aside, if one of the user pages being accessed happens to be swapped out, tasks could be blocked on sev_cmd_mutex for quite some time waiting for the page to come back from disk or wherever. Rinse and repeat, and I wouldn't be surprised if VM creation/boot can stall for hundreds of milliseconds or worse.

The alternative would be to take sev_cmd_mutex around the kvm_gmem_populate() call in snp_launch_update(), but even if the CPP maintainers were willing to provide hooks to allow that, I'm not willing to take such a hack in KVM.

So, very sadly, I think in-place conversion is dead for 7.4, and will need to be revived on top of an overhaul of sev_cmd_mutex locking.

Or I guess maybe a less painful temporary workaround would be to disable support for SNP if in-place conversion is enabled. That would avoid extending the in-place conversion bottleneck for TDX, pKVM, and CCA, without having to take a gross hack to make SNP play nice.

E.g. this as a workaround for 7.4 (not yet tested)

arch/x86/kvm/svm/sev.c +8 -0
index fad72cab76cf..df885b8214d4 100644
@@ -3213,6 +3213,14 @@ void __init sev_hardware_setup(void)
 	sev_snp_supported = sev_snp_enabled && cc_platform_has(CC_ATTR_HOST_SEV_SNP);
 
 out:
+	/*
+	 * FIXME: Allow SNP with in-place guest_memfd conversion once the PSP
+	 * driver no longer takes its global mutex outside of mmap_lock (which
+	 * leads to deadlock with sev_gmem_post_populate()).
+	 */
+	if (gmem_in_place_conversion)
+		sev_snp_supported = false;
+
 	if (sev_enabled) {
 		init_args.probe = true;
 
Quoted 114 lines
[ 2260.466972] ======================================================
[ 2260.473150] WARNING: possible circular locking dependency detected
[ 2260.479329] 7.3.0-smp--ce115a0096358-seanjc-soink #75 Tainted: G     U     O       
[ 2260.486981] ------------------------------------------------------
[ 2260.493161] sev_snp_vmsa_pu/24505 is trying to acquire lock:
[ 2260.498821] ffffffffc01f7900 (sev_cmd_mutex){+.+.}-{4:4}, at: sev_issue_cmd_external_user+0x39/0x60 [ccp]
[ 2260.508387] 
               but task is already holding lock:
[ 2260.514213] ffff93c9dd1cf070 (mapping.invalidate_lock#4){++++}-{4:4}, at: kvm_gmem_populate+0x18c/0x300 [kvm]
[ 2260.524275] 
               which lock already depends on the new lock.
[ 2260.532446] 
               the existing dependency chain (in reverse order) is:
[ 2260.539917] 
               -> #2 (mapping.invalidate_lock#4){++++}-{4:4}:
[ 2260.546878]        down_read+0x3d/0x160
[ 2260.550716]        kvm_gmem_fault_user_mapping+0x43/0x180 [kvm]
[ 2260.556663]        __do_fault+0x3f/0x110
[ 2260.560588]        do_pte_missing+0x515/0x1000
[ 2260.565035]        handle_mm_fault+0x7f2/0x1290
[ 2260.569567]        do_user_addr_fault+0x47d/0x6c0
[ 2260.574271]        exc_page_fault+0x67/0xa0
[ 2260.578458]        asm_exc_page_fault+0x26/0x30
[ 2260.582991] 
               -> #1 (&mm->mmap_lock){++++}-{4:4}:
[ 2260.588997]        __might_fault+0x59/0x80
[ 2260.593096]        _copy_to_user+0x23/0x60
[ 2260.597197]        sev_ioctl_do_platform_status+0x50/0x80 [ccp]
[ 2260.603116]        sev_ioctl+0x220/0x2b0 [ccp]
[ 2260.607560]        __se_sys_ioctl+0x6d/0xb0
[ 2260.611749]        do_syscall_64+0xfe/0x460
[ 2260.615933]        entry_SYSCALL_64_after_hwframe+0x4b/0x53
[ 2260.621506] 
               -> #0 (sev_cmd_mutex){+.+.}-{4:4}:
[ 2260.627426]        __lock_acquire+0x160d/0x2940
[ 2260.631957]        lock_acquire+0xf1/0x300
[ 2260.636050]        __mutex_lock+0x8f/0xdf0
[ 2260.640147]        sev_issue_cmd_external_user+0x39/0x60 [ccp]
[ 2260.645982]        sev_gmem_post_populate+0x189/0x230 [kvm_amd]
[ 2260.651901]        kvm_gmem_populate+0x1ed/0x300 [kvm]
[ 2260.657066]        snp_launch_update+0x1a7/0x250 [kvm_amd]
[ 2260.662551]        sev_mem_enc_ioctl+0x371/0x3d0 [kvm_amd]
[ 2260.668036]        kvm_vm_ioctl+0x52c/0x580 [kvm]
[ 2260.672761]        __se_sys_ioctl+0x6d/0xb0
[ 2260.676946]        do_syscall_64+0xfe/0x460
[ 2260.681133]        entry_SYSCALL_64_after_hwframe+0x4b/0x53
[ 2260.686706] 
               other info that might help us debug this:
[ 2260.694695] Chain exists of:
                 sev_cmd_mutex --> &mm->mmap_lock --> mapping.invalidate_lock#4
[ 2260.706077]  Possible unsafe locking scenario:
[ 2260.711993]        CPU0                    CPU1
[ 2260.716520]        ----                    ----
[ 2260.721052]   lock(mapping.invalidate_lock#4);
[ 2260.725498]                                lock(&mm->mmap_lock);
[ 2260.731503]                                lock(mapping.invalidate_lock#4);
[ 2260.738463]   lock(sev_cmd_mutex);
[ 2260.741868] 
                *** DEADLOCK ***
[ 2260.747778] locks held by sev_snp_vmsa_pu/24505: 3, last CPU#35:
[ 2260.753785]  #0: ffff93c9d54bd3a8 (&kvm->lock){+.+.}-{4:4}, at: sev_mem_enc_ioctl+0x73/0x3d0 [kvm_amd]
[ 2260.763083]  #1: ffff93c9d54bc0a0 (&kvm->slots_lock){+.+.}-{4:4}, at: snp_launch_update+0x145/0x250 [kvm_amd]
[ 2260.772991]  #2: ffff93c9dd1cf070 (mapping.invalidate_lock#4){++++}-{4:4}, at: kvm_gmem_populate+0x18c/0x300 [kvm]
[ 2260.783356] 
               stack backtrace:
[ 2260.787718] CPU: 35 UID: 0 PID: 24505 Comm: sev_snp_vmsa_pu Tainted: G     U     O        7.3.0-smp--ce115a0096358-seanjc-soink #75 PREEMPT 
[ 2260.800310] Tainted: [U]=USER, [O]=OOT_MODULE
[ 2260.804668] Hardware name: Google, Inc.                                                       Arcadia_IT_80/Arcadia_IT_80, BIOS 34.86.0-102 01/25/2026
[ 2260.818127] Call Trace:
[ 2260.820579]  <TASK>
[ 2260.822687]  dump_stack_lvl+0x54/0x70
[ 2260.826351]  print_circular_bug+0x2e4/0x300
[ 2260.830537]  check_noncircular+0xf5/0x120
[ 2260.834553]  __lock_acquire+0x160d/0x2940
[ 2260.838565]  ? __lock_acquire+0x65e/0x2940
[ 2260.842664]  ? rcu_is_watching+0x11/0x40
[ 2260.846587]  ? trace_sched_exit_tp+0x25/0xb0
[ 2260.850861]  lock_acquire+0xf1/0x300
[ 2260.854440]  ? sev_issue_cmd_external_user+0x39/0x60 [ccp]
[ 2260.859928]  ? lock_is_held_type+0xc7/0x120
[ 2260.864112]  ? sev_issue_cmd_external_user+0x39/0x60 [ccp]
[ 2260.869599]  __mutex_lock+0x8f/0xdf0
[ 2260.873178]  ? sev_issue_cmd_external_user+0x39/0x60 [ccp]
[ 2260.878663]  ? pfn_valid+0x89/0x220
[ 2260.882155]  ? pfn_valid+0x1fb/0x220
[ 2260.885736]  ? lookup_address+0x49/0x70
[ 2260.889577]  sev_issue_cmd_external_user+0x39/0x60 [ccp]
[ 2260.894890]  sev_gmem_post_populate+0x189/0x230 [kvm_amd]
[ 2260.900289]  kvm_gmem_populate+0x1ed/0x300 [kvm]
[ 2260.904938]  ? snp_bind_asid+0xd0/0xd0 [kvm_amd]
[ 2260.909561]  snp_launch_update+0x1a7/0x250 [kvm_amd]
[ 2260.914528]  sev_mem_enc_ioctl+0x371/0x3d0 [kvm_amd]
[ 2260.919493]  kvm_vm_ioctl+0x52c/0x580 [kvm]
[ 2260.923706]  ? kvm_gmem_ioctl+0x192/0x1e0 [kvm]
[ 2260.928266]  __se_sys_ioctl+0x6d/0xb0
[ 2260.931930]  do_syscall_64+0xfe/0x460
[ 2260.935595]  ? entry_SYSCALL_64_after_hwframe+0x4b/0x53
[ 2260.940822]  ? trace_irq_disable+0x25/0xc0
[ 2260.944921]  entry_SYSCALL_64_after_hwframe+0x4b/0x53
[ 2260.949972] RIP: 0033:0x474bdb
[ 2260.953031] Code: 00 48 89 44 24 18 31 c0 48 8d 44 24 60 c7 04 24 10 00 00 00 48 89 44 24 08 48 8d 44 24 20 48 89 44 24 10 b8 10 00 00 00 0f 05 <89> c2 3d 00 f0 ff ff 77 1c 48 8b 44 24 18 64 48 2b 04 25 28 00 00
[ 2260.971770] RSP: 002b:00007ffd188ac3f0 EFLAGS: 00000246 ORIG_RAX: 0000000000000010
[ 2260.979337] RAX: ffffffffffffffda RBX: 0000000028691b30 RCX: 0000000000474bdb
[ 2260.986468] RDX: 00007ffd188ac4a0 RSI: 00000000c008aeba RDI: 0000000000000007
[ 2260.993600] RBP: 0000000000135000 R08: 00000000004d50b8 R09: 00000000004e1b78
[ 2261.000734] R10: 0000000000000002 R11: 0000000000000246 R12: 00007ffd188ac4c0
[ 2261.007867] R13: 0000000000137000 R14: 0000000028693db0 R15: 0000000000137000
[ 2261.015001]  </TASK>
#55 lore raw

Re: [PATCH v14 13/22] KVM: selftests: Support guest_memfd in-place conversion

Quoted 65 lines · Sean Christopherson

+Ashish, Tom and Mike, dropped a bunch of others that probably don't care about the CCP/PSP driver.

Quoted 55 lines · Sean Christopherson
Quoted 16 lines · Xiaoyao Li

Review

Quoted 13 lines · Lisa Wang
(quoted diff) +7 -3
-	flags = 0;
-	if (is_guest_memfd_required(shape))
+	if (is_guest_memfd_required(shape)) {
  		flags |= KVM_MEM_GUEST_MEMFD;
+		if (kvm_has_gmem_attributes)
+			gmem_flags |= GUEST_MEMFD_FLAG_INIT_SHARED |
+				      GUEST_MEMFD_FLAG_MMAP;
+	}
-	vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, 0, 0, nr_pages, flags);
+	vm_mem_add(vm, VM_MEM_SRC_ANONYMOUS, 0, 0, nr_pages, flags,
+		   -1, 0, gmem_flags);
  	for (i = 0; i < NR_MEM_REGIONS; i++)
  		vm->memslots[i] = 0;

IMO, this diff belongs to the gmem in-place series.

Yep, you're 100% right. I pushed this to "coco" for now...

testing/selftests/kvm/lib/kvm_util.c +13 -2
index 3f559f207408..0c67d964a9aa 100644
@@ -494,6 +494,7 @@ struct kvm_vm *__vm_create(struct vm_shape shape, u32 nr_runnable_vcpus,
        u64 nr_pages = vm_nr_pages_required(shape.mode, nr_runnable_vcpus,
                                                 nr_extra_pages);
        struct userspace_mem_region *slot0;
+       u64 gmem_flags = 0;
        struct kvm_vm *vm;
        int i, flags;
 
@@ -509,10 +510,20 @@ struct kvm_vm *__vm_create(struct vm_shape shape, u32 nr_runnable_vcpus,
         * for CoCo VMs that require GUEST_MEMFD backed private memory.
         */
        flags = 0;
-       if (is_guest_memfd_required(shape))
+       if (is_guest_memfd_required(shape)) {
                flags |= KVM_MEM_GUEST_MEMFD;
 
-       vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, 0, 0, nr_pages, flags);
+               /*
+                * Allow mmap() and initialize the gmem instance as SHARED if
+                * in-place conversions are supported, so that the memory can
+                * be populated/initialized like any other memory region.
+                */
+               if (kvm_has_gmem_attributes)
+                       gmem_flags |= GUEST_MEMFD_FLAG_INIT_SHARED |
+                                     GUEST_MEMFD_FLAG_MMAP;
+       }
+
+       vm_mem_add(vm, VM_MEM_SRC_ANONYMOUS, 0, 0, nr_pages, flags, -1, 0, gmem_flags);
        for (i = 0; i < NR_MEM_REGIONS; i++)
                vm->memslots[i] = 0;
 
Quoted 2 lines

but the entire in-place conversion series may need to be dropped because there's a potential deadlock, and I don't see an obviously non-awful way to fix it.

The underlying problem is that drivers/crypto/ccp/sev-dev.c does memory allocations and copy_{to,from}_user() while holding sev_cmd_mutex in a variety of locations.

Quoted 8 lines

I genuinely think the right fix is to not do that, i.e. to perform invasive surgery on the driver so that it holds sev_cmd_mutex only for the operations that really truly need its protection. Because taking page faults while holding a *system*-wide lock is asking for trouble. Deadlock aside, if one of the user pages being accessed happens to be swapped out, tasks could be blocked on sev_cmd_mutex for quite some time waiting for the page to come back from disk or wherever. Rinse and repeat, and I wouldn't be surprised if VM creation/boot can stall for hundreds of milliseconds or worse.

Hi Sean,

Thanks for catching this. I had a discussion with Tom and Ashish and we agree that making the CCP driver more granular with it's use of sev_cmd_mutex seems like the best choice.

I've include a patch (below) that reworks the CCP driver to take this approach and did some light regression/build testing. Still need to take a closer look but am planning to post this as a proper patch if the approach seems okay to you. This mainly addresses /dev/sev IOCTLs since KVM/internal users use sev_issue_cmd_external_user() which does not do any copy_to/from_user()'s and is basically a direct firmware call which is the latest point where sev_cmd_mutex is needed.

I don't have a reproducer for the specific ABBA in question though; I'm confused at why sev_ioctl_do_platform_status() / SEV_CMD_PLATFORM_STATUS would trigger a gmem fault (outside of malicious/intentional case) since it is just querying status on behalf of host userspace and storing the result in userspace buffer where there's no reason to use a guest/gmem page. Was this test specifically set up to do this or was this reachable from a normal path? Just trying to see if there are interactions I'm failing to consider here.

Quoted 24 lines

The alternative would be to take sev_cmd_mutex around the kvm_gmem_populate() call in snp_launch_update(), but even if the CPP maintainers were willing to provide hooks to allow that, I'm not willing to take such a hack in KVM.

So, very sadly, I think in-place conversion is dead for 7.4, and will need to be revived on top of an overhaul of sev_cmd_mutex locking.

Or I guess maybe a less painful temporary workaround would be to disable support for SNP if in-place conversion is enabled. That would avoid extending the in-place conversion bottleneck for TDX, pKVM, and CCA, without having to take a gross hack to make SNP play nice.

E.g. this as a workaround for 7.4 (not yet tested)

arch/x86/kvm/svm/sev.c +1 -0
index fad72cab76cf..df885b8214d4 100644
@@ -3213,6 +3213,14 @@ void __init sev_hardware_setup(void)
 	sev_snp_supported = sev_snp_enabled && cc_platform_has(CC_ATTR_HOST_SEV_SNP);
 
 out:
+	/*

arch/x86/kvm/svm/sev.c void __init sev_hardware_setup(void) · line 3226

Quoted 10 lines
arch/x86/kvm/svm/sev.c +7 -0
+	 * FIXME: Allow SNP with in-place guest_memfd conversion once the PSP
+	 * driver no longer takes its global mutex outside of mmap_lock (which
+	 * leads to deadlock with sev_gmem_post_populate()).
+	 */
+	if (gmem_in_place_conversion)
+		sev_snp_supported = false;
+
 	if (sev_enabled) {
 		init_args.probe = true;
 

I would hate to hold everyone else up due to an SNP issue that (hopefully) already has a potential fix. So, if it comes to that and hard decisions need to be made, it would be great if we can just disable SNP specifically. The above approach seems reasonable to me.

Thanks,

Mike

From: Michael Roth <[email protected]>
Date: Mon, 12 Oct 2026 14:32:28 -0500
Subject: [PATCH] crypto: ccp: Don't hold sev_cmd_mutex when copying from/to user pages

(Proper commit message pending)

This can lead to deadlocks and slow performance. Copying to/from userspace pages so handle that outside of the criticial path.

The only bit of awkwardness here is that sev->snp_initialized also relies on the sev_cmd_mutex(), so in that case push the related checks down into the critical path. This currently results in sev_ioctl_do_snp_set_config() potentially doing an unnecessary copy_from_user() before it reports the original ENODEV error in cases, whereas before it would report ENODEV before doing the copy. This can be improved upon based on the fact that sev->snp_initialized can only be set a specific points in time rather than being truly dynamic.

---
 drivers/crypto/ccp/sev-dev.c | 73 ++++++++++++++++++++++++------------
 1 file changed, 49 insertions(+), 24 deletions(-)
drivers/crypto/ccp/sev-dev.c +49 -24
index f833cb7e4da3..ecbc9dfda6e5 100644
@@ -1914,6 +1914,8 @@ static int sev_ioctl_do_reset(struct sev_issue_cmd *argp, bool writable)
 	if (!writable)
 		return -EPERM;
 
+	guard(mutex)(&sev_cmd_mutex);
+
 	/*
 	 * The SEV spec requires that FACTORY_RESET must be issued in
 	 * UNINIT state. Before we go further lets check if any guest is
@@ -1946,7 +1948,8 @@ static int sev_ioctl_do_platform_status(struct sev_issue_cmd *argp)
 
 	memset(&data, 0, sizeof(data));
 
-	ret = __sev_do_cmd_locked(SEV_CMD_PLATFORM_STATUS, &data, &argp->error);
+	scoped_guard(mutex, &sev_cmd_mutex)
+		ret = __sev_do_cmd_locked(SEV_CMD_PLATFORM_STATUS, &data, &argp->error);
 	if (ret)
 		return ret;
 
@@ -1965,6 +1968,8 @@ static int sev_ioctl_do_pek_pdh_gen(int cmd, struct sev_issue_cmd *argp, bool wr
 	if (!writable)
 		return -EPERM;
 
+	guard(mutex)(&sev_cmd_mutex);
+
 	if (sev->sev_plat_status.state == SEV_STATE_UNINIT) {
 		rc = sev_move_to_init_state(argp, &shutdown_required);
 		if (rc)
@@ -2014,14 +2019,23 @@ static int sev_ioctl_do_pek_csr(struct sev_issue_cmd *argp, bool writable)
 	data.len = input.length;
 
 cmd:
+	mutex_lock(&sev_cmd_mutex);
+
 	if (sev->sev_plat_status.state == SEV_STATE_UNINIT) {
 		ret = sev_move_to_init_state(argp, &shutdown_required);
-		if (ret)
+		if (ret) {
+			mutex_unlock(&sev_cmd_mutex);
 			goto e_free_blob;
+		}
 	}
 
 	ret = __sev_do_cmd_locked(SEV_CMD_PEK_CSR, &data, &argp->error);
 
+	if (shutdown_required)
+		__sev_firmware_shutdown(sev, false);
+
+	mutex_unlock(&sev_cmd_mutex);
+
 	/*
 	 * Firmware will returns the length of the CSR blob (either the minimum
 	 * required length or the actual length written), return it to the user.
@@ -2042,9 +2056,6 @@ static int sev_ioctl_do_pek_csr(struct sev_issue_cmd *argp, bool writable)
 	}
 
 e_free_blob:
-	if (shutdown_required)
-		__sev_firmware_shutdown(sev, false);
-
 	kfree(blob);
 	return ret;
 }
@@ -2318,18 +2329,22 @@ static int sev_ioctl_do_pek_import(struct sev_issue_cmd *argp, bool writable)
 	data.oca_cert_len = input.oca_cert_len;
 
 	/* If platform is not in INIT state then transition it to INIT */
+	mutex_lock(&sev_cmd_mutex);
+
 	if (sev->sev_plat_status.state != SEV_STATE_INIT) {
 		ret = sev_move_to_init_state(argp, &shutdown_required);
 		if (ret)
-			goto e_free_oca;
+			goto e_unlock;
 	}
 
 	ret = __sev_do_cmd_locked(SEV_CMD_PEK_CERT_IMPORT, &data, &argp->error);
 
-e_free_oca:
+e_unlock:
 	if (shutdown_required)
 		__sev_firmware_shutdown(sev, false);
 
+	mutex_unlock(&sev_cmd_mutex);
+
 	kfree(oca_blob);
 e_free_pek:
 	kfree(pek_blob);
@@ -2372,7 +2387,8 @@ static int sev_ioctl_do_get_id2(struct sev_issue_cmd *argp)
 		data.len = 0;
 	}
 
-	ret = __sev_do_cmd_locked(SEV_CMD_GET_ID, &data, &argp->error);
+	scoped_guard(mutex, &sev_cmd_mutex)
+		ret = __sev_do_cmd_locked(SEV_CMD_GET_ID, &data, &argp->error);
 
 	/*
 	 * Firmware will return the length of the ID value (either the minimum
@@ -2430,7 +2446,8 @@ static int sev_ioctl_do_get_id(struct sev_issue_cmd *argp)
 	data->address = __psp_pa(id_blob);
 	data->len = user_size;
 
-	ret = __sev_do_cmd_locked(SEV_CMD_GET_ID, data, &argp->error);
+	scoped_guard(mutex, &sev_cmd_mutex)
+		ret = __sev_do_cmd_locked(SEV_CMD_GET_ID, data, &argp->error);
 	if (!ret) {
 		if (copy_to_user((void __user *)argp->data, id_blob, data->len))
 			ret = -EFAULT;
@@ -2492,19 +2509,29 @@ static int sev_ioctl_do_pdh_export(struct sev_issue_cmd *argp, bool writable)
 	data.cert_chain_len = input.cert_chain_len;
 
 cmd:
+	mutex_lock(&sev_cmd_mutex);
+
 	/* If platform is not in INIT state then transition it to INIT. */
 	if (sev->sev_plat_status.state != SEV_STATE_INIT) {
 		if (!writable) {
 			ret = -EPERM;
+			mutex_unlock(&sev_cmd_mutex);
 			goto e_free_cert;
 		}
 		ret = sev_move_to_init_state(argp, &shutdown_required);
-		if (ret)
+		if (ret) {
+			mutex_unlock(&sev_cmd_mutex);
 			goto e_free_cert;
+		}
 	}
 
 	ret = __sev_do_cmd_locked(SEV_CMD_PDH_CERT_EXPORT, &data, &argp->error);
 
+	if (shutdown_required)
+		__sev_firmware_shutdown(sev, false);
+
+	mutex_unlock(&sev_cmd_mutex);
+
 	/*
 	 * Firmware will return the length of the blobs (either the minimum
 	 * required length or the actual length written), return 'em to the user.
@@ -2535,9 +2562,6 @@ static int sev_ioctl_do_pdh_export(struct sev_issue_cmd *argp, bool writable)
 	}
 
 e_free_cert:
-	if (shutdown_required)
-		__sev_firmware_shutdown(sev, false);
-
 	kfree(cert_blob);
 e_free_pdh:
 	kfree(pdh_blob);
@@ -2605,7 +2629,8 @@ static int sev_ioctl_do_snp_platform_status(struct sev_issue_cmd *argp)
 	if (!argp->data)
 		return -EINVAL;
 
-	ret = __sev_do_snp_platform_status(&status, &argp->error);
+	scoped_guard(mutex, &sev_cmd_mutex)
+		ret = __sev_do_snp_platform_status(&status, &argp->error);
 	if (ret < 0)
 		return ret;
 
@@ -2623,6 +2648,8 @@ static int sev_ioctl_do_snp_commit(struct sev_issue_cmd *argp)
 
 	buf.len = sizeof(buf);
 
+	guard(mutex)(&sev_cmd_mutex);
+
 	ret = __sev_do_cmd_locked(SEV_CMD_SNP_COMMIT, &buf, &argp->error);
 
 	return ret;
@@ -2639,12 +2666,14 @@ static int sev_ioctl_do_snp_set_config(struct sev_issue_cmd *argp, bool writable
 	if (!writable)
 		return -EPERM;
 
-	if (!sev->snp_initialized)
-		return -ENODEV;
-
 	if (copy_from_user(&config, (void __user *)argp->data, sizeof(config)))
 		return -EFAULT;
 
+	guard(mutex)(&sev_cmd_mutex);
+
+	if (!sev->snp_initialized)
+		return -ENODEV;
+
 	return __sev_do_cmd_locked(SEV_CMD_SNP_CONFIG, &config, &argp->error);
 }
 
@@ -2677,7 +2706,8 @@ static int sev_ioctl_do_snp_vlek_load(struct sev_issue_cmd *argp, bool writable)
 
 	input.vlek_wrapped_address = __psp_pa(blob);
 
-	ret = __sev_do_cmd_locked(SEV_CMD_SNP_VLEK_LOAD, &input, &argp->error);
+	scoped_guard(mutex, &sev_cmd_mutex)
+		ret = __sev_do_cmd_locked(SEV_CMD_SNP_VLEK_LOAD, &input, &argp->error);
 	kfree(blob);
 
 	return ret;
@@ -2702,8 +2732,6 @@ static long sev_ioctl(struct file *file, unsigned int ioctl, unsigned long arg)
 	if (input.cmd > SEV_MAX)
 		return -EINVAL;
 
-	mutex_lock(&sev_cmd_mutex);
-
 	switch (input.cmd) {
 
 	case SEV_FACTORY_RESET:
@@ -2747,14 +2775,11 @@ static long sev_ioctl(struct file *file, unsigned int ioctl, unsigned long arg)
 		ret = sev_ioctl_do_snp_vlek_load(&input, writable);
 		break;
 	default:
-		ret = -EINVAL;
-		goto out;
+		return -EINVAL;
 	}
 
 	if (copy_to_user(argp, &input, sizeof(struct sev_issue_cmd)))
 		ret = -EFAULT;
-out:
-	mutex_unlock(&sev_cmd_mutex);
 
 	return ret;
 }
Signature · 1 line
2.43.0
Quoted 114 lines
Quoted 114 lines
[ 2260.466972] ======================================================
[ 2260.473150] WARNING: possible circular locking dependency detected
[ 2260.479329] 7.3.0-smp--ce115a0096358-seanjc-soink #75 Tainted: G     U     O       
[ 2260.486981] ------------------------------------------------------
[ 2260.493161] sev_snp_vmsa_pu/24505 is trying to acquire lock:
[ 2260.498821] ffffffffc01f7900 (sev_cmd_mutex){+.+.}-{4:4}, at: sev_issue_cmd_external_user+0x39/0x60 [ccp]
[ 2260.508387] 
               but task is already holding lock:
[ 2260.514213] ffff93c9dd1cf070 (mapping.invalidate_lock#4){++++}-{4:4}, at: kvm_gmem_populate+0x18c/0x300 [kvm]
[ 2260.524275] 
               which lock already depends on the new lock.
[ 2260.532446] 
               the existing dependency chain (in reverse order) is:
[ 2260.539917] 
               -> #2 (mapping.invalidate_lock#4){++++}-{4:4}:
[ 2260.546878]        down_read+0x3d/0x160
[ 2260.550716]        kvm_gmem_fault_user_mapping+0x43/0x180 [kvm]
[ 2260.556663]        __do_fault+0x3f/0x110
[ 2260.560588]        do_pte_missing+0x515/0x1000
[ 2260.565035]        handle_mm_fault+0x7f2/0x1290
[ 2260.569567]        do_user_addr_fault+0x47d/0x6c0
[ 2260.574271]        exc_page_fault+0x67/0xa0
[ 2260.578458]        asm_exc_page_fault+0x26/0x30
[ 2260.582991] 
               -> #1 (&mm->mmap_lock){++++}-{4:4}:
[ 2260.588997]        __might_fault+0x59/0x80
[ 2260.593096]        _copy_to_user+0x23/0x60
[ 2260.597197]        sev_ioctl_do_platform_status+0x50/0x80 [ccp]
[ 2260.603116]        sev_ioctl+0x220/0x2b0 [ccp]
[ 2260.607560]        __se_sys_ioctl+0x6d/0xb0
[ 2260.611749]        do_syscall_64+0xfe/0x460
[ 2260.615933]        entry_SYSCALL_64_after_hwframe+0x4b/0x53
[ 2260.621506] 
               -> #0 (sev_cmd_mutex){+.+.}-{4:4}:
[ 2260.627426]        __lock_acquire+0x160d/0x2940
[ 2260.631957]        lock_acquire+0xf1/0x300
[ 2260.636050]        __mutex_lock+0x8f/0xdf0
[ 2260.640147]        sev_issue_cmd_external_user+0x39/0x60 [ccp]
[ 2260.645982]        sev_gmem_post_populate+0x189/0x230 [kvm_amd]
[ 2260.651901]        kvm_gmem_populate+0x1ed/0x300 [kvm]
[ 2260.657066]        snp_launch_update+0x1a7/0x250 [kvm_amd]
[ 2260.662551]        sev_mem_enc_ioctl+0x371/0x3d0 [kvm_amd]
[ 2260.668036]        kvm_vm_ioctl+0x52c/0x580 [kvm]
[ 2260.672761]        __se_sys_ioctl+0x6d/0xb0
[ 2260.676946]        do_syscall_64+0xfe/0x460
[ 2260.681133]        entry_SYSCALL_64_after_hwframe+0x4b/0x53
[ 2260.686706] 
               other info that might help us debug this:
[ 2260.694695] Chain exists of:
                 sev_cmd_mutex --> &mm->mmap_lock --> mapping.invalidate_lock#4
[ 2260.706077]  Possible unsafe locking scenario:
[ 2260.711993]        CPU0                    CPU1
[ 2260.716520]        ----                    ----
[ 2260.721052]   lock(mapping.invalidate_lock#4);
[ 2260.725498]                                lock(&mm->mmap_lock);
[ 2260.731503]                                lock(mapping.invalidate_lock#4);
[ 2260.738463]   lock(sev_cmd_mutex);
[ 2260.741868] 
                *** DEADLOCK ***
[ 2260.747778] locks held by sev_snp_vmsa_pu/24505: 3, last CPU#35:
[ 2260.753785]  #0: ffff93c9d54bd3a8 (&kvm->lock){+.+.}-{4:4}, at: sev_mem_enc_ioctl+0x73/0x3d0 [kvm_amd]
[ 2260.763083]  #1: ffff93c9d54bc0a0 (&kvm->slots_lock){+.+.}-{4:4}, at: snp_launch_update+0x145/0x250 [kvm_amd]
[ 2260.772991]  #2: ffff93c9dd1cf070 (mapping.invalidate_lock#4){++++}-{4:4}, at: kvm_gmem_populate+0x18c/0x300 [kvm]
[ 2260.783356] 
               stack backtrace:
[ 2260.787718] CPU: 35 UID: 0 PID: 24505 Comm: sev_snp_vmsa_pu Tainted: G     U     O        7.3.0-smp--ce115a0096358-seanjc-soink #75 PREEMPT 
[ 2260.800310] Tainted: [U]=USER, [O]=OOT_MODULE
[ 2260.804668] Hardware name: Google, Inc.                                                       Arcadia_IT_80/Arcadia_IT_80, BIOS 34.86.0-102 01/25/2026
[ 2260.818127] Call Trace:
[ 2260.820579]  <TASK>
[ 2260.822687]  dump_stack_lvl+0x54/0x70
[ 2260.826351]  print_circular_bug+0x2e4/0x300
[ 2260.830537]  check_noncircular+0xf5/0x120
[ 2260.834553]  __lock_acquire+0x160d/0x2940
[ 2260.838565]  ? __lock_acquire+0x65e/0x2940
[ 2260.842664]  ? rcu_is_watching+0x11/0x40
[ 2260.846587]  ? trace_sched_exit_tp+0x25/0xb0
[ 2260.850861]  lock_acquire+0xf1/0x300
[ 2260.854440]  ? sev_issue_cmd_external_user+0x39/0x60 [ccp]
[ 2260.859928]  ? lock_is_held_type+0xc7/0x120
[ 2260.864112]  ? sev_issue_cmd_external_user+0x39/0x60 [ccp]
[ 2260.869599]  __mutex_lock+0x8f/0xdf0
[ 2260.873178]  ? sev_issue_cmd_external_user+0x39/0x60 [ccp]
[ 2260.878663]  ? pfn_valid+0x89/0x220
[ 2260.882155]  ? pfn_valid+0x1fb/0x220
[ 2260.885736]  ? lookup_address+0x49/0x70
[ 2260.889577]  sev_issue_cmd_external_user+0x39/0x60 [ccp]
[ 2260.894890]  sev_gmem_post_populate+0x189/0x230 [kvm_amd]
[ 2260.900289]  kvm_gmem_populate+0x1ed/0x300 [kvm]
[ 2260.904938]  ? snp_bind_asid+0xd0/0xd0 [kvm_amd]
[ 2260.909561]  snp_launch_update+0x1a7/0x250 [kvm_amd]
[ 2260.914528]  sev_mem_enc_ioctl+0x371/0x3d0 [kvm_amd]
[ 2260.919493]  kvm_vm_ioctl+0x52c/0x580 [kvm]
[ 2260.923706]  ? kvm_gmem_ioctl+0x192/0x1e0 [kvm]
[ 2260.928266]  __se_sys_ioctl+0x6d/0xb0
[ 2260.931930]  do_syscall_64+0xfe/0x460
[ 2260.935595]  ? entry_SYSCALL_64_after_hwframe+0x4b/0x53
[ 2260.940822]  ? trace_irq_disable+0x25/0xc0
[ 2260.944921]  entry_SYSCALL_64_after_hwframe+0x4b/0x53
[ 2260.949972] RIP: 0033:0x474bdb
[ 2260.953031] Code: 00 48 89 44 24 18 31 c0 48 8d 44 24 60 c7 04 24 10 00 00 00 48 89 44 24 08 48 8d 44 24 20 48 89 44 24 10 b8 10 00 00 00 0f 05 <89> c2 3d 00 f0 ff ff 77 1c 48 8b 44 24 18 64 48 2b 04 25 28 00 00
[ 2260.971770] RSP: 002b:00007ffd188ac3f0 EFLAGS: 00000246 ORIG_RAX: 0000000000000010
[ 2260.979337] RAX: ffffffffffffffda RBX: 0000000028691b30 RCX: 0000000000474bdb
[ 2260.986468] RDX: 00007ffd188ac4a0 RSI: 00000000c008aeba RDI: 0000000000000007
[ 2260.993600] RBP: 0000000000135000 R08: 00000000004d50b8 R09: 00000000004e1b78
[ 2261.000734] R10: 0000000000000002 R11: 0000000000000246 R12: 00007ffd188ac4c0
[ 2261.007867] R13: 0000000000137000 R14: 0000000028693db0 R15: 0000000000137000
[ 2261.015001]  </TASK>
#56 lore raw

Re: [PATCH v14 13/22] KVM: selftests: Support guest_memfd in-place conversion

Quoted 8 lines · Michael Roth

I don't have a reproducer for the specific ABBA in question though; I'm confused at why sev_ioctl_do_platform_status() / SEV_CMD_PLATFORM_STATUS would trigger a gmem fault (outside of malicious/intentional case) since it is just querying status on behalf of host userspace and storing the result in userspace buffer where there's no reason to use a guest/gmem page. Was this test specifically set up to do this or was this reachable from a normal path? Just trying to see if there are interactions I'm failing to consider here.

I was just running KVM selftests, although I was running all of them at once.

The condition doesn't require an actual gmem fault. The uaccess APIs have a __might_fault() to communicate that a page fault may occur, and lockdep is smart enough to piece together the problematic chain of locks.

-> #1 (&mm->mmap_lock){++++}-{4:4}:

         __might_fault+0x59/0x80  <===================
         _copy_to_user+0x23/0x60
         sev_ioctl_do_platform_status+0x50/0x80 [ccp]
         sev_ioctl+0x220/0x2b0 [ccp]
         __se_sys_ioctl+0x6d/0xb0
         do_syscall_64+0xfe/0x460
         entry_SYSCALL_64_after_hwframe+0x4b/0x53
#57 lore raw

Re: [PATCH v14 13/22] KVM: selftests: Support guest_memfd in-place conversion

Quoted 16 lines · Sean Christopherson
Quoted 8 lines · Michael Roth

I don't have a reproducer for the specific ABBA in question though; I'm confused at why sev_ioctl_do_platform_status() / SEV_CMD_PLATFORM_STATUS would trigger a gmem fault (outside of malicious/intentional case) since it is just querying status on behalf of host userspace and storing the result in userspace buffer where there's no reason to use a guest/gmem page. Was this test specifically set up to do this or was this reachable from a normal path? Just trying to see if there are interactions I'm failing to consider here.

I was just running KVM selftests, although I was running all of them at once.

The condition doesn't require an actual gmem fault. The uaccess APIs have a __might_fault() to communicate that a page fault may occur, and lockdep is smart enough to piece together the problematic chain of locks.

Quoted 8 lines

-> #1 (&mm->mmap_lock){++++}-{4:4}:

         __might_fault+0x59/0x80  <===================
         _copy_to_user+0x23/0x60
         sev_ioctl_do_platform_status+0x50/0x80 [ccp]
         sev_ioctl+0x220/0x2b0 [ccp]
         __se_sys_ioctl+0x6d/0xb0
         do_syscall_64+0xfe/0x460
         entry_SYSCALL_64_after_hwframe+0x4b/0x53

Ok, that's pretty spiffy. I'll try that on my end as well.

Thanks!

-Mike

#58 lore raw

Re: [PATCH v14 13/22] KVM: selftests: Support guest_memfd in-place conversion

In reply to Sean Christopherson
Quoted 65 lines · Sean Christopherson

+Ashish, Tom and Mike, dropped a bunch of others that probably don't care about the CCP/PSP driver.

Quoted 55 lines · Sean Christopherson
Quoted 16 lines · Xiaoyao Li

Review

Quoted 13 lines · Lisa Wang
(quoted diff) +7 -3
-	flags = 0;
-	if (is_guest_memfd_required(shape))
+	if (is_guest_memfd_required(shape)) {
  		flags |= KVM_MEM_GUEST_MEMFD;
+		if (kvm_has_gmem_attributes)
+			gmem_flags |= GUEST_MEMFD_FLAG_INIT_SHARED |
+				      GUEST_MEMFD_FLAG_MMAP;
+	}
-	vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, 0, 0, nr_pages, flags);
+	vm_mem_add(vm, VM_MEM_SRC_ANONYMOUS, 0, 0, nr_pages, flags,
+		   -1, 0, gmem_flags);
  	for (i = 0; i < NR_MEM_REGIONS; i++)
  		vm->memslots[i] = 0;

IMO, this diff belongs to the gmem in-place series.

Yep, you're 100% right. I pushed this to "coco" for now...

testing/selftests/kvm/lib/kvm_util.c +13 -2
index 3f559f207408..0c67d964a9aa 100644
@@ -494,6 +494,7 @@ struct kvm_vm *__vm_create(struct vm_shape shape, u32 nr_runnable_vcpus,
        u64 nr_pages = vm_nr_pages_required(shape.mode, nr_runnable_vcpus,
                                                 nr_extra_pages);
        struct userspace_mem_region *slot0;
+       u64 gmem_flags = 0;
        struct kvm_vm *vm;
        int i, flags;
 
@@ -509,10 +510,20 @@ struct kvm_vm *__vm_create(struct vm_shape shape, u32 nr_runnable_vcpus,
         * for CoCo VMs that require GUEST_MEMFD backed private memory.
         */
        flags = 0;
-       if (is_guest_memfd_required(shape))
+       if (is_guest_memfd_required(shape)) {
                flags |= KVM_MEM_GUEST_MEMFD;
 
-       vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, 0, 0, nr_pages, flags);
+               /*
+                * Allow mmap() and initialize the gmem instance as SHARED if
+                * in-place conversions are supported, so that the memory can
+                * be populated/initialized like any other memory region.
+                */
+               if (kvm_has_gmem_attributes)
+                       gmem_flags |= GUEST_MEMFD_FLAG_INIT_SHARED |
+                                     GUEST_MEMFD_FLAG_MMAP;
+       }
+
+       vm_mem_add(vm, VM_MEM_SRC_ANONYMOUS, 0, 0, nr_pages, flags, -1, 0, gmem_flags);
        for (i = 0; i < NR_MEM_REGIONS; i++)
                vm->memslots[i] = 0;
 
Quoted 2 lines

but the entire in-place conversion series may need to be dropped because there's a potential deadlock, and I don't see an obviously non-awful way to fix it.

The underlying problem is that drivers/crypto/ccp/sev-dev.c does memory allocations and copy_{to,from}_user() while holding sev_cmd_mutex in a variety of locations.

Quoted 3 lines

I genuinely think the right fix is to not do that, i.e. to perform invasive surgery on the driver so that it holds sev_cmd_mutex only for the operations that really truly need its protection.

I reached this same conclusion, and I think this is the right fix.

To add on, the main purpose of taking the invalidate lock in kvm_gmem_fault_user_mapping() is to synchronize with conversions. mapping.invalidate_lock is the lock used to protect attributes.

(The maple_tree's internal RCU lock is for lockless readers (these readers are only within KVM) and respect the KVM MMU protocol and will check again before committing the result of the read.)

The purpose of taking the invalidate lock in the populate flow is also to synchronize with conversions, so using a lock other than mapping.invalidate_lock wouldn't work, both cases would still take that other lock.

Quoted 9 lines

Because taking page faults while holding a *system*-wide
lock is asking for trouble. Deadlock aside, if one of the user pages being accessed happens to be swapped out, tasks could be blocked on sev_cmd_mutex for quite some time waiting for the page to come back from disk or wherever. Rinse and repeat, and I wouldn't be surprised if VM creation/boot can stall for hundreds of milliseconds or worse.

The alternative would be to take sev_cmd_mutex around the kvm_gmem_populate() call in snp_launch_update(), but even if the CPP maintainers were willing to provide hooks

I realized you meant CCP/PSP instead of CPP :)

Quoted 2 lines

to allow that, I'm not willing to take such a hack in KVM.

Is this a hack because sev_cmd_mutex() would be held for a long time while the populate loop executes?

Quoted 16 lines

So, very sadly, I think in-place conversion is dead for 7.4, and will need to be revived on top of an overhaul of sev_cmd_mutex locking.

Or I guess maybe a less painful temporary workaround would be to disable support for SNP if in-place conversion is enabled. That would avoid extending the in-place conversion bottleneck for TDX, pKVM, and CCA, without having to take a gross hack to make SNP play nice.

E.g. this as a workaround for 7.4 (not yet tested)

arch/x86/kvm/svm/sev.c +0 -0
index fad72cab76cf..df885b8214d4 100644
@@ -3213,6 +3213,14 @@ void __init sev_hardware_setup(void)
 	sev_snp_supported = sev_snp_enabled && cc_platform_has(CC_ATTR_HOST_SEV_SNP);

arch/x86/kvm/svm/sev.c void __init sev_hardware_setup(void) · line 3223

Quoted 10 lines
arch/x86/kvm/svm/sev.c +8 -0
 
 out:
+	/*
+	 * FIXME: Allow SNP with in-place guest_memfd conversion once the PSP
+	 * driver no longer takes its global mutex outside of mmap_lock (which
+	 * leads to deadlock with sev_gmem_post_populate()).
+	 */
+	if (gmem_in_place_conversion)
+		sev_snp_supported = false;
+

If we go with something like this, when SNP and gmem_in_place_conversion starts to work together, would we need some kind of new CAP or other way to advertise that guest_memfd and SNP are now friends?

If Michael/Tom/Ashish are fine with this, I'd definitely prefer some stopgap over delaying all of conversions.

I just saw Michael's email, thank you so much for jumping on this quickly!

Quoted 117 lines
arch/x86/kvm/svm/sev.c +0 -0
 	if (sev_enabled) {
 		init_args.probe = true;
 
Quoted 114 lines
[ 2260.466972] ======================================================
[ 2260.473150] WARNING: possible circular locking dependency detected
[ 2260.479329] 7.3.0-smp--ce115a0096358-seanjc-soink #75 Tainted: G     U     O
[ 2260.486981] ------------------------------------------------------
[ 2260.493161] sev_snp_vmsa_pu/24505 is trying to acquire lock:
[ 2260.498821] ffffffffc01f7900 (sev_cmd_mutex){+.+.}-{4:4}, at: sev_issue_cmd_external_user+0x39/0x60 [ccp]
[ 2260.508387]
               but task is already holding lock:
[ 2260.514213] ffff93c9dd1cf070 (mapping.invalidate_lock#4){++++}-{4:4}, at: kvm_gmem_populate+0x18c/0x300 [kvm]
[ 2260.524275]
               which lock already depends on the new lock.
[ 2260.532446]
               the existing dependency chain (in reverse order) is:
[ 2260.539917]
               -> #2 (mapping.invalidate_lock#4){++++}-{4:4}:
[ 2260.546878]        down_read+0x3d/0x160
[ 2260.550716]        kvm_gmem_fault_user_mapping+0x43/0x180 [kvm]
[ 2260.556663]        __do_fault+0x3f/0x110
[ 2260.560588]        do_pte_missing+0x515/0x1000
[ 2260.565035]        handle_mm_fault+0x7f2/0x1290
[ 2260.569567]        do_user_addr_fault+0x47d/0x6c0
[ 2260.574271]        exc_page_fault+0x67/0xa0
[ 2260.578458]        asm_exc_page_fault+0x26/0x30
[ 2260.582991]
               -> #1 (&mm->mmap_lock){++++}-{4:4}:
[ 2260.588997]        __might_fault+0x59/0x80
[ 2260.593096]        _copy_to_user+0x23/0x60
[ 2260.597197]        sev_ioctl_do_platform_status+0x50/0x80 [ccp]
[ 2260.603116]        sev_ioctl+0x220/0x2b0 [ccp]
[ 2260.607560]        __se_sys_ioctl+0x6d/0xb0
[ 2260.611749]        do_syscall_64+0xfe/0x460
[ 2260.615933]        entry_SYSCALL_64_after_hwframe+0x4b/0x53
[ 2260.621506]
               -> #0 (sev_cmd_mutex){+.+.}-{4:4}:
[ 2260.627426]        __lock_acquire+0x160d/0x2940
[ 2260.631957]        lock_acquire+0xf1/0x300
[ 2260.636050]        __mutex_lock+0x8f/0xdf0
[ 2260.640147]        sev_issue_cmd_external_user+0x39/0x60 [ccp]
[ 2260.645982]        sev_gmem_post_populate+0x189/0x230 [kvm_amd]
[ 2260.651901]        kvm_gmem_populate+0x1ed/0x300 [kvm]
[ 2260.657066]        snp_launch_update+0x1a7/0x250 [kvm_amd]
[ 2260.662551]        sev_mem_enc_ioctl+0x371/0x3d0 [kvm_amd]
[ 2260.668036]        kvm_vm_ioctl+0x52c/0x580 [kvm]
[ 2260.672761]        __se_sys_ioctl+0x6d/0xb0
[ 2260.676946]        do_syscall_64+0xfe/0x460
[ 2260.681133]        entry_SYSCALL_64_after_hwframe+0x4b/0x53
[ 2260.686706]
               other info that might help us debug this:
[ 2260.694695] Chain exists of:
                 sev_cmd_mutex --> &mm->mmap_lock --> mapping.invalidate_lock#4
[ 2260.706077]  Possible unsafe locking scenario:
[ 2260.711993]        CPU0                    CPU1
[ 2260.716520]        ----                    ----
[ 2260.721052]   lock(mapping.invalidate_lock#4);
[ 2260.725498]                                lock(&mm->mmap_lock);
[ 2260.731503]                                lock(mapping.invalidate_lock#4);
[ 2260.738463]   lock(sev_cmd_mutex);
[ 2260.741868]
                *** DEADLOCK ***
[ 2260.747778] locks held by sev_snp_vmsa_pu/24505: 3, last CPU#35:
[ 2260.753785]  #0: ffff93c9d54bd3a8 (&kvm->lock){+.+.}-{4:4}, at: sev_mem_enc_ioctl+0x73/0x3d0 [kvm_amd]
[ 2260.763083]  #1: ffff93c9d54bc0a0 (&kvm->slots_lock){+.+.}-{4:4}, at: snp_launch_update+0x145/0x250 [kvm_amd]
[ 2260.772991]  #2: ffff93c9dd1cf070 (mapping.invalidate_lock#4){++++}-{4:4}, at: kvm_gmem_populate+0x18c/0x300 [kvm]
[ 2260.783356]
               stack backtrace:
[ 2260.787718] CPU: 35 UID: 0 PID: 24505 Comm: sev_snp_vmsa_pu Tainted: G     U     O        7.3.0-smp--ce115a0096358-seanjc-soink #75 PREEMPT
[ 2260.800310] Tainted: [U]=USER, [O]=OOT_MODULE
[ 2260.804668] Hardware name: Google, Inc.                                                       Arcadia_IT_80/Arcadia_IT_80, BIOS 34.86.0-102 01/25/2026
[ 2260.818127] Call Trace:
[ 2260.820579]  <TASK>
[ 2260.822687]  dump_stack_lvl+0x54/0x70
[ 2260.826351]  print_circular_bug+0x2e4/0x300
[ 2260.830537]  check_noncircular+0xf5/0x120
[ 2260.834553]  __lock_acquire+0x160d/0x2940
[ 2260.838565]  ? __lock_acquire+0x65e/0x2940
[ 2260.842664]  ? rcu_is_watching+0x11/0x40
[ 2260.846587]  ? trace_sched_exit_tp+0x25/0xb0
[ 2260.850861]  lock_acquire+0xf1/0x300
[ 2260.854440]  ? sev_issue_cmd_external_user+0x39/0x60 [ccp]
[ 2260.859928]  ? lock_is_held_type+0xc7/0x120
[ 2260.864112]  ? sev_issue_cmd_external_user+0x39/0x60 [ccp]
[ 2260.869599]  __mutex_lock+0x8f/0xdf0
[ 2260.873178]  ? sev_issue_cmd_external_user+0x39/0x60 [ccp]
[ 2260.878663]  ? pfn_valid+0x89/0x220
[ 2260.882155]  ? pfn_valid+0x1fb/0x220
[ 2260.885736]  ? lookup_address+0x49/0x70
[ 2260.889577]  sev_issue_cmd_external_user+0x39/0x60 [ccp]
[ 2260.894890]  sev_gmem_post_populate+0x189/0x230 [kvm_amd]
[ 2260.900289]  kvm_gmem_populate+0x1ed/0x300 [kvm]
[ 2260.904938]  ? snp_bind_asid+0xd0/0xd0 [kvm_amd]
[ 2260.909561]  snp_launch_update+0x1a7/0x250 [kvm_amd]
[ 2260.914528]  sev_mem_enc_ioctl+0x371/0x3d0 [kvm_amd]
[ 2260.919493]  kvm_vm_ioctl+0x52c/0x580 [kvm]
[ 2260.923706]  ? kvm_gmem_ioctl+0x192/0x1e0 [kvm]
[ 2260.928266]  __se_sys_ioctl+0x6d/0xb0
[ 2260.931930]  do_syscall_64+0xfe/0x460
[ 2260.935595]  ? entry_SYSCALL_64_after_hwframe+0x4b/0x53
[ 2260.940822]  ? trace_irq_disable+0x25/0xc0
[ 2260.944921]  entry_SYSCALL_64_after_hwframe+0x4b/0x53
[ 2260.949972] RIP: 0033:0x474bdb
[ 2260.953031] Code: 00 48 89 44 24 18 31 c0 48 8d 44 24 60 c7 04 24 10 00 00 00 48 89 44 24 08 48 8d 44 24 20 48 89 44 24 10 b8 10 00 00 00 0f 05 <89> c2 3d 00 f0 ff ff 77 1c 48 8b 44 24 18 64 48 2b 04 25 28 00 00
[ 2260.971770] RSP: 002b:00007ffd188ac3f0 EFLAGS: 00000246 ORIG_RAX: 0000000000000010
[ 2260.979337] RAX: ffffffffffffffda RBX: 0000000028691b30 RCX: 0000000000474bdb
[ 2260.986468] RDX: 00007ffd188ac4a0 RSI: 00000000c008aeba RDI: 0000000000000007
[ 2260.993600] RBP: 0000000000135000 R08: 00000000004d50b8 R09: 00000000004e1b78
[ 2261.000734] R10: 0000000000000002 R11: 0000000000000246 R12: 00007ffd188ac4c0
[ 2261.007867] R13: 0000000000137000 R14: 0000000028693db0 R15: 0000000000137000
[ 2261.015001]  </TASK>
#59 lore raw

Re: [PATCH v14 13/22] KVM: selftests: Support guest_memfd in-place conversion

Quoted 11 lines · Ackerley Tng
Quoted 2 lines · Sean Christopherson

The alternative would be to take sev_cmd_mutex around the kvm_gmem_populate() call in snp_launch_update(), but even if the CPP maintainers were willing to provide hooks

I realized you meant CCP/PSP instead of CPP :)

Quoted 2 lines

to allow that, I'm not willing to take such a hack in KVM.

Is this a hack because sev_cmd_mutex() would be held for a long time while the populate loop executes?

It would also bleed CCP internals into KVM, and in general doesn't pass a basic "is this the right thing to do" test.

Quoted 17 lines
Quoted 17 lines

So, very sadly, I think in-place conversion is dead for 7.4, and will need to be revived on top of an overhaul of sev_cmd_mutex locking.

Or I guess maybe a less painful temporary workaround would be to disable support for SNP if in-place conversion is enabled. That would avoid extending the in-place conversion bottleneck for TDX, pKVM, and CCA, without having to take a gross hack to make SNP play nice.

E.g. this as a workaround for 7.4 (not yet tested)

arch/x86/kvm/svm/sev.c +0 -0
index fad72cab76cf..df885b8214d4 100644
@@ -3213,6 +3213,14 @@ void __init sev_hardware_setup(void)
 	sev_snp_supported = sev_snp_enabled && cc_platform_has(CC_ATTR_HOST_SEV_SNP);
 
Quoted 13 lines

arch/x86/kvm/svm/sev.c void __init sev_hardware_setup(void) · line 3223

Quoted 9 lines
arch/x86/kvm/svm/sev.c +8 -0
 out:
+	/*
+	 * FIXME: Allow SNP with in-place guest_memfd conversion once the PSP
+	 * driver no longer takes its global mutex outside of mmap_lock (which
+	 * leads to deadlock with sev_gmem_post_populate()).
+	 */
+	if (gmem_in_place_conversion)
+		sev_snp_supported = false;
+

If we go with something like this, when SNP and gmem_in_place_conversion starts to work together, would we need some kind of new CAP or other way to advertise that guest_memfd and SNP are now friends?

I don't think so? KVM already enumerates support for SNP, so reverting the block should tell userspace everything it needs. We'd need to document that enabling in-place conversion disables SNP support, but I think it's purely a documentation problem.

#60 lore raw

Re: [PATCH v14 13/22] KVM: selftests: Support guest_memfd in-place conversion

In reply to Lisa Wang
Quoted 2 lines · Lisa Wang

Set the memory region as shared if guest_memfd in-place conversion is support, so that the KVM selftest framework functions can populate

^^^^^^^ nit: supported

Quoted 3 lines

mmap()-ed guest_memfd memory the same way memory from other memory providers are populated.

[ ... ]

tools/testing/selftests/kvm/lib/kvm_util.c struct kvm_vm *__vm_create(struct vm_shape shape, u32 nr_runnable_vcpus, · line 501

Quoted 9 lines
tools/testing/selftests/kvm/lib/kvm_util.c +2 -2
@@ -495,14 +497,20 @@ struct kvm_vm *__vm_create(struct vm_shape shape, u32 nr_runnable_vcpus,
 	vm = ____vm_create(shape);
 
 	/*
-	 * Force GUEST_MEMFD for the primary memory region if necessary, e.g.
-	 * for CoCo VMs that require GUEST_MEMFD backed private memory.
+	 * Force GUEST_MEMFD for the primary memory region if necessary, and
+	 * initialize it as shared if in-place conversion is support, so the
                                                             ^^^^^^^
                                                             same nit
Quoted 3 lines
tools/testing/selftests/kvm/lib/kvm_util.c +2 -0
+	 * selftest framework can populate it exactly like other memory
+	 * providers.
 	 */
author
#61 lore raw

PATCH v14 14/22 KVM: selftests: Expose function to allocate vCPU stack

In reply to Lisa Wang

From: Sagi Shahar <[email protected]>

Introduce kvm_allocate_vcpu_stack() to allocate a vCPU's stack in preparation for TDX to allocate a vCPU's stack and initialize its stack pointer.

TDX VMs' registers are protected state and cannot be initialized using the KVM_SET_REGS ioctl() that is used for normal VMs. A TDX vCPU's stack address will be a property of the TDX specific boot code that initializes the vCPUs' stack pointers at boot.

---
 tools/testing/selftests/kvm/include/x86/processor.h |  2 ++
 tools/testing/selftests/kvm/lib/x86/processor.c     | 16 +++++++++++-----
 2 files changed, 13 insertions(+), 5 deletions(-)
tools/testing/selftests/kvm/include/x86/processor.h +2 -0
index 7e00c9414595..ff3219edab44 100644
@@ -1144,6 +1144,8 @@ static inline void vcpu_clear_cpuid_feature(struct kvm_vcpu *vcpu,
 	vcpu_set_or_clear_cpuid_feature(vcpu, feature, false);
 }
 
+gva_t kvm_allocate_vcpu_stack(struct kvm_vm *vm);
+
 u64 vcpu_get_msr(struct kvm_vcpu *vcpu, u64 msr_index);
 int _vcpu_set_msr(struct kvm_vcpu *vcpu, u64 msr_index, u64 msr_value);
 
tools/testing/selftests/kvm/lib/x86/processor.c +11 -5
index 748c6041f8bf..8f8bb90fec35 100644
@@ -813,12 +813,9 @@ void vcpu_arch_set_entry_point(struct kvm_vcpu *vcpu, void *guest_code)
 	vcpu_regs_set(vcpu, &regs);
 }
 
-struct kvm_vcpu *vm_arch_vcpu_add(struct kvm_vm *vm, u32 vcpu_id)
+gva_t kvm_allocate_vcpu_stack(struct kvm_vm *vm)
 {
-	struct kvm_mp_state mp_state;
-	struct kvm_regs regs;
 	gva_t stack_gva;
-	struct kvm_vcpu *vcpu;
 
 	stack_gva = __vm_alloc(vm, DEFAULT_STACK_PGS * getpagesize(),
 			       DEFAULT_GUEST_STACK_VADDR_MIN, MEM_REGION_DATA);
@@ -838,6 +835,15 @@ struct kvm_vcpu *vm_arch_vcpu_add(struct kvm_vm *vm, u32 vcpu_id)
 		    "__vm_alloc() did not provide a page-aligned address");
 	stack_gva -= 8;
 
+	return stack_gva;
+}
+
+struct kvm_vcpu *vm_arch_vcpu_add(struct kvm_vm *vm, u32 vcpu_id)
+{
+	struct kvm_mp_state mp_state;
+	struct kvm_vcpu *vcpu;
+	struct kvm_regs regs;
+
 	vcpu = __vm_vcpu_add(vm, vcpu_id);
 	vcpu_init_cpuid(vcpu, kvm_get_supported_cpuid());
 	vcpu_init_sregs(vm, vcpu);
@@ -846,7 +852,7 @@ struct kvm_vcpu *vm_arch_vcpu_add(struct kvm_vm *vm, u32 vcpu_id)
 	/* Setup guest general purpose registers */
 	vcpu_regs_get(vcpu, &regs);
 	regs.rflags = regs.rflags | 0x2;
-	regs.rsp = stack_gva;
+	regs.rsp = kvm_allocate_vcpu_stack(vm);
 	vcpu_regs_set(vcpu, &regs);
 
 	/* Setup the MP state */
Signature · 1 line
2.55.0.229.g6434b31f56-goog
Reviewed-by on v14 14/22
#62 lore raw

Re: [PATCH v14 14/22] KVM: selftests: Expose function to allocate vCPU stack

Quoted 11 lines · Lisa Wang

From: Sagi Shahar <[email protected]>

Introduce kvm_allocate_vcpu_stack() to allocate a vCPU's stack in preparation for TDX to allocate a vCPU's stack and initialize its stack pointer.

TDX VMs' registers are protected state and cannot be initialized using the KVM_SET_REGS ioctl() that is used for normal VMs. A TDX vCPU's stack address will be a property of the TDX specific boot code that initializes the vCPUs' stack pointers at boot.

Quoted 4 lines
author
#63 lore raw

PATCH v14 15/22 KVM: selftests: Call KVM_TDX_INIT_VCPU when creating a new TDX vcpu

In reply to Lisa Wang

From: Sagi Shahar <[email protected]>

TDX VMs need to issue the KVM_TDX_INIT_VCPU ioctl for each vcpu after vcpu creation.

Since the cpuids for TD are managed by the TDX module, read the values virtualized for the TD using KVM_TDX_GET_CPUID and set them in kvm using KVM_SET_CPUID2 so that kvm has an accurate view of the VM cpuid values.

---
 .../selftests/kvm/include/x86/tdx/tdx_util.h       | 21 ++++++++++++++
 tools/testing/selftests/kvm/lib/x86/processor.c    | 33 ++++++++++++++++------
 2 files changed, 46 insertions(+), 8 deletions(-)
tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h +21 -0
index f5003cbaa106..07e7f5c90329 100644
@@ -44,6 +44,27 @@ static inline bool is_tdx_vm(struct kvm_vm *vm)
 	}								\
 })
 
+#define __tdx_vcpu_ioctl(vcpu, cmd, _flags, arg)			\
+({									\
+	union {								\
+		struct kvm_tdx_cmd c;					\
+		unsigned long raw;					\
+	} tdx_cmd = { .c = {						\
+		.id = (cmd),						\
+		.flags = (u32)(_flags),					\
+		.data = (u64)(arg),					\
+	} };								\
+									\
+	__vcpu_ioctl(vcpu, KVM_MEMORY_ENCRYPT_OP, &tdx_cmd.raw);	\
+})
+
+#define tdx_vcpu_ioctl(vcpu, cmd, flags, arg)				\
+({									\
+	int ret = __tdx_vcpu_ioctl(vcpu, cmd, flags, arg);		\
+	TEST_ASSERT(!ret, "%s failed, errno: %d (%s)",			\
+		     #cmd, errno, strerror(errno));			\
+})
+
 void tdx_init_vm(struct kvm_vm *vm, u64 attributes);
 void tdx_vm_setup_boot_code_region(struct kvm_vm *vm);
 void tdx_vm_setup_boot_parameters_region(struct kvm_vm *vm, u32 nr_runnable_vcpus);
tools/testing/selftests/kvm/lib/x86/processor.c +25 -8
index 8f8bb90fec35..f6a2aa6b86a4 100644
@@ -838,6 +838,17 @@ gva_t kvm_allocate_vcpu_stack(struct kvm_vm *vm)
 	return stack_gva;
 }
 
+static void tdx_vcpu_init(struct kvm_vm *vm, struct kvm_vcpu *vcpu)
+{
+	struct kvm_cpuid2 *cpuid;
+
+	cpuid = allocate_kvm_cpuid2(MAX_NR_CPUID_ENTRIES);
+	tdx_vcpu_ioctl(vcpu, KVM_TDX_GET_CPUID, 0, cpuid);
+	vcpu_init_cpuid(vcpu, cpuid);
+	free(cpuid);
+	tdx_vcpu_ioctl(vcpu, KVM_TDX_INIT_VCPU, 0, NULL);
+}
+
 struct kvm_vcpu *vm_arch_vcpu_add(struct kvm_vm *vm, u32 vcpu_id)
 {
 	struct kvm_mp_state mp_state;
@@ -845,15 +856,21 @@ struct kvm_vcpu *vm_arch_vcpu_add(struct kvm_vm *vm, u32 vcpu_id)
 	struct kvm_regs regs;
 
 	vcpu = __vm_vcpu_add(vm, vcpu_id);
-	vcpu_init_cpuid(vcpu, kvm_get_supported_cpuid());
-	vcpu_init_sregs(vm, vcpu);
-	vcpu_init_xcrs(vm, vcpu);
 
-	/* Setup guest general purpose registers */
-	vcpu_regs_get(vcpu, &regs);
-	regs.rflags = regs.rflags | 0x2;
-	regs.rsp = kvm_allocate_vcpu_stack(vm);
-	vcpu_regs_set(vcpu, &regs);
+	if (is_tdx_vm(vm)) {
+		tdx_vcpu_init(vm, vcpu);
+	} else {
+		vcpu_init_cpuid(vcpu, kvm_get_supported_cpuid());
+
+		vcpu_init_sregs(vm, vcpu);
+		vcpu_init_xcrs(vm, vcpu);
+
+		/* Setup guest general purpose registers */
+		vcpu_regs_get(vcpu, &regs);
+		regs.rflags = regs.rflags | 0x2;
+		regs.rsp = kvm_allocate_vcpu_stack(vm);
+		vcpu_regs_set(vcpu, &regs);
+	}
 
 	/* Setup the MP state */
 	mp_state.mp_state = 0;
Signature · 1 line
2.55.0.229.g6434b31f56-goog
#64 lore raw

Re: [PATCH v14 15/22] KVM: selftests: Call KVM_TDX_INIT_VCPU when creating a new TDX vcpu

Quoted 6 lines · Lisa Wang

From: Sagi Shahar <[email protected]>

TDX VMs need to issue the KVM_TDX_INIT_VCPU ioctl for each vcpu after vcpu creation.

Since the cpuids for TD are managed by the TDX module, read the values

Please use CPUIDs instead of cpuids.

Quoted 2 lines

virtualized for the TD using KVM_TDX_GET_CPUID and set them in kvm using KVM_SET_CPUID2 so that kvm has an accurate view of the VM cpuid values.

This does two things, and KVM_SET_CPUID2 isn't a hard requirement for KVM_TDX_INIT_VCPU. we'd better split it into two patches.

Quoted 25 lines
---
  .../selftests/kvm/include/x86/tdx/tdx_util.h       | 21 ++++++++++++++
  tools/testing/selftests/kvm/lib/x86/processor.c    | 33 ++++++++++++++++------
  2 files changed, 46 insertions(+), 8 deletions(-)
tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h +10 -0
index f5003cbaa106..07e7f5c90329 100644
@@ -44,6 +44,27 @@ static inline bool is_tdx_vm(struct kvm_vm *vm)
  	}								\
  })
 
+#define __tdx_vcpu_ioctl(vcpu, cmd, _flags, arg)			\
+({									\
+	union {								\
+		struct kvm_tdx_cmd c;					\
+		unsigned long raw;					\
+	} tdx_cmd = { .c = {						\
+		.id = (cmd),						\
+		.flags = (u32)(_flags),					\
+		.data = (u64)(arg),					\
+	} };								\

tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h static inline bool is_tdx_vm(struct kvm_vm *vm) · line 66

Quoted 10 lines
tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h +10 -0
+									\
+	__vcpu_ioctl(vcpu, KVM_MEMORY_ENCRYPT_OP, &tdx_cmd.raw);	\
+})
+
+#define tdx_vcpu_ioctl(vcpu, cmd, flags, arg)				\
+({									\
+	int ret = __tdx_vcpu_ioctl(vcpu, cmd, flags, arg);		\
+	TEST_ASSERT(!ret, "%s failed, errno: %d (%s)",			\
+		     #cmd, errno, strerror(errno));			\
+})

It doesn't handle the tdx_cmd.c.hw_error for vcpu ioctl, but handle it for vm ioctl, which looks inconsistent. This at least deserves a justification in the changelog.

Quoted 8 lines
tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h +0 -0
  void tdx_init_vm(struct kvm_vm *vm, u64 attributes);
  void tdx_vm_setup_boot_code_region(struct kvm_vm *vm);
  void tdx_vm_setup_boot_parameters_region(struct kvm_vm *vm, u32 nr_runnable_vcpus);
tools/testing/selftests/kvm/lib/x86/processor.c +0 -0
index 8f8bb90fec35..f6a2aa6b86a4 100644

tools/testing/selftests/kvm/lib/x86/processor.c gva_t kvm_allocate_vcpu_stack(struct kvm_vm *vm) · line 846

Quoted 9 lines
tools/testing/selftests/kvm/lib/x86/processor.c +6 -0
@@ -838,6 +838,17 @@ gva_t kvm_allocate_vcpu_stack(struct kvm_vm *vm)
  	return stack_gva;
  }
 
+static void tdx_vcpu_init(struct kvm_vm *vm, struct kvm_vcpu *vcpu)
+{
+	struct kvm_cpuid2 *cpuid;
+
+	cpuid = allocate_kvm_cpuid2(MAX_NR_CPUID_ENTRIES);
+	tdx_vcpu_ioctl(vcpu, KVM_TDX_GET_CPUID, 0, cpuid);

If I remember correctly, though KVM_TDX_GET_CPUID is a vcpu ioctl, the data returned by KVM is retrived from TDX module and TDX module only maintains the TD scope CPUID. So the CPUID returned here is TD scope, for per-vcpu CPUIDs, e.g., x2apicid, we need to update them accordingly.

This seems not a functional gap. I'm OK to leave it to the future, but please leave a TODO comment for it.

Quoted 39 lines
tools/testing/selftests/kvm/lib/x86/processor.c +19 -8
+	vcpu_init_cpuid(vcpu, cpuid);
+	free(cpuid);
+	tdx_vcpu_ioctl(vcpu, KVM_TDX_INIT_VCPU, 0, NULL);
+}
+
  struct kvm_vcpu *vm_arch_vcpu_add(struct kvm_vm *vm, u32 vcpu_id)
  {
  	struct kvm_mp_state mp_state;
@@ -845,15 +856,21 @@ struct kvm_vcpu *vm_arch_vcpu_add(struct kvm_vm *vm, u32 vcpu_id)
  	struct kvm_regs regs;
 
  	vcpu = __vm_vcpu_add(vm, vcpu_id);
-	vcpu_init_cpuid(vcpu, kvm_get_supported_cpuid());
-	vcpu_init_sregs(vm, vcpu);
-	vcpu_init_xcrs(vm, vcpu);
 
-	/* Setup guest general purpose registers */
-	vcpu_regs_get(vcpu, &regs);
-	regs.rflags = regs.rflags | 0x2;
-	regs.rsp = kvm_allocate_vcpu_stack(vm);
-	vcpu_regs_set(vcpu, &regs);
+	if (is_tdx_vm(vm)) {
+		tdx_vcpu_init(vm, vcpu);
+	} else {
+		vcpu_init_cpuid(vcpu, kvm_get_supported_cpuid());
+
+		vcpu_init_sregs(vm, vcpu);
+		vcpu_init_xcrs(vm, vcpu);
+
+		/* Setup guest general purpose registers */
+		vcpu_regs_get(vcpu, &regs);
+		regs.rflags = regs.rflags | 0x2;
+		regs.rsp = kvm_allocate_vcpu_stack(vm);
+		vcpu_regs_set(vcpu, &regs);
+	}
 
  	/* Setup the MP state */
  	mp_state.mp_state = 0;
#65 lore raw

Re: [PATCH v14 15/22] KVM: selftests: Call KVM_TDX_INIT_VCPU when creating a new TDX vcpu

On 8/14/2026 4:32 PM, Xiaoyao Li wrote:

[...]

Quoted 8 lines

tools/testing/selftests/kvm/lib/x86/processor.c gva_t kvm_allocate_vcpu_stack(struct kvm_vm *vm) · line 846

Quoted 6 lines

  +static void tdx_vcpu_init(struct kvm_vm *vm, struct kvm_vcpu *vcpu)

tools/testing/selftests/kvm/lib/x86/processor.c +5 -0
+{
+    struct kvm_cpuid2 *cpuid;
+
+    cpuid = allocate_kvm_cpuid2(MAX_NR_CPUID_ENTRIES);
+    tdx_vcpu_ioctl(vcpu, KVM_TDX_GET_CPUID, 0, cpuid);

If I remember correctly, though KVM_TDX_GET_CPUID is a vcpu ioctl, the data returned by KVM is retrived from TDX module and TDX module only maintains the TD scope CPUID.

KVM_TDX_GET_CPUID does return TD-scoped CPUID metadata only.

Quoted 4 lines

So the CPUID returned here is TD scope, for per-vcpu CPUIDs, e.g., x2apicid, we need to update them accordingly.

This seems not a functional gap. I'm OK to leave it to the future, but please leave a TODO comment for it.

The handling for legacy VMs is similar, i.e. kvm_get_supported_cpuid()also has no per-vcpu CPUID info like APIC ID. It seems that KVM selftests doesn't test CPUID topology enumeration.

A comment could be helpful, although it may not be a TODO comment.

#66 lore raw

Re: [PATCH v14 15/22] KVM: selftests: Call KVM_TDX_INIT_VCPU when creating a new TDX vcpu

In reply to Xiaoyao Li
Quoted 18 lines · Xiaoyao Li
Quoted 18 lines
  	}								\
  })
tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h +16 -0
+#define __tdx_vcpu_ioctl(vcpu, cmd, _flags, arg)			\
+({									\
+	union {								\
+		struct kvm_tdx_cmd c;					\
+		unsigned long raw;					\
+	} tdx_cmd = { .c = {						\
+		.id = (cmd),						\
+		.flags = (u32)(_flags),					\
+		.data = (u64)(arg),					\
+	} };								\
+									\
+	__vcpu_ioctl(vcpu, KVM_MEMORY_ENCRYPT_OP, &tdx_cmd.raw);	\
+})
+
+#define tdx_vcpu_ioctl(vcpu, cmd, flags, arg)				\
+({									\
Quoted 8 lines

tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h static inline bool is_tdx_vm(struct kvm_vm *vm) · line 66

Quoted 4 lines
tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h +4 -0
+	int ret = __tdx_vcpu_ioctl(vcpu, cmd, flags, arg);		\
+	TEST_ASSERT(!ret, "%s failed, errno: %d (%s)",			\
+		     #cmd, errno, strerror(errno));			\
+})

It doesn't handle the tdx_cmd.c.hw_error for vcpu ioctl, but handle it for vm ioctl, which looks inconsistent. This at least deserves a justification in the changelog.

Looks like this assymetry came from a v13 comment [1].

tdx_vcpu_ioctl() actually doesn't write back hw_error to userspace like tdx_vm_ioctl() does, so hw_error is vCPU-scoped only right now. This is a bit subtle so probably deserves to be called out.

[1] https://lore.kernel.org/all/[email protected]/

Quoted 1 line
author
#67 lore raw

PATCH v14 16/22 KVM: selftests: Load per-vCPU guest stack in TDX boot parameters

In reply to Lisa Wang

From: Sagi Shahar <[email protected]>

Allocate a guest stack for each vCPU and record the GVA in the TDX boot parameters region to allow proper vCPU initialization.

---
 tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h |  1 +
 tools/testing/selftests/kvm/lib/x86/processor.c        |  2 ++
 tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c     | 10 ++++++++++
 3 files changed, 13 insertions(+)
tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h +1 -0
index 07e7f5c90329..93ce6e1ad2c8 100644
@@ -69,5 +69,6 @@ void tdx_init_vm(struct kvm_vm *vm, u64 attributes);
 void tdx_vm_setup_boot_code_region(struct kvm_vm *vm);
 void tdx_vm_setup_boot_parameters_region(struct kvm_vm *vm, u32 nr_runnable_vcpus);
 void tdx_vm_load_common_boot_parameters(struct kvm_vm *vm);
+void tdx_vcpu_load_boot_parameters(struct kvm_vm *vm, struct kvm_vcpu *vcpu);
 
 #endif /* SELFTESTS_TDX_TDX_UTIL_H */
tools/testing/selftests/kvm/lib/x86/processor.c +2 -0
index f6a2aa6b86a4..5e415c6577b8 100644
@@ -847,6 +847,8 @@ static void tdx_vcpu_init(struct kvm_vm *vm, struct kvm_vcpu *vcpu)
 	vcpu_init_cpuid(vcpu, cpuid);
 	free(cpuid);
 	tdx_vcpu_ioctl(vcpu, KVM_TDX_INIT_VCPU, 0, NULL);
+
+	tdx_vcpu_load_boot_parameters(vm, vcpu);
 }
 
 struct kvm_vcpu *vm_arch_vcpu_add(struct kvm_vm *vm, u32 vcpu_id)
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +10 -0
index 5bbe51ad0f66..3c3ac6c7ce40 100644
@@ -104,6 +104,16 @@ void tdx_vm_load_common_boot_parameters(struct kvm_vm *vm)
 	TEST_ASSERT(params->idtr.base != 0, "idt base address should not be 0");
 }
 
+void tdx_vcpu_load_boot_parameters(struct kvm_vm *vm, struct kvm_vcpu *vcpu)
+{
+	struct td_boot_parameters *params =
+		addr_gpa2hva(vm, TD_BOOT_PARAMETERS_GPA);
+	struct td_per_vcpu_parameters *vcpu_params =
+		&params->per_vcpu[vcpu->id];
+
+	vcpu_params->esp_gva = kvm_allocate_vcpu_stack(vm);
+}
+
 static struct kvm_tdx_capabilities *tdx_read_capabilities(struct kvm_vm *vm)
 {
 	static struct kvm_tdx_capabilities *tdx_cap;
Signature · 1 line
2.55.0.229.g6434b31f56-goog
Reviewed-by on v14 16/22
#68 lore raw

Re: [PATCH v14 16/22] KVM: selftests: Load per-vCPU guest stack in TDX boot parameters

Quoted 10 lines · Lisa Wang

From: Sagi Shahar <[email protected]>

Allocate a guest stack for each vCPU and record the GVA in the TDX boot parameters region to allow proper vCPU initialization.

Quoted 52 lines
---
  tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h |  1 +
  tools/testing/selftests/kvm/lib/x86/processor.c        |  2 ++
  tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c     | 10 ++++++++++
  3 files changed, 13 insertions(+)
tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h +1 -0
index 07e7f5c90329..93ce6e1ad2c8 100644
@@ -69,5 +69,6 @@ void tdx_init_vm(struct kvm_vm *vm, u64 attributes);
  void tdx_vm_setup_boot_code_region(struct kvm_vm *vm);
  void tdx_vm_setup_boot_parameters_region(struct kvm_vm *vm, u32 nr_runnable_vcpus);
  void tdx_vm_load_common_boot_parameters(struct kvm_vm *vm);
+void tdx_vcpu_load_boot_parameters(struct kvm_vm *vm, struct kvm_vcpu *vcpu);
 
  #endif /* SELFTESTS_TDX_TDX_UTIL_H */
tools/testing/selftests/kvm/lib/x86/processor.c +2 -0
index f6a2aa6b86a4..5e415c6577b8 100644
@@ -847,6 +847,8 @@ static void tdx_vcpu_init(struct kvm_vm *vm, struct kvm_vcpu *vcpu)
  	vcpu_init_cpuid(vcpu, cpuid);
  	free(cpuid);
  	tdx_vcpu_ioctl(vcpu, KVM_TDX_INIT_VCPU, 0, NULL);
+
+	tdx_vcpu_load_boot_parameters(vm, vcpu);
  }
 
  struct kvm_vcpu *vm_arch_vcpu_add(struct kvm_vm *vm, u32 vcpu_id)
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +10 -0
index 5bbe51ad0f66..3c3ac6c7ce40 100644
@@ -104,6 +104,16 @@ void tdx_vm_load_common_boot_parameters(struct kvm_vm *vm)
  	TEST_ASSERT(params->idtr.base != 0, "idt base address should not be 0");
  }
 
+void tdx_vcpu_load_boot_parameters(struct kvm_vm *vm, struct kvm_vcpu *vcpu)
+{
+	struct td_boot_parameters *params =
+		addr_gpa2hva(vm, TD_BOOT_PARAMETERS_GPA);
+	struct td_per_vcpu_parameters *vcpu_params =
+		&params->per_vcpu[vcpu->id];
+
+	vcpu_params->esp_gva = kvm_allocate_vcpu_stack(vm);
+}
+
  static struct kvm_tdx_capabilities *tdx_read_capabilities(struct kvm_vm *vm)
  {
  	static struct kvm_tdx_capabilities *tdx_cap;
#69 lore raw

Re: [PATCH v14 16/22] KVM: selftests: Load per-vCPU guest stack in TDX boot parameters

In reply to Lisa Wang
Quoted 16 lines · Lisa Wang

From: Sagi Shahar <[email protected]>

Allocate a guest stack for each vCPU and record the GVA in the TDX boot parameters region to allow proper vCPU initialization.

---
 tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h |  1 +
 tools/testing/selftests/kvm/lib/x86/processor.c        |  2 ++
 tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c     | 10 ++++++++++
 3 files changed, 13 insertions(+)

[ ... ]

tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c void tdx_vm_load_common_boot_parameters(struct kvm_vm *vm) · line 114

Quoted 9 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +8 -0
+void tdx_vcpu_load_boot_parameters(struct kvm_vm *vm, struct kvm_vcpu *vcpu)
+{
+	struct td_boot_parameters *params =
+		addr_gpa2hva(vm, TD_BOOT_PARAMETERS_GPA);
+	struct td_per_vcpu_parameters *vcpu_params =
+		&params->per_vcpu[vcpu->id];
+
+	vcpu_params->esp_gva = kvm_allocate_vcpu_stack(vm);

Just flagging that there is a subtle truncation here. The TDX boot code sets esp in 32-bit mode so it has the same visibility constraint as CR3, which has an explicit check ("vm->mmu.pgd < (1ULL << 32)").

Quoted 10 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +2 -0
+}
+
 static struct kvm_tdx_capabilities *tdx_read_capabilities(struct kvm_vm *vm)
 {
 	static struct kvm_tdx_capabilities *tdx_cap;
Signature · 2 lines
-- 
2.55.0.229.g6434b31f56-goog
author
#70 lore raw

PATCH v14 17/22 KVM: selftests: Set entry point for TDX guest code

In reply to Lisa Wang

From: Sagi Shahar <[email protected]>

Since the rip register is inaccessible for TDX VMs, we need a different way to set the guest entry point for TDX VMs. This is done by writing the guest code address to a predefined location in the guest memory and loading it into rip as part of the TDX boot code.

---
 tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h |  1 +
 tools/testing/selftests/kvm/lib/x86/processor.c        | 10 +++++++---
 tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c     | 10 ++++++++++
 3 files changed, 18 insertions(+), 3 deletions(-)
tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h +1 -0
index 93ce6e1ad2c8..307e2c0bc9c9 100644
@@ -70,5 +70,6 @@ void tdx_vm_setup_boot_code_region(struct kvm_vm *vm);
 void tdx_vm_setup_boot_parameters_region(struct kvm_vm *vm, u32 nr_runnable_vcpus);
 void tdx_vm_load_common_boot_parameters(struct kvm_vm *vm);
 void tdx_vcpu_load_boot_parameters(struct kvm_vm *vm, struct kvm_vcpu *vcpu);
+void tdx_vcpu_set_entry_point(struct kvm_vcpu *vcpu, void *guest_code);
 
 #endif /* SELFTESTS_TDX_TDX_UTIL_H */
tools/testing/selftests/kvm/lib/x86/processor.c +7 -3
index 5e415c6577b8..4b97a9a1bb5e 100644
@@ -808,9 +808,13 @@ void vcpu_arch_set_entry_point(struct kvm_vcpu *vcpu, void *guest_code)
 {
 	struct kvm_regs regs;
 
-	vcpu_regs_get(vcpu, &regs);
-	regs.rip = (unsigned long) guest_code;
-	vcpu_regs_set(vcpu, &regs);
+	if (is_tdx_vm(vcpu->vm)) {
+		tdx_vcpu_set_entry_point(vcpu, guest_code);
+	} else {
+		vcpu_regs_get(vcpu, &regs);
+		regs.rip = (unsigned long)guest_code;
+		vcpu_regs_set(vcpu, &regs);
+	}
 }
 
 gva_t kvm_allocate_vcpu_stack(struct kvm_vm *vm)
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +10 -0
index 3c3ac6c7ce40..be63d8652a02 100644
@@ -114,6 +114,16 @@ void tdx_vcpu_load_boot_parameters(struct kvm_vm *vm, struct kvm_vcpu *vcpu)
 	vcpu_params->esp_gva = kvm_allocate_vcpu_stack(vm);
 }
 
+void tdx_vcpu_set_entry_point(struct kvm_vcpu *vcpu, void *guest_code)
+{
+	struct td_boot_parameters *params =
+		addr_gpa2hva(vcpu->vm, TD_BOOT_PARAMETERS_GPA);
+	struct td_per_vcpu_parameters *vcpu_params =
+		&params->per_vcpu[vcpu->id];
+
+	vcpu_params->guest_code = (u64)guest_code;
+}
+
 static struct kvm_tdx_capabilities *tdx_read_capabilities(struct kvm_vm *vm)
 {
 	static struct kvm_tdx_capabilities *tdx_cap;
Signature · 1 line
2.55.0.229.g6434b31f56-goog
Reviewed-by on v14 17/22
#71 lore raw

Re: [PATCH v14 17/22] KVM: selftests: Set entry point for TDX guest code

Quoted 9 lines · Lisa Wang

From: Sagi Shahar <[email protected]>

Since the rip register is inaccessible for TDX VMs, we need a different way to set the guest entry point for TDX VMs. This is done by writing the guest code address to a predefined location in the guest memory and loading it into rip as part of the TDX boot code.

Quoted 60 lines
---
  tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h |  1 +
  tools/testing/selftests/kvm/lib/x86/processor.c        | 10 +++++++---
  tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c     | 10 ++++++++++
  3 files changed, 18 insertions(+), 3 deletions(-)
tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h +1 -0
index 93ce6e1ad2c8..307e2c0bc9c9 100644
@@ -70,5 +70,6 @@ void tdx_vm_setup_boot_code_region(struct kvm_vm *vm);
  void tdx_vm_setup_boot_parameters_region(struct kvm_vm *vm, u32 nr_runnable_vcpus);
  void tdx_vm_load_common_boot_parameters(struct kvm_vm *vm);
  void tdx_vcpu_load_boot_parameters(struct kvm_vm *vm, struct kvm_vcpu *vcpu);
+void tdx_vcpu_set_entry_point(struct kvm_vcpu *vcpu, void *guest_code);
 
  #endif /* SELFTESTS_TDX_TDX_UTIL_H */
tools/testing/selftests/kvm/lib/x86/processor.c +7 -3
index 5e415c6577b8..4b97a9a1bb5e 100644
@@ -808,9 +808,13 @@ void vcpu_arch_set_entry_point(struct kvm_vcpu *vcpu, void *guest_code)
  {
  	struct kvm_regs regs;
 
-	vcpu_regs_get(vcpu, &regs);
-	regs.rip = (unsigned long) guest_code;
-	vcpu_regs_set(vcpu, &regs);
+	if (is_tdx_vm(vcpu->vm)) {
+		tdx_vcpu_set_entry_point(vcpu, guest_code);
+	} else {
+		vcpu_regs_get(vcpu, &regs);
+		regs.rip = (unsigned long)guest_code;
+		vcpu_regs_set(vcpu, &regs);
+	}
  }
 
  gva_t kvm_allocate_vcpu_stack(struct kvm_vm *vm)
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +10 -0
index 3c3ac6c7ce40..be63d8652a02 100644
@@ -114,6 +114,16 @@ void tdx_vcpu_load_boot_parameters(struct kvm_vm *vm, struct kvm_vcpu *vcpu)
  	vcpu_params->esp_gva = kvm_allocate_vcpu_stack(vm);
  }
 
+void tdx_vcpu_set_entry_point(struct kvm_vcpu *vcpu, void *guest_code)
+{
+	struct td_boot_parameters *params =
+		addr_gpa2hva(vcpu->vm, TD_BOOT_PARAMETERS_GPA);
+	struct td_per_vcpu_parameters *vcpu_params =
+		&params->per_vcpu[vcpu->id];
+
+	vcpu_params->guest_code = (u64)guest_code;
+}
+
  static struct kvm_tdx_capabilities *tdx_read_capabilities(struct kvm_vm *vm)
  {
  	static struct kvm_tdx_capabilities *tdx_cap;
Reviewed-by on v14 17/22
#72 lore raw

Re: [PATCH v14 17/22] KVM: selftests: Set entry point for TDX guest code

In reply to Lisa Wang
Quoted 9 lines · Lisa Wang

From: Sagi Shahar <[email protected]>

Since the rip register is inaccessible for TDX VMs, we need a different way to set the guest entry point for TDX VMs. This is done by writing the guest code address to a predefined location in the guest memory and loading it into rip as part of the TDX boot code.

author
#73 lore raw

PATCH v14 18/22 KVM: selftests: Add helpers to init TDX memory and finalize VM

In reply to Lisa Wang

From: Ackerley Tng <[email protected]>

TDX protected memory needs to be measured and encrypted before it can be used by the guest. Traverse the VM's memory regions and initialize all the protected ranges by calling KVM_TDX_INIT_MEM_REGION.

Once all the memory is initialized, the VM can be finalized by calling KVM_TDX_FINALIZE_VM.

---
 .../selftests/kvm/include/x86/tdx/tdx_util.h       |  2 +
 tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c | 63 ++++++++++++++++++++++
 2 files changed, 65 insertions(+)
tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h +2 -0
index 307e2c0bc9c9..35f0b2b7ac40 100644
@@ -72,4 +72,6 @@ void tdx_vm_load_common_boot_parameters(struct kvm_vm *vm);
 void tdx_vcpu_load_boot_parameters(struct kvm_vm *vm, struct kvm_vcpu *vcpu);
 void tdx_vcpu_set_entry_point(struct kvm_vcpu *vcpu, void *guest_code);
 
+void tdx_vm_finalize(struct kvm_vm *vm);
+
 #endif /* SELFTESTS_TDX_TDX_UTIL_H */
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +63 -0

71 lines not on this page · raw message

Signature · 1 line
2.55.0.229.g6434b31f56-goog
#74 lore raw

Re: [PATCH v14 18/22] KVM: selftests: Add helpers to init TDX memory and finalize VM

Quoted 75 lines · Lisa Wang

From: Ackerley Tng <[email protected]>

TDX protected memory needs to be measured and encrypted before it can be used by the guest. Traverse the VM's memory regions and initialize all the protected ranges by calling KVM_TDX_INIT_MEM_REGION.

Once all the memory is initialized, the VM can be finalized by calling KVM_TDX_FINALIZE_VM.

---
  .../selftests/kvm/include/x86/tdx/tdx_util.h       |  2 +
  tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c | 63 ++++++++++++++++++++++
  2 files changed, 65 insertions(+)
tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h +2 -0
index 307e2c0bc9c9..35f0b2b7ac40 100644
@@ -72,4 +72,6 @@ void tdx_vm_load_common_boot_parameters(struct kvm_vm *vm);
  void tdx_vcpu_load_boot_parameters(struct kvm_vm *vm, struct kvm_vcpu *vcpu);
  void tdx_vcpu_set_entry_point(struct kvm_vcpu *vcpu, void *guest_code);
 
+void tdx_vm_finalize(struct kvm_vm *vm);
+
  #endif /* SELFTESTS_TDX_TDX_UTIL_H */
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +27 -0
index be63d8652a02..831b0e5160df 100644
@@ -1,5 +1,7 @@
  // SPDX-License-Identifier: GPL-2.0-only
 
+#include <linux/align.h>
+
  #include "processor.h"
  #include "tdx/td_boot.h"
  #include "tdx/tdx_util.h"
@@ -251,3 +253,64 @@ void tdx_init_vm(struct kvm_vm *vm, u64 attributes)
 
  	free(init_vm);
  }
+
+static void tdx_init_mem_region(struct kvm_vm *vm, void *source_pages,
+				u64 gpa, u64 size)
+{
+	u32 flags = KVM_TDX_MEASURE_MEMORY_REGION;
+	struct kvm_tdx_init_mem_region mem_region = {
+		.source_addr = (u64)source_pages,
+		.gpa = gpa,
+		.nr_pages = size / PAGE_SIZE,
+	};
+	struct kvm_vcpu *vcpu;
+
+	vcpu = list_first_entry_or_null(&vm->vcpus, struct kvm_vcpu, list);
+
+	TEST_ASSERT(size && IS_ALIGNED(size, PAGE_SIZE),
+		"Cannot add partial pages to the guest memory.\n");
+	TEST_ASSERT(IS_ALIGNED((u64)source_pages, PAGE_SIZE),
+		"Source memory buffer is not page aligned\n");
+	tdx_vcpu_ioctl(vcpu, KVM_TDX_INIT_MEM_REGION, flags, &mem_region);
+}
+
+static void tdx_load_private_memory(struct kvm_vm *vm)
+{
+	struct userspace_mem_region *region;
+	int ctr;

tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c void tdx_init_vm(struct kvm_vm *vm, u64 attributes) · line 290

Quoted 10 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +10 -0
+
+	hash_for_each(vm->regions.slot_hash, ctr, region, slot_node) {
+		const struct sparsebit *protected_pages = region->protected_phy_pages;
+		const gpa_t gpa_base = region->region.guest_phys_addr;
+		const u64 hva_base = region->region.userspace_addr;
+		const sparsebit_idx_t lowest_page_in_region = gpa_base >> vm->page_shift;
+		void *source_pages = NULL;
+		sparsebit_idx_t i, j;
+
+		if (!sparsebit_any_set(protected_pages))

sparebit_any_set() doens't check if the input is NULL. So we need to check it here.

Quoted 7 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +7 -0
+			continue;
+
+		TEST_ASSERT(region->region.guest_memfd != -1,
+			    "TD private memory must be backed by guest_memfd");
+
+		sparsebit_for_each_set_range(protected_pages, i, j) {
+			const u64 size_to_load = (j - i + 1) * vm->page_size;

tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c void tdx_init_vm(struct kvm_vm *vm, u64 attributes) · line 306

Quoted 10 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +9 -0
+			const u64 offset =
+				(i - lowest_page_in_region) * vm->page_size;
+			const u64 hva = hva_base + offset;
+			const u64 gpa = gpa_base + offset;
+
+			if (!kvm_has_gmem_attributes)
+				source_pages = (void *)hva;
+
 
+			vm_mem_set_private(vm, gpa, size_to_load);

So vm_mem_set_private() has to be called at this late stage when run with in-place gmem. But for non in-place gmem, we can actually call vm_mem_set_private() in __vm_phy_pages_alloc().

Calling vm_mem_set_private() here instead of in __vm_phy_pages_alloc() looks like a trick to me. That is, we cannot set the page as private when allocating a guest physical page as protected because if doing so, we cannot write the initial content to it.

This is the topic about how to implement the infras for in-place gmem, not the issue of this series. Let me go read the selftest patches of gmem in-place series and we can discuss there.

Quoted 11 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +10 -0
+			tdx_init_mem_region(vm, source_pages, gpa, size_to_load);
+		}
+	}
+}
+
+void tdx_vm_finalize(struct kvm_vm *vm)
+{
+	tdx_load_private_memory(vm);
+	tdx_vm_ioctl(vm, KVM_TDX_FINALIZE_VM, 0, NULL);
+}
#75 lore raw

Re: [PATCH v14 18/22] KVM: selftests: Add helpers to init TDX memory and finalize VM

Quoted 37 lines · Xiaoyao Li

[...snip...]

Quoted 4 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +4 -0
+static void tdx_load_private_memory(struct kvm_vm *vm)
+{
+	struct userspace_mem_region *region;
+	int ctr;

tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c void tdx_init_vm(struct kvm_vm *vm, u64 attributes) · line 290

Quoted 10 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +10 -0
+
+	hash_for_each(vm->regions.slot_hash, ctr, region, slot_node) {
+		const struct sparsebit *protected_pages = region->protected_phy_pages;
+		const gpa_t gpa_base = region->region.guest_phys_addr;
+		const u64 hva_base = region->region.userspace_addr;
+		const sparsebit_idx_t lowest_page_in_region = gpa_base >> vm->page_shift;
+		void *source_pages = NULL;
+		sparsebit_idx_t i, j;
+
+		if (!sparsebit_any_set(protected_pages))

sparebit_any_set() doens't check if the input is NULL. So we need to check it here.

Quoted 15 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +15 -0
+			continue;
+
+		TEST_ASSERT(region->region.guest_memfd != -1,
+			    "TD private memory must be backed by guest_memfd");
+
+		sparsebit_for_each_set_range(protected_pages, i, j) {
+			const u64 size_to_load = (j - i + 1) * vm->page_size;
+			const u64 offset =
+				(i - lowest_page_in_region) * vm->page_size;
+			const u64 hva = hva_base + offset;
+			const u64 gpa = gpa_base + offset;
+
+			if (!kvm_has_gmem_attributes)
+				source_pages = (void *)hva;
+
Quoted 8 lines

tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c void tdx_init_vm(struct kvm_vm *vm, u64 attributes) · line 306

Quoted 1 line
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +1 -0
+			vm_mem_set_private(vm, gpa, size_to_load);

So vm_mem_set_private() has to be called at this late stage when run with in-place gmem. But for non in-place gmem, we can actually call vm_mem_set_private() in __vm_phy_pages_alloc().

Calling vm_mem_set_private() here instead of in __vm_phy_pages_alloc() looks like a trick to me.

I thought this is fine because __vm_phy_pages_alloc() seems to be a rather low-level function, where the responsibility of the function is just to allocate (for find some physical pages). Calling vm_mem_set_private() in there seems to be doing too much.

Quoted 4 lines

That is, we cannot set the page as private
when allocating a guest physical page as protected because if doing so, we cannot write the initial content to it.

I think calling it here isn't a trick, it's a good way to reuse all the existing code that builds up the guest image in place. In-place conversion allows you to set stuff up in shared memory and then convert everything when you're done and also populate the memory.

Quoted 15 lines

This is the topic about how to implement the infras for in-place gmem, not the issue of this series. Let me go read the selftest patches of gmem in-place series and we can discuss there.

Quoted 11 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +10 -0
+			tdx_init_mem_region(vm, source_pages, gpa, size_to_load);
+		}
+	}
+}
+
+void tdx_vm_finalize(struct kvm_vm *vm)
+{
+	tdx_load_private_memory(vm);
+	tdx_vm_ioctl(vm, KVM_TDX_FINALIZE_VM, 0, NULL);
+}
#76 lore raw

Re: [PATCH v14 18/22] KVM: selftests: Add helpers to init TDX memory and finalize VM

Quoted 45 lines · Ackerley Tng
Quoted 43 lines · Xiaoyao Li

[...snip...]

Quoted 4 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +4 -0
+static void tdx_load_private_memory(struct kvm_vm *vm)
+{
+	struct userspace_mem_region *region;
+	int ctr;

tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c void tdx_init_vm(struct kvm_vm *vm, u64 attributes) · line 290

Quoted 10 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +10 -0
+
+	hash_for_each(vm->regions.slot_hash, ctr, region, slot_node) {
+		const struct sparsebit *protected_pages = region->protected_phy_pages;
+		const gpa_t gpa_base = region->region.guest_phys_addr;
+		const u64 hva_base = region->region.userspace_addr;
+		const sparsebit_idx_t lowest_page_in_region = gpa_base >> vm->page_shift;
+		void *source_pages = NULL;
+		sparsebit_idx_t i, j;
+
+		if (!sparsebit_any_set(protected_pages))

sparebit_any_set() doens't check if the input is NULL. So we need to check it here.

Quoted 7 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +7 -0
+			continue;
+
+		TEST_ASSERT(region->region.guest_memfd != -1,
+			    "TD private memory must be backed by guest_memfd");
+
+		sparsebit_for_each_set_range(protected_pages, i, j) {
+			const u64 size_to_load = (j - i + 1) * vm->page_size;

tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c void tdx_init_vm(struct kvm_vm *vm, u64 attributes) · line 306

Quoted 10 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +9 -0
+			const u64 offset =
+				(i - lowest_page_in_region) * vm->page_size;
+			const u64 hva = hva_base + offset;
+			const u64 gpa = gpa_base + offset;
+
+			if (!kvm_has_gmem_attributes)
+				source_pages = (void *)hva;
+
 
+			vm_mem_set_private(vm, gpa, size_to_load);

So vm_mem_set_private() has to be called at this late stage when run with in-place gmem. But for non in-place gmem, we can actually call vm_mem_set_private() in __vm_phy_pages_alloc().

Quoted 7 lines
Quoted 2 lines

Calling vm_mem_set_private() here instead of in __vm_phy_pages_alloc() looks like a trick to me.

I thought this is fine because __vm_phy_pages_alloc() seems to be a rather low-level function, where the responsibility of the function is just to allocate (for find some physical pages). Calling vm_mem_set_private() in there seems to be doing too much.

__vm_phy_pages_alloc() takes a parameter @protected, which is used to tell the allocated physical pages need to be protected(private) or not. It looks weird that a page is allocated as protected but actually it is still shared.

Quoted 9 lines
Quoted 4 lines

That is, we cannot set the page as private
when allocating a guest physical page as protected because if doing so, we cannot write the initial content to it.

I think calling it here isn't a trick, it's a good way to reuse all the existing code that builds up the guest image in place. In-place conversion allows you to set stuff up in shared memory and then convert everything when you're done and also populate the memory.

This is based on the assumption of "in-place conversion". However the TDX selftests should be able to run without "in-place conversion". There is no hard dependency on it.

I think this patch implements what patch 13[1] of this series says "For CoCo VMs, pages that need to be private are explicitly set to private before executing the VM."

But all of this is for the case of "in-place conversion". When "in-place conversion" is not supported/enabled by the kernel, GUEST_MEMFD_FLAG_INIT_SHARED for Coco VMs is *not* allowed and all the memory are private by default and no need to call vm_mem_set_private() here at all.

[1] https://lore.kernel.org/all/[email protected]/

Quoted 15 lines
Quoted 15 lines

This is the topic about how to implement the infras for in-place gmem, not the issue of this series. Let me go read the selftest patches of gmem in-place series and we can discuss there.

Quoted 11 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +10 -0
+			tdx_init_mem_region(vm, source_pages, gpa, size_to_load);
+		}
+	}
+}
+
+void tdx_vm_finalize(struct kvm_vm *vm)
+{
+	tdx_load_private_memory(vm);
+	tdx_vm_ioctl(vm, KVM_TDX_FINALIZE_VM, 0, NULL);
+}
#77 lore raw

Re: [PATCH v14 18/22] KVM: selftests: Add helpers to init TDX memory and finalize VM

Quoted 54 lines · Xiaoyao Li
Quoted 45 lines · Ackerley Tng
Quoted 43 lines · Xiaoyao Li

[...snip...]

Quoted 4 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +4 -0
+static void tdx_load_private_memory(struct kvm_vm *vm)
+{
+	struct userspace_mem_region *region;
+	int ctr;

tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c void tdx_init_vm(struct kvm_vm *vm, u64 attributes) · line 290

Quoted 10 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +10 -0
+
+	hash_for_each(vm->regions.slot_hash, ctr, region, slot_node) {
+		const struct sparsebit *protected_pages = region->protected_phy_pages;
+		const gpa_t gpa_base = region->region.guest_phys_addr;
+		const u64 hva_base = region->region.userspace_addr;
+		const sparsebit_idx_t lowest_page_in_region = gpa_base >> vm->page_shift;
+		void *source_pages = NULL;
+		sparsebit_idx_t i, j;
+
+		if (!sparsebit_any_set(protected_pages))

sparebit_any_set() doens't check if the input is NULL. So we need to check it here.

Quoted 7 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +7 -0
+			continue;
+
+		TEST_ASSERT(region->region.guest_memfd != -1,
+			    "TD private memory must be backed by guest_memfd");
+
+		sparsebit_for_each_set_range(protected_pages, i, j) {
+			const u64 size_to_load = (j - i + 1) * vm->page_size;

tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c void tdx_init_vm(struct kvm_vm *vm, u64 attributes) · line 306

Quoted 10 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +9 -0
+			const u64 offset =
+				(i - lowest_page_in_region) * vm->page_size;
+			const u64 hva = hva_base + offset;
+			const u64 gpa = gpa_base + offset;
+
+			if (!kvm_has_gmem_attributes)
+				source_pages = (void *)hva;
+
 
+			vm_mem_set_private(vm, gpa, size_to_load);

So vm_mem_set_private() has to be called at this late stage when run with in-place gmem. But for non in-place gmem, we can actually call vm_mem_set_private() in __vm_phy_pages_alloc().

Quoted 7 lines
Quoted 2 lines

Calling vm_mem_set_private() here instead of in __vm_phy_pages_alloc() looks like a trick to me.

I thought this is fine because __vm_phy_pages_alloc() seems to be a rather low-level function, where the responsibility of the function is just to allocate (for find some physical pages). Calling vm_mem_set_private() in there seems to be doing too much.

Quoted 5 lines

__vm_phy_pages_alloc() takes a parameter @protected, which is used to tell the allocated physical pages need to be protected(private) or not. It looks weird that a page is allocated as protected but actually it is still shared.

I think there are a few different things here:

1. Finding some range of guest physical addresses for a page. This is __vm_phy_pages_alloc().

2. Tracking the guest physical addresses as shared or private in the userspace VMM. This is setting region->protected_phy_pages.

3. Creating private vs shared mappings in guest page tables. This is done in __virt_pg_map().

4. Transferring region->protected_phy_pages into the kernel. This is either the VM or guest_memfd conversion ioctl.

5. Loading private memory based on region->protected_phy_pages. This is done in tdx_load_private_memory() and the SNP counterpart.

So protected_phy_pages tracks all the pages that will be initialized as private, not the pages that are private (since vm_mem_set_private() doesn't update protected_phy_pages).

(2) is coupled to whether the VM has protected memory in vm_phy_pages_alloc(). This is kind of an awkward assumption since a CoCo VM can have shared memory too, and especially for selftests there can be shared pages right from the start.

However, setting protected_phy_pages in (2) and referencing in it (3),
(4) and (5) makes sense since that's the common path, and we want the common path to be easy for the selftest writer.

I think there could be some refactoring so that the different components 1 through 5 are opened up for easier usage but I haven't thought about how it should look like and anyway I think that belongs in a separate series.

Imo the direct response to looking allocated as protected but is actually shared is that the page was allocated as "will be loaded as private", and doesn't make any statement on whether it is shared or private.

Does seeing protected_phy_pages as "will be loaded as private" help in explaining why vm_mem_set_private() is not called in __vm_phy_pages_alloc() but is done only in tdx_load_private_memory()?

Quoted 18 lines
Quoted 9 lines
Quoted 4 lines

That is, we cannot set the page as private
when allocating a guest physical page as protected because if doing so, we cannot write the initial content to it.

I think calling it here isn't a trick, it's a good way to reuse all the existing code that builds up the guest image in place. In-place conversion allows you to set stuff up in shared memory and then convert everything when you're done and also populate the memory.

This is based on the assumption of "in-place conversion". However the TDX selftests should be able to run without "in-place conversion". There is no hard dependency on it.

I think this patch implements what patch 13[1] of this series says "For CoCo VMs, pages that need to be private are explicitly set to private before executing the VM."

Quoted 9 lines

But all of this is for the case of "in-place conversion". When "in-place conversion" is not supported/enabled by the kernel, GUEST_MEMFD_FLAG_INIT_SHARED for Coco VMs is *not* allowed and all the memory are private by default and no need to call vm_mem_set_private() here at all.

[1]
https://lore.kernel.org/all/[email protected]/

Doing (3) as part of (5) isn't just for in-place conversion. Without in-place conversion, source_pages should be the address mmap()-ed from some non-guest_memfd memory, which by the time of tdx_load_private_memory(), would already be populated with whatever needs to be loaded. Populate needs to be called on private memory regardless of whether we use in-place conversions.

Quoted 15 lines
Quoted 15 lines
Quoted 15 lines

This is the topic about how to implement the infras for in-place gmem, not the issue of this series. Let me go read the selftest patches of gmem in-place series and we can discuss there.

Quoted 11 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +10 -0
+			tdx_init_mem_region(vm, source_pages, gpa, size_to_load);
+		}
+	}
+}
+
+void tdx_vm_finalize(struct kvm_vm *vm)
+{
+	tdx_load_private_memory(vm);
+	tdx_vm_ioctl(vm, KVM_TDX_FINALIZE_VM, 0, NULL);
+}
author
#78 lore raw

PATCH v14 19/22 KVM: selftests: Finalize TD memory as part of kvm_arch_vm_finalize_vcpus

In reply to Lisa Wang

From: Sagi Shahar <[email protected]>

Finalize TDX VM after creation to make it runnable.

---
 tools/testing/selftests/kvm/lib/x86/processor.c | 6 ++++++
 1 file changed, 6 insertions(+)
tools/testing/selftests/kvm/lib/x86/processor.c +6 -0
index 4b97a9a1bb5e..8319ae3d9fe6 100644
@@ -1489,6 +1489,12 @@ bool kvm_arch_has_default_irqchip(void)
 	return true;
 }
 
+void kvm_arch_vm_finalize_vcpus(struct kvm_vm *vm)
+{
+	if (is_tdx_vm(vm))
+		tdx_vm_finalize(vm);
+}
+
 void setup_smram(struct kvm_vm *vm, struct kvm_vcpu *vcpu, u64 smram_gpa,
 		 const void *smi_handler, size_t handler_size)
 {
Signature · 1 line
2.55.0.229.g6434b31f56-goog
#79 lore raw

Re: [PATCH v14 19/22] KVM: selftests: Finalize TD memory as part of kvm_arch_vm_finalize_vcpus

Quoted 3 lines · Lisa Wang

From: Sagi Shahar <[email protected]>

Finalize TDX VM after creation to make it runnable.

The changelog is too weak.

Quoted 26 lines
---
  tools/testing/selftests/kvm/lib/x86/processor.c | 6 ++++++
  1 file changed, 6 insertions(+)
tools/testing/selftests/kvm/lib/x86/processor.c +6 -0
index 4b97a9a1bb5e..8319ae3d9fe6 100644
@@ -1489,6 +1489,12 @@ bool kvm_arch_has_default_irqchip(void)
  	return true;
  }
 
+void kvm_arch_vm_finalize_vcpus(struct kvm_vm *vm)
+{
+	if (is_tdx_vm(vm))
+		tdx_vm_finalize(vm);
+}
+
  void setup_smram(struct kvm_vm *vm, struct kvm_vcpu *vcpu, u64 smram_gpa,
  		 const void *smi_handler, size_t handler_size)
  {
#80 lore raw

Re: [PATCH v14 19/22] KVM: selftests: Finalize TD memory as part of kvm_arch_vm_finalize_vcpus

Quoted 7 lines · Xiaoyao Li
Quoted 3 lines · Lisa Wang

From: Sagi Shahar <[email protected]>

Finalize TDX VM after creation to make it runnable.

The changelog is too weak.

Some other points I can think of (Xiaoyao, please add on!):

+ Why was kvm_arch_vm_finalize_vcpus() was picked as the lifecycle hook in which to finalize the VM? (See comments from the earlier revision from Sean) What are the rejected alternatives?
+ Expand on "make it runnable": finalizing also performs measurements

Just confirming: Peter, you meant to add the () at the end of kvm_arch_vm_finalize_vcpus in the commit subject, right?

Quoted 26 lines
Quoted 26 lines
---
  tools/testing/selftests/kvm/lib/x86/processor.c | 6 ++++++
  1 file changed, 6 insertions(+)
tools/testing/selftests/kvm/lib/x86/processor.c +6 -0
index 4b97a9a1bb5e..8319ae3d9fe6 100644
@@ -1489,6 +1489,12 @@ bool kvm_arch_has_default_irqchip(void)
  	return true;
  }
 
+void kvm_arch_vm_finalize_vcpus(struct kvm_vm *vm)
+{
+	if (is_tdx_vm(vm))
+		tdx_vm_finalize(vm);
+}
+
  void setup_smram(struct kvm_vm *vm, struct kvm_vcpu *vcpu, u64 smram_gpa,
  		 const void *smi_handler, size_t handler_size)
  {
#81 lore raw

Re: [PATCH v14 19/22] KVM: selftests: Finalize TD memory as part of kvm_arch_vm_finalize_vcpus

Quoted 12 lines · Ackerley Tng
Quoted 7 lines · Xiaoyao Li
Quoted 3 lines · Lisa Wang

From: Sagi Shahar <[email protected]>

Finalize TDX VM after creation to make it runnable.

The changelog is too weak.

Some other points I can think of (Xiaoyao, please add on!):

Quoted 7 lines

+ Why was kvm_arch_vm_finalize_vcpus() was picked as the lifecycle hook in which to finalize the VM? (See comments from the earlier revision from Sean) What are the rejected alternatives?
+ Expand on "make it runnable": finalizing also performs measurements

Just confirming: Peter, you meant to add the () at the end of kvm_arch_vm_finalize_vcpus in the commit subject, right?

Yep correct

Quoted 1 line
#82 lore raw

Re: [PATCH v14 19/22] KVM: selftests: Finalize TD memory as part of kvm_arch_vm_finalize_vcpus

In reply to Lisa Wang

Nit: use "kvm_arch_vm_finalize_vcpus()" in the subject line

Quoted 11 lines · Lisa Wang

From: Sagi Shahar <[email protected]>

Finalize TDX VM after creation to make it runnable.

---
 tools/testing/selftests/kvm/lib/x86/processor.c | 6 ++++++
 1 file changed, 6 insertions(+)
author
#83 lore raw

PATCH v14 20/22 KVM: selftests: Implement MMIO WRITE for the TDX VM

In reply to Lisa Wang

From: Erdem Aktas <[email protected]>

Implement the tdx_mmio_write() to allow TDX VMs to request MMIO emulation.

Follow the Intel Guest-Hypervisor Communication Interface (GHCI) spec to the minimum extent that a spec-abiding TDX module will pass the request to KVM. Skip implementing the #VE handler as described in the GHCI spec to reduce selftests dependency.

To perform emulated I/O, VMs use the TDG.VP.VMCALL instruction to request MMIO.

---
 tools/testing/selftests/kvm/Makefile.kvm          |  1 +
 tools/testing/selftests/kvm/include/x86/tdx/tdx.h | 17 +++++++++++
 tools/testing/selftests/kvm/lib/x86/tdx/tdx.S     | 37 +++++++++++++++++++++++
 3 files changed, 55 insertions(+)
tools/testing/selftests/kvm/Makefile.kvm +1 -0
index 645d9aac61db..28b33ed8c2e6 100644
@@ -33,6 +33,7 @@ LIBKVM_x86 += lib/x86/tdx/tdx_util.c
 LIBKVM_x86 += lib/x86/ucall.c
 LIBKVM_x86 += lib/x86/vmx.c
 LIBKVM_x86 += lib/x86/tdx/td_boot.S
+LIBKVM_x86 += lib/x86/tdx/tdx.S
 
 LIBKVM_arm64 += lib/arm64/gic.c
 LIBKVM_arm64 += lib/arm64/gic_v3.c
tools/testing/selftests/kvm/include/x86/tdx/tdx.h new file +17 -0
new file mode 100644
index 000000000000..6355a30bb47f
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef SELFTEST_KVM_TDX_TDX_H
+#define SELFTEST_KVM_TDX_TDX_H
+
+#include <linux/types.h>
+
+#define TDG_VP_VMCALL_VE_REQUEST_MMIO    48
+#define TDVMCALL_MMIO_WRITE		  1
+
+u64 __tdcall(u64 leaf, u64 r12, u64 r13, u64 r14, u64 r15);
+
+static inline u64 tdx_mmio_write(u64 address, u32 size, u64 data_in)
+{
+	return __tdcall(TDG_VP_VMCALL_VE_REQUEST_MMIO, size,
+			TDVMCALL_MMIO_WRITE, address, data_in);
+}
+#endif /* SELFTEST_KVM_TDX_TDX_H */
tools/testing/selftests/kvm/lib/x86/tdx/tdx.S new file +37 -0
new file mode 100644
index 000000000000..dff48a3624a1
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+.globl __tdcall
+/*
+ * u64 __tdcall(u64 leaf, u64 r12, u64 r13, u64 r14, u64 r15)
+ */
+__tdcall:
+	push %r12
+	push %r13
+	push %r14
+	push %r15
+
+	/* Map C ABI fast arguments to TDX GHCI payload registers */
+	/* RDI=leaf, RSI=r12, RDX=r13, RCX=r14, R8=r15 */
+
+	mov %rdi, %r11
+	mov %rsi, %r12
+	mov %rdx, %r13
+	mov %rcx, %r14
+	mov %r8,  %r15
+
+	/* TDCALL boilerplate */
+	mov $0, %rax
+	mov $0xFC00, %rcx	/* Expose R10-R15 */
+	mov $0, %r10
+
+	/* tdcall instruction */
+	.byte 0x66, 0x0f, 0x01, 0xcc
+
+	/* Extract status */
+	mov %r10, %rax
+
+	pop %r15
+	pop %r14
+	pop %r13
+	pop %r12
+	ret
Signature · 1 line
2.55.0.229.g6434b31f56-goog
Reviewed-by on v14 20/22
#84 lore raw

Re: [PATCH v14 20/22] KVM: selftests: Implement MMIO WRITE for the TDX VM

Quoted 104 lines · Lisa Wang

From: Erdem Aktas <[email protected]>

Implement the tdx_mmio_write() to allow TDX VMs to request MMIO emulation.

Follow the Intel Guest-Hypervisor Communication Interface (GHCI) spec to the minimum extent that a spec-abiding TDX module will pass the request to KVM. Skip implementing the #VE handler as described in the GHCI spec to reduce selftests dependency.

To perform emulated I/O, VMs use the TDG.VP.VMCALL instruction to request MMIO.

---
 tools/testing/selftests/kvm/Makefile.kvm          |  1 +
 tools/testing/selftests/kvm/include/x86/tdx/tdx.h | 17 +++++++++++
 tools/testing/selftests/kvm/lib/x86/tdx/tdx.S     | 37 +++++++++++++++++++++++
 3 files changed, 55 insertions(+)
tools/testing/selftests/kvm/Makefile.kvm +1 -0
index 645d9aac61db..28b33ed8c2e6 100644
@@ -33,6 +33,7 @@ LIBKVM_x86 += lib/x86/tdx/tdx_util.c
 LIBKVM_x86 += lib/x86/ucall.c
 LIBKVM_x86 += lib/x86/vmx.c
 LIBKVM_x86 += lib/x86/tdx/td_boot.S
+LIBKVM_x86 += lib/x86/tdx/tdx.S
 
 LIBKVM_arm64 += lib/arm64/gic.c
 LIBKVM_arm64 += lib/arm64/gic_v3.c
tools/testing/selftests/kvm/include/x86/tdx/tdx.h new file +17 -0
new file mode 100644
index 000000000000..6355a30bb47f
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef SELFTEST_KVM_TDX_TDX_H
+#define SELFTEST_KVM_TDX_TDX_H
+
+#include <linux/types.h>
+
+#define TDG_VP_VMCALL_VE_REQUEST_MMIO    48
+#define TDVMCALL_MMIO_WRITE		  1
+
+u64 __tdcall(u64 leaf, u64 r12, u64 r13, u64 r14, u64 r15);
+
+static inline u64 tdx_mmio_write(u64 address, u32 size, u64 data_in)
+{
+	return __tdcall(TDG_VP_VMCALL_VE_REQUEST_MMIO, size,
+			TDVMCALL_MMIO_WRITE, address, data_in);
+}
+#endif /* SELFTEST_KVM_TDX_TDX_H */
tools/testing/selftests/kvm/lib/x86/tdx/tdx.S new file +37 -1
new file mode 100644
index 000000000000..dff48a3624a1
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+.globl __tdcall
+/*
+ * u64 __tdcall(u64 leaf, u64 r12, u64 r13, u64 r14, u64 r15)
+ */
+__tdcall:
+	push %r12
+	push %r13
+	push %r14
+	push %r15
+
+	/* Map C ABI fast arguments to TDX GHCI payload registers */
+	/* RDI=leaf, RSI=r12, RDX=r13, RCX=r14, R8=r15 */
+
+	mov %rdi, %r11
+	mov %rsi, %r12
+	mov %rdx, %r13
+	mov %rcx, %r14
+	mov %r8,  %r15
+
+	/* TDCALL boilerplate */
+	mov $0, %rax
+	mov $0xFC00, %rcx	/* Expose R10-R15 */
+	mov $0, %r10
+
+	/* tdcall instruction */
+	.byte 0x66, 0x0f, 0x01, 0xcc
+
+	/* Extract status */
+	mov %r10, %rax
+
+	pop %r15
+	pop %r14
+	pop %r13
+	pop %r12
+	ret
 
--
Quoted 1 line

2.55.0.229.g6434b31f56-goog

We've been discussing internally if there's a nicer way to write this and let it scale to other tdcall wrappers in future tests, especially when we run out of registers for passing parameters (input and output) for other tdcalls.

Looks like the only way to prevent the compiler from messing up register state before the tdcall instruction is to be completely explicit about it, and to use assembly in a .S file.

I miss how readable it was in v13 [1]. Sean's comments at [1] are also echoed in [2].

[1] https://lore.kernel.org/all/[email protected]/
[2] https://lore.kernel.org/all/[email protected]/

We can figure out how best to extend this when the other tests are worked on.

#85 lore raw

Re: [PATCH v14 20/22] KVM: selftests: Implement MMIO WRITE for the TDX VM

In reply to Lisa Wang
Quoted 8 lines · Lisa Wang

From: Erdem Aktas <[email protected]>

Implement the tdx_mmio_write() to allow TDX VMs to request MMIO emulation.

Follow the Intel Guest-Hypervisor Communication Interface (GHCI) spec to the minimum extent that a spec-abiding TDX module will pass the request to KVM.

I don't get what it wants to say with this.

Quoted 4 lines

Skip implementing the #VE handler as described in the GHCI spec to reduce selftests dependency.

To perform emulated I/O, VMs use the TDG.VP.VMCALL instruction to

To perform emulated MMIO?

Quoted 31 lines

request MMIO.

---
  tools/testing/selftests/kvm/Makefile.kvm          |  1 +
  tools/testing/selftests/kvm/include/x86/tdx/tdx.h | 17 +++++++++++
  tools/testing/selftests/kvm/lib/x86/tdx/tdx.S     | 37 +++++++++++++++++++++++
  3 files changed, 55 insertions(+)
tools/testing/selftests/kvm/Makefile.kvm +1 -0
index 645d9aac61db..28b33ed8c2e6 100644
@@ -33,6 +33,7 @@ LIBKVM_x86 += lib/x86/tdx/tdx_util.c
  LIBKVM_x86 += lib/x86/ucall.c
  LIBKVM_x86 += lib/x86/vmx.c
  LIBKVM_x86 += lib/x86/tdx/td_boot.S
+LIBKVM_x86 += lib/x86/tdx/tdx.S
 
  LIBKVM_arm64 += lib/arm64/gic.c
  LIBKVM_arm64 += lib/arm64/gic_v3.c
tools/testing/selftests/kvm/include/x86/tdx/tdx.h new file +0 -0
new file mode 100644
index 000000000000..6355a30bb47f

tools/testing/selftests/kvm/include/x86/tdx/tdx.h line 7

Quoted 7 lines
tools/testing/selftests/kvm/include/x86/tdx/tdx.h new file +7 -0
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef SELFTEST_KVM_TDX_TDX_H
+#define SELFTEST_KVM_TDX_TDX_H
+
+#include <linux/types.h>
+
+#define TDG_VP_VMCALL_VE_REQUEST_MMIO    48

I know it is the name in GHCI spec. But MMIO doesn't have to be invoked by #VE. I think we can directly call it

TDG_VP_VMCALL_MMIO

tools/testing/selftests/kvm/include/x86/tdx/tdx.h line 8

Quoted 1 line
tools/testing/selftests/kvm/include/x86/tdx/tdx.h new file +1 -0
+#define TDVMCALL_MMIO_WRITE		  1

So we are defining TDG_VP_VMCALL_* and TDVMCALL_*. We'd better to make it consistent.

Quoted 14 lines
tools/testing/selftests/kvm/include/x86/tdx/tdx.h new file +8 -0
+u64 __tdcall(u64 leaf, u64 r12, u64 r13, u64 r14, u64 r15);
+
+static inline u64 tdx_mmio_write(u64 address, u32 size, u64 data_in)
+{
+	return __tdcall(TDG_VP_VMCALL_VE_REQUEST_MMIO, size,
+			TDVMCALL_MMIO_WRITE, address, data_in);
+}
+#endif /* SELFTEST_KVM_TDX_TDX_H */
tools/testing/selftests/kvm/lib/x86/tdx/tdx.S new file +0 -0
new file mode 100644
index 000000000000..dff48a3624a1

tools/testing/selftests/kvm/lib/x86/tdx/tdx.S line 5

Quoted 5 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx.S new file +5 -0
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+.globl __tdcall
+/*
+ * u64 __tdcall(u64 leaf, u64 r12, u64 r13, u64 r14, u64 r15)

this funtion is specific to TDG.VP.VMCALL. Please just name it tdg_vp_vmcall()

tools/testing/selftests/kvm/lib/x86/tdx/tdx.S line 14

Quoted 9 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx.S new file +9 -0
+ */
+__tdcall:
+	push %r12
+	push %r13
+	push %r14
+	push %r15
+
+	/* Map C ABI fast arguments to TDX GHCI payload registers */
+	/* RDI=leaf, RSI=r12, RDX=r13, RCX=r14, R8=r15 */

/*
*
*/

for multiple-line comments?

tools/testing/selftests/kvm/lib/x86/tdx/tdx.S line 22

Quoted 7 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx.S new file +7 -0
+	mov %rdi, %r11
+	mov %rsi, %r12
+	mov %rdx, %r13
+	mov %rcx, %r14
+	mov %r8,  %r15
+
+	/* TDCALL boilerplate */

it's not TDCALL boilerplate but TDG.VP.VMCALL since below rax is hardcoded to 0.

Quoted 16 lines
tools/testing/selftests/kvm/lib/x86/tdx/tdx.S new file +15 -0
+	mov $0, %rax
+	mov $0xFC00, %rcx	/* Expose R10-R15 */
+	mov $0, %r10
+
+	/* tdcall instruction */
+	.byte 0x66, 0x0f, 0x01, 0xcc
+
+	/* Extract status */
+	mov %r10, %rax
+
+	pop %r15
+	pop %r14
+	pop %r13
+	pop %r12
+	ret
#86 lore raw

Re: [PATCH v14 20/22] KVM: selftests: Implement MMIO WRITE for the TDX VM

In reply to Lisa Wang
Quoted 13 lines · Lisa Wang

From: Erdem Aktas <[email protected]>

Implement the tdx_mmio_write() to allow TDX VMs to request MMIO emulation.

Follow the Intel Guest-Hypervisor Communication Interface (GHCI) spec to the minimum extent that a spec-abiding TDX module will pass the request to KVM. Skip implementing the #VE handler as described in the GHCI spec to reduce selftests dependency.

To perform emulated I/O, VMs use the TDG.VP.VMCALL instruction to request MMIO.

[ ... ]

Quoted 6 lines
tools/testing/selftests/kvm/include/x86/tdx/tdx.h new file +0 -0
new file mode 100644
index 000000000000..6355a30bb47f

tools/testing/selftests/kvm/include/x86/tdx/tdx.h line 8

Quoted 8 lines
tools/testing/selftests/kvm/include/x86/tdx/tdx.h new file +8 -0
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef SELFTEST_KVM_TDX_TDX_H
+#define SELFTEST_KVM_TDX_TDX_H
+
+#include <linux/types.h>
+
+#define TDG_VP_VMCALL_VE_REQUEST_MMIO    48
+#define TDVMCALL_MMIO_WRITE		  1

The indentation in these two lines is off.

tools/testing/selftests/kvm/include/x86/tdx/tdx.h line 10

Quoted 2 lines
tools/testing/selftests/kvm/include/x86/tdx/tdx.h new file +2 -0
+
+u64 __tdcall(u64 leaf, u64 r12, u64 r13, u64 r14, u64 r15);

As Xiaoyao already pointed out, this function name is misleading. This is a TDG.VP.VMCALL (or a TDVMCALL), and r11 is a subfunction (the guest kernel simply calls it "fn").

Quoted 13 lines
tools/testing/selftests/kvm/include/x86/tdx/tdx.h new file +13 -0
+
+.globl __tdcall
+/*
+ * u64 __tdcall(u64 leaf, u64 r12, u64 r13, u64 r14, u64 r15)
+ */
+__tdcall:
+	push %r12
+	push %r13
+	push %r14
+	push %r15
+
+	/* Map C ABI fast arguments to TDX GHCI payload registers */
+	/* RDI=leaf, RSI=r12, RDX=r13, RCX=r14, R8=r15 */

[ ... ]

tools/testing/selftests/kvm/include/x86/tdx/tdx.h

Quoted 6 lines
tools/testing/selftests/kvm/include/x86/tdx/tdx.h new file +6 -0
+
+	pop %r15
+	pop %r14
+	pop %r13
+	pop %r12
+	ret

This is probably more of a nit... td_boot.S has:

.section .note.GNU-stack,"",%progbits

...to disable executable stack.

Quoted 5 lines
Signature · 2 lines
-- 
2.55.0.229.g6434b31f56-goog
author
#87 lore raw

PATCH v14 21/22 KVM: selftests: Add ucall support for TDX

In reply to Lisa Wang

From: Ackerley Tng <[email protected]>

Implement TDX ucall using TDCALL-based MMIO to pass the ucall address from the VM to the host.

In standard KVM selftests, ucall uses a PIO instruction as a trigger to exit to the host, which then retrieves the ucall address by reading the guest's RDI register. This approach is incompatible with TDX because the host cannot access guest registers.

Furthermore, PIO exits only expose 4 bytes of immediate data, which is insufficient for a 8-byte ucall address. By using TDCALL-based MMIO, the VM can share the full 8-byte address in a single exit without refactoring the common ucall framework and other non-x86 architectures.

---
 tools/testing/selftests/kvm/include/x86/ucall.h |  6 -----
 tools/testing/selftests/kvm/lib/x86/ucall.c     | 33 +++++++++++++++++++++++++
 2 files changed, 33 insertions(+), 6 deletions(-)
tools/testing/selftests/kvm/include/x86/ucall.h +0 -6
index 0e4950041e3e..7e54ec2c1a45 100644
@@ -2,12 +2,6 @@
 #ifndef SELFTEST_KVM_UCALL_H
 #define SELFTEST_KVM_UCALL_H
 
-#include "kvm_util.h"
-
 #define UCALL_EXIT_REASON       KVM_EXIT_IO
 
-static inline void ucall_arch_init(struct kvm_vm *vm, gpa_t mmio_gpa)
-{
-}
-
 #endif
tools/testing/selftests/kvm/lib/x86/ucall.c +33 -0
index e7dd5791959b..7a954b2d0bdd 100644
@@ -5,11 +5,35 @@
  * Copyright (C) 2018, Red Hat, Inc.
  */
 #include "kvm_util.h"
+#include "tdx/tdx.h"
+#include "tdx/tdx_util.h"
 
 #define UCALL_PIO_PORT ((u16)0x1000)
 
+/* HPET address is guaranteed to be unused for ucall MMIO */
+#define UCALL_MMIO_GPA 0xfed00000
+
+static u8 vm_type;
+static gpa_t ucall_mmio_gpa;
+
+void ucall_arch_init(struct kvm_vm *vm, gpa_t mmio_gpa)
+{
+	vm_type = vm->type;
+	sync_global_to_guest(vm, vm_type);
+
+	if (is_tdx_vm(vm)) {
+		ucall_mmio_gpa = UCALL_MMIO_GPA | vm->arch.s_bit;
+		sync_global_to_guest(vm, ucall_mmio_gpa);
+	}
+}
+
 void ucall_arch_do_ucall(gva_t uc)
 {
+	if (vm_type == KVM_X86_TDX_VM) {
+		tdx_mmio_write(ucall_mmio_gpa, sizeof(gva_t), uc);
+		return;
+	}
+
 	/*
 	 * FIXME: Revert this hack (the entire commit that added it) once nVMX
 	 * preserves L2 GPRs across a nested VM-Exit.  If a ucall from L2, e.g.
@@ -46,6 +70,15 @@ void *ucall_arch_get_ucall(struct kvm_vcpu *vcpu)
 {
 	struct kvm_run *run = vcpu->run;
 
+	if (vm_type == KVM_X86_TDX_VM) {
+		if (run->exit_reason == KVM_EXIT_MMIO &&
+		    run->mmio.phys_addr == UCALL_MMIO_GPA &&
+		    run->mmio.len == sizeof(gva_t) &&
+		    run->mmio.is_write)
+			return (void *)(*((gva_t *)run->mmio.data));
+		return NULL;
+	}
+
 	if (run->exit_reason == KVM_EXIT_IO && run->io.port == UCALL_PIO_PORT) {
 		struct kvm_regs regs;
 
Signature · 1 line
2.55.0.229.g6434b31f56-goog
#88 lore raw

Re: [PATCH v14 21/22] KVM: selftests: Add ucall support for TDX

Quoted 30 lines · Lisa Wang

From: Ackerley Tng <[email protected]>

Implement TDX ucall using TDCALL-based MMIO to pass the ucall address from the VM to the host.

In standard KVM selftests, ucall uses a PIO instruction as a trigger to exit to the host, which then retrieves the ucall address by reading the guest's RDI register. This approach is incompatible with TDX because the host cannot access guest registers.

Furthermore, PIO exits only expose 4 bytes of immediate data, which is insufficient for a 8-byte ucall address. By using TDCALL-based MMIO, the VM can share the full 8-byte address in a single exit without refactoring the common ucall framework and other non-x86 architectures.

---
  tools/testing/selftests/kvm/include/x86/ucall.h |  6 -----
  tools/testing/selftests/kvm/lib/x86/ucall.c     | 33 +++++++++++++++++++++++++
  2 files changed, 33 insertions(+), 6 deletions(-)
tools/testing/selftests/kvm/include/x86/ucall.h +0 -0
index 0e4950041e3e..7e54ec2c1a45 100644

tools/testing/selftests/kvm/include/x86/ucall.h line 5

Quoted 6 lines
tools/testing/selftests/kvm/include/x86/ucall.h +0 -2
@@ -2,12 +2,6 @@
  #ifndef SELFTEST_KVM_UCALL_H
  #define SELFTEST_KVM_UCALL_H
 
-#include "kvm_util.h"
-
  #define UCALL_EXIT_REASON       KVM_EXIT_IO

This seems to leave the potential issue for future since x86 now supports different EXIT_REASON for UCALL.

Quoted 18 lines
tools/testing/selftests/kvm/include/x86/ucall.h +0 -4
-static inline void ucall_arch_init(struct kvm_vm *vm, gpa_t mmio_gpa)
-{
-}
-
  #endif
tools/testing/selftests/kvm/lib/x86/ucall.c +2 -0
index e7dd5791959b..7a954b2d0bdd 100644
@@ -5,11 +5,35 @@
   * Copyright (C) 2018, Red Hat, Inc.
   */
  #include "kvm_util.h"
+#include "tdx/tdx.h"
+#include "tdx/tdx_util.h"
 
  #define UCALL_PIO_PORT ((u16)0x1000)
 

tools/testing/selftests/kvm/lib/x86/ucall.c line 22

Quoted 10 lines
tools/testing/selftests/kvm/lib/x86/ucall.c +10 -0
+/* HPET address is guaranteed to be unused for ucall MMIO */
+#define UCALL_MMIO_GPA 0xfed00000
+
+static u8 vm_type;
+static gpa_t ucall_mmio_gpa;
+
+void ucall_arch_init(struct kvm_vm *vm, gpa_t mmio_gpa)
+{
+	vm_type = vm->type;
+	sync_global_to_guest(vm, vm_type);

It works and it looks simple. But we have the architectural approach to test if a guest is TD guest, by checking the CPUID 0x21.

Since checking CPUID 0x21 is not complex, and as a bonus it can help test if TDX module behaves correctly for CPUID leaf 0x21, I think we should switch to use CPUID 0x21 to check if it is TDX VM in guest code?

tools/testing/selftests/kvm/lib/x86/ucall.c line 25

Quoted 2 lines
tools/testing/selftests/kvm/lib/x86/ucall.c +2 -0
+	if (is_tdx_vm(vm)) {
+		ucall_mmio_gpa = UCALL_MMIO_GPA | vm->arch.s_bit;

So the passed-in @mmio_gpa is not used. Why cannot use the mmio_gpa,

slot0->region.guest_phys_addr + slot0->region.memory_size

chosen by __vm_create()?

Quoted 31 lines
tools/testing/selftests/kvm/lib/x86/ucall.c +18 -0
+		sync_global_to_guest(vm, ucall_mmio_gpa);
+	}
+}
+
  void ucall_arch_do_ucall(gva_t uc)
  {
+	if (vm_type == KVM_X86_TDX_VM) {
+		tdx_mmio_write(ucall_mmio_gpa, sizeof(gva_t), uc);
+		return;
+	}
+
  	/*
  	 * FIXME: Revert this hack (the entire commit that added it) once nVMX
  	 * preserves L2 GPRs across a nested VM-Exit.  If a ucall from L2, e.g.
@@ -46,6 +70,15 @@ void *ucall_arch_get_ucall(struct kvm_vcpu *vcpu)
  {
  	struct kvm_run *run = vcpu->run;
 
+	if (vm_type == KVM_X86_TDX_VM) {
+		if (run->exit_reason == KVM_EXIT_MMIO &&
+		    run->mmio.phys_addr == UCALL_MMIO_GPA &&
+		    run->mmio.len == sizeof(gva_t) &&
+		    run->mmio.is_write)
+			return (void *)(*((gva_t *)run->mmio.data));
+		return NULL;
+	}
+
  	if (run->exit_reason == KVM_EXIT_IO && run->io.port == UCALL_PIO_PORT) {
  		struct kvm_regs regs;
 
#89 lore raw

Re: [PATCH v14 21/22] KVM: selftests: Add ucall support for TDX

Quoted 7 lines · Xiaoyao Li

tools/testing/selftests/kvm/lib/x86/ucall.c line 21

Quoted 7 lines
tools/testing/selftests/kvm/lib/x86/ucall.c +7 -0
+
+static u8 vm_type;
+static gpa_t ucall_mmio_gpa;
+
+void ucall_arch_init(struct kvm_vm *vm, gpa_t mmio_gpa)
+{
+	vm_type = vm->type;

vm_type is u8 but vm->type is unsigned long

Quoted 8 lines

tools/testing/selftests/kvm/lib/x86/ucall.c line 22

Quoted 1 line
tools/testing/selftests/kvm/lib/x86/ucall.c +1 -0
+	sync_global_to_guest(vm, vm_type);

It works and it looks simple. But we have the architectural approach to test if a guest is TD guest, by checking the CPUID 0x21.

Since checking CPUID 0x21 is not complex, and as a bonus it can help test if TDX module behaves correctly for CPUID leaf 0x21, I think we should switch to use CPUID 0x21 to check if it is TDX VM in guest code?

Hmm... This makes me wonder if vm->arch.s_bit below could be replaced with the same architectural approach. GPAW is available through TDG.VP.INFO or the initial RBX value. This does require a bit more plumbing though.

Quoted 9 lines

tools/testing/selftests/kvm/lib/x86/ucall.c line 25

Quoted 3 lines
tools/testing/selftests/kvm/lib/x86/ucall.c +2 -0
+	if (is_tdx_vm(vm)) {
+		ucall_mmio_gpa = UCALL_MMIO_GPA | vm->arch.s_bit;

So the passed-in @mmio_gpa is not used. Why cannot use the mmio_gpa,

slot0->region.guest_phys_addr + slot0->region.memory_size

chosen by __vm_create()?

Looks like this was discussed previously [1]. Maybe at least a comment here would help?

[1] https://lore.kernel.org/all/[email protected]/

Quoted 1 line
#90 lore raw

Re: [PATCH v14 21/22] KVM: selftests: Add ucall support for TDX

Quoted 16 lines · Peter Fang
Quoted 7 lines · Xiaoyao Li

tools/testing/selftests/kvm/lib/x86/ucall.c line 21

Quoted 7 lines
tools/testing/selftests/kvm/lib/x86/ucall.c +7 -0
+
+static u8 vm_type;
+static gpa_t ucall_mmio_gpa;
+
+void ucall_arch_init(struct kvm_vm *vm, gpa_t mmio_gpa)
+{
+	vm_type = vm->type;

vm_type is u8 but vm->type is unsigned long

Quoted 5 lines

tools/testing/selftests/kvm/lib/x86/ucall.c line 22

Quoted 1 line
tools/testing/selftests/kvm/lib/x86/ucall.c +1 -0
+	sync_global_to_guest(vm, vm_type);

It works and it looks simple. But we have the architectural approach to test if a guest is TD guest, by checking the CPUID 0x21.

Quoted 8 lines
Quoted 3 lines

Since checking CPUID 0x21 is not complex, and as a bonus it can help test if TDX module behaves correctly for CPUID leaf 0x21, I think we should switch to use CPUID 0x21 to check if it is TDX VM in guest code?

Hmm... This makes me wonder if vm->arch.s_bit below could be replaced with the same architectural approach. GPAW is available through TDG.VP.INFO or the initial RBX value. This does require a bit more plumbing though.

I'm afraid not. Because below is host code, and vm->arch.s_bit is not used in guest code.

Or are suggesting something like dropping the

	if (is_tdx_vm(vm)) {
		ucall_mmio_gpa = UCALL_MMIO_GPA | vm->arch.s_bit;
		sync_global_to_guest(vm, ucall_mmio_gpa);
	}

entirely and use below hardcoded value instead in guest code?

	UCALL_MMIO_GPA | 1 << (GPAW - 1)
Quoted 6 lines
Quoted 6 lines

tools/testing/selftests/kvm/lib/x86/ucall.c line 25

Quoted 3 lines
tools/testing/selftests/kvm/lib/x86/ucall.c +2 -0
+	if (is_tdx_vm(vm)) {
+		ucall_mmio_gpa = UCALL_MMIO_GPA | vm->arch.s_bit;

So the passed-in @mmio_gpa is not used. Why cannot use the mmio_gpa,

Quoted 6 lines
Quoted 3 lines
   slot0->region.guest_phys_addr + slot0->region.memory_size

chosen by __vm_create()?

Looks like this was discussed previously [1]. Maybe at least a comment here would help?

yeah. A comment surely will help.

Quoted 3 lines

[1] https://lore.kernel.org/all/[email protected]/

Quoted 1 line
#91 lore raw

Re: [PATCH v14 21/22] KVM: selftests: Add ucall support for TDX

Quoted 11 lines · Xiaoyao Li
Quoted 5 lines

Hmm... This makes me wonder if vm->arch.s_bit below could be replaced with the same architectural approach. GPAW is available through TDG.VP.INFO or the initial RBX value. This does require a bit more plumbing though.

I'm afraid not. Because below is host code, and vm->arch.s_bit is not used in guest code.

Or are suggesting something like dropping the

Quoted 8 lines
	if (is_tdx_vm(vm)) {
		ucall_mmio_gpa = UCALL_MMIO_GPA | vm->arch.s_bit;
		sync_global_to_guest(vm, ucall_mmio_gpa);
	}

entirely and use below hardcoded value instead in guest code?

	UCALL_MMIO_GPA | 1 << (GPAW - 1)

Yeah this is what I meant. Just drop sync_global_to_guest() entirely and do things like a normal TDX guest would.

Quoted 20 lines
Quoted 7 lines
Quoted 7 lines

tools/testing/selftests/kvm/lib/x86/ucall.c line 25

Quoted 4 lines
tools/testing/selftests/kvm/lib/x86/ucall.c +2 -0
+	if (is_tdx_vm(vm)) {
+		ucall_mmio_gpa = UCALL_MMIO_GPA | vm->arch.s_bit;

So the passed-in @mmio_gpa is not used. Why cannot use the mmio_gpa,

Quoted 6 lines
Quoted 3 lines
   slot0->region.guest_phys_addr + slot0->region.memory_size

chosen by __vm_create()?

Looks like this was discussed previously [1]. Maybe at least a comment here would help?

yeah. A comment surely will help.

Quoted 3 lines

[1] https://lore.kernel.org/all/[email protected]/

Quoted 1 line
#92 lore raw

Re: [PATCH v14 21/22] KVM: selftests: Add ucall support for TDX

Quoted 17 lines · Peter Fang
Quoted 16 lines · Xiaoyao Li
Quoted 5 lines

Hmm... This makes me wonder if vm->arch.s_bit below could be replaced with the same architectural approach. GPAW is available through TDG.VP.INFO or the initial RBX value. This does require a bit more plumbing though.

I'm afraid not. Because below is host code, and vm->arch.s_bit is not used in guest code.

Or are suggesting something like dropping the

	if (is_tdx_vm(vm)) {
		ucall_mmio_gpa = UCALL_MMIO_GPA | vm->arch.s_bit;
		sync_global_to_guest(vm, ucall_mmio_gpa);
	}
Quoted 6 lines
Quoted 3 lines

entirely and use below hardcoded value instead in guest code?

	UCALL_MMIO_GPA | 1 << (GPAW - 1)

Yeah this is what I meant. Just drop sync_global_to_guest() entirely and do things like a normal TDX guest would.

Blech. Every time I come back to this series we're still discussing ucall crud, and "doing thing like a normal TDX guest". Selftests aren't normal guests.

I know I suggested using the HPET base, but I only did so very begrudgingly as I couldn't come up with a better alternative to emulated MMIO, and the end result is quite gross. Not only does the code ignore @mmio_gpa but still obviously use emulated MMIO, it requires synchronizing data to the guest because KVM disallows "private" MMIO.

  void ucall_arch_init(struct kvm_vm *vm, gpa_t mmio_gpa)
  {
         vm_type = vm->type;
         sync_global_to_guest(vm, vm_type);
         if (is_tdx_vm(vm)) {
                 ucall_mmio_gpa = UCALL_MMIO_GPA | vm->arch.s_bit;
                 sync_global_to_guest(vm, ucall_mmio_gpa);
         }

}

Retrieving GPA via TDG.VP.INFO isn't any better, it's still an absurd amount of "work" for something that should be trivial.

Can't we just abuse TDVMCALL_REPORT_FATAL_ERROR? AFAICT, there's no restriction on the data payload, and there's enough space to all but guarantee we'll never get a false positive.

Pulling in Xiaoyao's idea about using CPUID...

Quoted 11 lines

Review

Quoted 4 lines
(quoted diff) +4 -0
+void ucall_arch_init(struct kvm_vm *vm, gpa_t mmio_gpa)
+{
+     vm_type = vm->type;
+     sync_global_to_guest(vm, vm_type);

It works and it looks simple. But we have the architectural approach to test if a guest is TD guest, by checking the CPUID 0x21.

Since checking CPUID 0x21 is not complex, and as a bonus it can help test if TDX module behaves correctly for CPUID leaf 0x21, I think we should switch to use CPUID 0x21 to check if it is TDX VM in guest code?

Absolutely not. It will require at least one an extra VM-Exit for TDX and non-TDX guests alike, and thanks to Intel's wonderful CPUID behavior of having unsupported leaves return the last supported leaf, the guest would have to check CPUID.0x0 and then CPUID.0x21 on modern hardware, i.e. would incur two extra VM-Exits. I don't care about the performance, but from a debug perspective that's going to be awful, as what should be a super simple operation will be polluted with unwanted data.

Stop trying to reinvent the wheel and just use a virtual function table.

I've verified the attached patches don't break non-TDX selftests, someone just needs to test the TDX changes.

From 659487e0a9862900b052836229e3ac366c602f59 Mon Sep 17 00:00:00 2001
From: Sean Christopherson <[email protected]>
Date: Fri, 28 Aug 2026 06:46:51 -0700
Subject: [PATCH 1/3] KVM: selftests: Add support for per-VM ucall ops on x86

Add a layer of indirection to x86's ucall infrastructure to allow wiring up a different set of {do,get}_ucall() operations for TDX VMs. TDX can't use port I/O (at least, not robustly), as the TDX ABI doesn't allow the guest to share arbitrary register state with the host on a PIO exit, and the PIO data payload is limited to 4 bytes, i.e. would potentially truncate the address of the per-ucall structure.

No functional change intended, as the ops are still hardwirte to the common x86 ucall functions.

---
 .../testing/selftests/kvm/include/x86/ucall.h |  6 +----
 tools/testing/selftests/kvm/lib/x86/ucall.c   | 27 +++++++++++++++++--
 2 files changed, 26 insertions(+), 7 deletions(-)
tools/testing/selftests/kvm/include/x86/ucall.h +1 -5
index 0e4950041e3e..3639f03a4da9 100644
@@ -2,12 +2,8 @@
 #ifndef SELFTEST_KVM_UCALL_H
 #define SELFTEST_KVM_UCALL_H
 
-#include "kvm_util.h"
+#include "linux/kvm.h"
 
 #define UCALL_EXIT_REASON       KVM_EXIT_IO
 
-static inline void ucall_arch_init(struct kvm_vm *vm, gpa_t mmio_gpa)
-{
-}
-
 #endif
tools/testing/selftests/kvm/lib/x86/ucall.c +25 -2
index e7dd5791959b..444d0f0ba138 100644
@@ -8,7 +8,7 @@
 
 #define UCALL_PIO_PORT ((u16)0x1000)
 
-void ucall_arch_do_ucall(gva_t uc)
+static void ucall_x86_do_ucall(gva_t uc)
 {
 	/*
 	 * FIXME: Revert this hack (the entire commit that added it) once nVMX
@@ -42,7 +42,7 @@ void ucall_arch_do_ucall(gva_t uc)
 		     HORRIFIC_L2_UCALL_CLOBBER_HACK);
 }
 
-void *ucall_arch_get_ucall(struct kvm_vcpu *vcpu)
+static void *ucall_x86_get_ucall(struct kvm_vcpu *vcpu)
 {
 	struct kvm_run *run = vcpu->run;
 
@@ -54,3 +54,26 @@ void *ucall_arch_get_ucall(struct kvm_vcpu *vcpu)
 	}
 	return NULL;
 }
+
+static struct {
+	void (*do_ucall)(gva_t uc);
+	void *(*get_ucall)(struct kvm_vcpu *vcpu);
+} ucall_x86_ops = {
+	.do_ucall = ucall_x86_do_ucall,
+	.get_ucall = ucall_x86_get_ucall,
+};
+
+void ucall_arch_init(struct kvm_vm *vm, gpa_t mmio_gpa)
+{
+	sync_global_to_guest(vm, ucall_x86_ops);
+}
+
+void ucall_arch_do_ucall(gva_t uc)
+{
+	return ucall_x86_ops.do_ucall(uc);
+}
+
+void *ucall_arch_get_ucall(struct kvm_vcpu *vcpu)
+{
+	return ucall_x86_ops.get_ucall(vcpu);
+}

base-commit: 8ab7454faa8be2d6c1fc29c857b9f9611ddb174b

Signature · 1 line
2.55.0.897.gb25b4bd76c-goog

From 60f3e988c65048c63217ec157e9ec228a4c484b7 Mon Sep 17 00:00:00 2001
From: Sean Christopherson <[email protected]>
Date: Fri, 28 Aug 2026 06:52:56 -0700
Subject: [PATCH 2/3] KVM: selftests: Add support for TDX ucalls, via
TDVMCALL_REPORT_FATAL_ERROR

Add support for doing ucalls on TDX by abusing TDVMCALL_REPORT_FATAL_ERROR to pass the address of the payload to the host. The "fatal error" TDVMCALL is perfectly suited for passing information to host userspace, is both the TDX Module and KVM allow the guest to pass (almost) all registers to the host, i.e. provide enough of a data payload to make a collision with a real fatal error practically impossible.

TDX can't use port I/O, as the TDX ABI doesn't allow the guest to share arbitrary register state with the host on a port I/O exit, and the port I/O data payload is limited to 4 bytes, i.e. would potentially truncate the ucall address.

Alternatively, TDX could use MMIO, but using a magic emulated MMIO address is fragile (see the TODO in __vm_create()), especially for TDX since TDX doesn't support read-only memslots, i.e. doesn't have line of sight towards addressing the TODO. E.g. TDX could hardcode the address to something that is all but guaranteed to be unused on x86, e.g. the I/O APIC base address or the HPET address, but that doesn't truly address the fragility concerns, and it's ugly because ucall_arch_init() would completely ignore the passed in @mmio_gpa despite obviously utilizing emulated MMIO.

---
 .../selftests/kvm/include/x86/tdx/tdx.h       |  7 +++++
 tools/testing/selftests/kvm/lib/x86/ucall.c   | 27 ++++++++++++++++++-
 2 files changed, 33 insertions(+), 1 deletion(-)
tools/testing/selftests/kvm/include/x86/tdx/tdx.h +7 -0
index 6355a30bb47f..6582e6c99697 100644
@@ -4,6 +4,13 @@
 
 #include <linux/types.h>
 
+/* 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
+#define TDVMCALL_SETUP_EVENT_NOTIFY_INTERRUPT	0x10004
+
 #define TDG_VP_VMCALL_VE_REQUEST_MMIO    48
 #define TDVMCALL_MMIO_WRITE		  1
 
tools/testing/selftests/kvm/lib/x86/ucall.c +26 -1
index 444d0f0ba138..93686e1dd3f3 100644
@@ -5,8 +5,28 @@
  * Copyright (C) 2018, Red Hat, Inc.
  */
 #include "kvm_util.h"
+#include "tdx/tdx.h"
+#include "tdx/tdx_util.h"
 
-#define UCALL_PIO_PORT ((u16)0x1000)
+#define UCALL_PIO_PORT		((u16)0x1000)
+#define UCALL_TDX_MAGIC		0xabacadabaULL
+
+static void ucall_tdx_do_ucall(gva_t uc)
+{
+	__tdcall(TDVMCALL_REPORT_FATAL_ERROR, UCALL_TDX_MAGIC, uc, 0, 0);
+}
+
+static void *ucall_tdx_get_ucall(struct kvm_vcpu *vcpu)
+{
+	struct kvm_run *run = vcpu->run;
+
+	if (run->exit_reason == KVM_EXIT_SYSTEM_EVENT &&
+	    run->system_event.type == KVM_SYSTEM_EVENT_TDX_FATAL &&
+	    run->system_event.data[12] == UCALL_TDX_MAGIC)
+		return (void *)(run->system_event.data[13]);
+
+	return NULL;
+}
 
 static void ucall_x86_do_ucall(gva_t uc)
 {
@@ -65,6 +85,11 @@ static struct {
 
 void ucall_arch_init(struct kvm_vm *vm, gpa_t mmio_gpa)
 {
+	if (is_tdx_vm(vm)) {
+		ucall_x86_ops.do_ucall = ucall_tdx_do_ucall;
+		ucall_x86_ops.get_ucall = ucall_tdx_get_ucall;
+	}
+
 	sync_global_to_guest(vm, ucall_x86_ops);
 }
 
Signature · 1 line
2.55.0.897.gb25b4bd76c-goog
#93 lore raw

Re: [PATCH v14 21/22] KVM: selftests: Add ucall support for TDX

Quoted 43 lines · Sean Christopherson
Quoted 17 lines · Peter Fang
Quoted 16 lines · Xiaoyao Li
Quoted 5 lines

Hmm... This makes me wonder if vm->arch.s_bit below could be replaced with the same architectural approach. GPAW is available through TDG.VP.INFO or the initial RBX value. This does require a bit more plumbing though.

I'm afraid not. Because below is host code, and vm->arch.s_bit is not used in guest code.

Or are suggesting something like dropping the

	if (is_tdx_vm(vm)) {
		ucall_mmio_gpa = UCALL_MMIO_GPA | vm->arch.s_bit;
		sync_global_to_guest(vm, ucall_mmio_gpa);
	}
Quoted 6 lines
Quoted 3 lines

entirely and use below hardcoded value instead in guest code?

	UCALL_MMIO_GPA | 1 << (GPAW - 1)

Yeah this is what I meant. Just drop sync_global_to_guest() entirely and do things like a normal TDX guest would.

Blech. Every time I come back to this series we're still discussing ucall crud, and "doing thing like a normal TDX guest". Selftests aren't normal guests.

I know I suggested using the HPET base, but I only did so very begrudgingly as I couldn't come up with a better alternative to emulated MMIO, and the end result is quite gross. Not only does the code ignore @mmio_gpa but still obviously use emulated MMIO, it requires synchronizing data to the guest because KVM disallows "private" MMIO.

  void ucall_arch_init(struct kvm_vm *vm, gpa_t mmio_gpa)
  {
         vm_type = vm->type;
         sync_global_to_guest(vm, vm_type);
         if (is_tdx_vm(vm)) {
                 ucall_mmio_gpa = UCALL_MMIO_GPA | vm->arch.s_bit;
                 sync_global_to_guest(vm, ucall_mmio_gpa);
         }
Quoted 8 lines

}

Retrieving GPA via TDG.VP.INFO isn't any better, it's still an absurd amount of "work" for something that should be trivial.

Can't we just abuse TDVMCALL_REPORT_FATAL_ERROR? AFAICT, there's no restriction on the data payload, and there's enough space to all but guarantee we'll never get a false positive.

Ohh interesting idea to use TDVMCALL_REPORT_FATAL_ERROR to pass through data. Yeah this looks good to me.

Quoted 1 line
#94 lore raw

Re: [PATCH v14 21/22] KVM: selftests: Add ucall support for TDX

In reply to Sean Christopherson
Quoted 66 lines · Sean Christopherson
Quoted 17 lines · Peter Fang
Quoted 16 lines · Xiaoyao Li
Quoted 5 lines

Hmm... This makes me wonder if vm->arch.s_bit below could be replaced with the same architectural approach. GPAW is available through TDG.VP.INFO or the initial RBX value. This does require a bit more plumbing though.

I'm afraid not. Because below is host code, and vm->arch.s_bit is not used in guest code.

Or are suggesting something like dropping the

	if (is_tdx_vm(vm)) {
		ucall_mmio_gpa = UCALL_MMIO_GPA | vm->arch.s_bit;
		sync_global_to_guest(vm, ucall_mmio_gpa);
	}
Quoted 6 lines
Quoted 3 lines

entirely and use below hardcoded value instead in guest code?

	UCALL_MMIO_GPA | 1 << (GPAW - 1)

Yeah this is what I meant. Just drop sync_global_to_guest() entirely and do things like a normal TDX guest would.

Blech. Every time I come back to this series we're still discussing ucall crud, and "doing thing like a normal TDX guest". Selftests aren't normal guests.

I know I suggested using the HPET base, but I only did so very begrudgingly as I couldn't come up with a better alternative to emulated MMIO, and the end result is quite gross. Not only does the code ignore @mmio_gpa but still obviously use emulated MMIO, it requires synchronizing data to the guest because KVM disallows "private" MMIO.

  void ucall_arch_init(struct kvm_vm *vm, gpa_t mmio_gpa)
  {
         vm_type = vm->type;
         sync_global_to_guest(vm, vm_type);
         if (is_tdx_vm(vm)) {
                 ucall_mmio_gpa = UCALL_MMIO_GPA | vm->arch.s_bit;
                 sync_global_to_guest(vm, ucall_mmio_gpa);
         }

}

Retrieving GPA via TDG.VP.INFO isn't any better, it's still an absurd amount of "work" for something that should be trivial.

Can't we just abuse TDVMCALL_REPORT_FATAL_ERROR? AFAICT, there's no restriction on the data payload, and there's enough space to all but guarantee we'll never get a false positive.

Pulling in Xiaoyao's idea about using CPUID...

Quoted 11 lines

tools/testing/selftests/kvm/lib/x86/ucall.c void *ucall_arch_get_ucall(struct kvm_vcpu *vcpu)

Quoted 4 lines
tools/testing/selftests/kvm/lib/x86/ucall.c +4 -0
+void ucall_arch_init(struct kvm_vm *vm, gpa_t mmio_gpa)
+{
+     vm_type = vm->type;
+     sync_global_to_guest(vm, vm_type);

It works and it looks simple. But we have the architectural approach to test if a guest is TD guest, by checking the CPUID 0x21.

Since checking CPUID 0x21 is not complex, and as a bonus it can help test if TDX module behaves correctly for CPUID leaf 0x21, I think we should switch to use CPUID 0x21 to check if it is TDX VM in guest code?

Quoted 6 lines

Absolutely not. It will require at least one an extra VM-Exit for TDX and non-TDX guests alike, and thanks to Intel's wonderful CPUID behavior of having unsupported leaves return the last supported leaf, the guest would have to check CPUID.0x0 and then CPUID.0x21 on modern hardware, i.e. would incur two extra VM-Exits. I don't care about the performance, but from a debug perspective that's going to be awful, as what should be a super simple operation will be polluted with unwanted data.

As you said, "Selftests aren't normal guests". So the guest code doesn't need to check CPUID.0x0 to get maximum supported leaf before CPUID.0x21.

And for TDX, the CPUID doesn't cause VM-Exit to KVM, it only exits to TDX module, which is transparent to KVM.

Anyway, if we go with the TDVMCALL_REPORT_FATAL_ERROR as you proposed, we don't need the logic to check if it is a TDX VM in the guest.

Quoted 4 lines

Stop trying to reinvent the wheel and just use a virtual function table.

I've verified the attached patches don't break non-TDX selftests, someone just needs to test the TDX changes.

I test it and it works.

author
#95 lore raw

PATCH v14 22/22 KVM: selftests: Add TDX lifecycle test

In reply to Lisa Wang

From: Sagi Shahar <[email protected]>

Adding a test to verify TDX lifecycle by creating a simple TDX VM.

---
 tools/testing/selftests/kvm/Makefile.kvm           |  1 +
 .../testing/selftests/kvm/include/x86/processor.h  |  1 +
 .../selftests/kvm/include/x86/tdx/tdx_util.h       |  5 ++++
 tools/testing/selftests/kvm/x86/tdx_vm_test.c      | 33 ++++++++++++++++++++++
 4 files changed, 40 insertions(+)
tools/testing/selftests/kvm/Makefile.kvm +1 -0
index 28b33ed8c2e6..3561f9284e6f 100644
@@ -167,6 +167,7 @@ TEST_GEN_PROGS_x86 += rseq_test
 TEST_GEN_PROGS_x86 += steal_time
 TEST_GEN_PROGS_x86 += system_counter_offset_test
 TEST_GEN_PROGS_x86 += pre_fault_memory_test
+TEST_GEN_PROGS_x86 += x86/tdx_vm_test
 
 # Compiled outputs used by test targets
 TEST_GEN_PROGS_EXTENDED_x86 += x86/nx_huge_pages_test
tools/testing/selftests/kvm/include/x86/processor.h +1 -0
index ff3219edab44..fa112a1c1c84 100644
@@ -372,6 +372,7 @@ static inline unsigned int x86_model(unsigned int eax)
 #define VM_SHAPE_SEV		VM_TYPE(KVM_X86_SEV_VM)
 #define VM_SHAPE_SEV_ES		VM_TYPE(KVM_X86_SEV_ES_VM)
 #define VM_SHAPE_SNP		VM_TYPE(KVM_X86_SNP_VM)
+#define VM_SHAPE_TDX		VM_TYPE(KVM_X86_TDX_VM)
 
 #define PHYSICAL_PAGE_MASK      GENMASK_ULL(51, 12)
 
tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h +5 -0
index 35f0b2b7ac40..9a882644a48d 100644
@@ -11,6 +11,11 @@ static inline bool is_tdx_vm(struct kvm_vm *vm)
 	return vm->type == KVM_X86_TDX_VM;
 }
 
+static inline bool is_tdx_supported(void)
+{
+	return !!(kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(KVM_X86_TDX_VM));
+}
+
 /*
  * TDX ioctls
  * Use underscores to avoid collisions with struct member names.
tools/testing/selftests/kvm/x86/tdx_vm_test.c new file +33 -0
new file mode 100644
index 000000000000..7cdcaf33b585
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include "processor.h"
+#include "kvm_util.h"
+#include "tdx/tdx_util.h"
+#include "ucall_common.h"
+#include "kselftest_harness.h"
+
+static void guest_code_lifecycle(void)
+{
+	GUEST_DONE();
+}
+
+TEST(verify_td_lifecycle)
+{
+	struct kvm_vcpu *vcpu;
+	struct kvm_vm *vm;
+	struct ucall uc;
+
+	vm = vm_create_shape_with_one_vcpu(VM_SHAPE_TDX, &vcpu,
+					   guest_code_lifecycle);
+
+	vcpu_run(vcpu);
+	TEST_ASSERT_EQ(get_ucall(vcpu, &uc), UCALL_DONE);
+
+	kvm_vm_free(vm);
+}
+
+int main(int argc, char **argv)
+{
+	TEST_REQUIRE(is_tdx_supported());
+	return test_harness_run(argc, argv);
+}
Signature · 1 line
2.55.0.229.g6434b31f56-goog
Reviewed-by on v14 22/22
#96 lore raw

Re: [PATCH v14 22/22] KVM: selftests: Add TDX lifecycle test

Quoted 6 lines · Lisa Wang

From: Sagi Shahar <[email protected]>

Adding a test to verify TDX lifecycle by creating a simple TDX VM.

Quoted 87 lines
---
  tools/testing/selftests/kvm/Makefile.kvm           |  1 +
  .../testing/selftests/kvm/include/x86/processor.h  |  1 +
  .../selftests/kvm/include/x86/tdx/tdx_util.h       |  5 ++++
  tools/testing/selftests/kvm/x86/tdx_vm_test.c      | 33 ++++++++++++++++++++++
  4 files changed, 40 insertions(+)
tools/testing/selftests/kvm/Makefile.kvm +1 -0
index 28b33ed8c2e6..3561f9284e6f 100644
@@ -167,6 +167,7 @@ TEST_GEN_PROGS_x86 += rseq_test
  TEST_GEN_PROGS_x86 += steal_time
  TEST_GEN_PROGS_x86 += system_counter_offset_test
  TEST_GEN_PROGS_x86 += pre_fault_memory_test
+TEST_GEN_PROGS_x86 += x86/tdx_vm_test
 
  # Compiled outputs used by test targets
  TEST_GEN_PROGS_EXTENDED_x86 += x86/nx_huge_pages_test
tools/testing/selftests/kvm/include/x86/processor.h +1 -0
index ff3219edab44..fa112a1c1c84 100644
@@ -372,6 +372,7 @@ static inline unsigned int x86_model(unsigned int eax)
  #define VM_SHAPE_SEV		VM_TYPE(KVM_X86_SEV_VM)
  #define VM_SHAPE_SEV_ES		VM_TYPE(KVM_X86_SEV_ES_VM)
  #define VM_SHAPE_SNP		VM_TYPE(KVM_X86_SNP_VM)
+#define VM_SHAPE_TDX		VM_TYPE(KVM_X86_TDX_VM)
 
  #define PHYSICAL_PAGE_MASK      GENMASK_ULL(51, 12)
 
tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h +5 -0
index 35f0b2b7ac40..9a882644a48d 100644
@@ -11,6 +11,11 @@ static inline bool is_tdx_vm(struct kvm_vm *vm)
  	return vm->type == KVM_X86_TDX_VM;
  }
 
+static inline bool is_tdx_supported(void)
+{
+	return !!(kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(KVM_X86_TDX_VM));
+}
+
  /*
   * TDX ioctls
   * Use underscores to avoid collisions with struct member names.
tools/testing/selftests/kvm/x86/tdx_vm_test.c new file +33 -0
new file mode 100644
index 000000000000..7cdcaf33b585
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include "processor.h"
+#include "kvm_util.h"
+#include "tdx/tdx_util.h"
+#include "ucall_common.h"
+#include "kselftest_harness.h"
+
+static void guest_code_lifecycle(void)
+{
+	GUEST_DONE();
+}
+
+TEST(verify_td_lifecycle)
+{
+	struct kvm_vcpu *vcpu;
+	struct kvm_vm *vm;
+	struct ucall uc;
+
+	vm = vm_create_shape_with_one_vcpu(VM_SHAPE_TDX, &vcpu,
+					   guest_code_lifecycle);
+
+	vcpu_run(vcpu);
+	TEST_ASSERT_EQ(get_ucall(vcpu, &uc), UCALL_DONE);
+
+	kvm_vm_free(vm);
+}
+
+int main(int argc, char **argv)
+{
+	TEST_REQUIRE(is_tdx_supported());
+	return test_harness_run(argc, argv);
+}
#97 lore raw

Re: [PATCH v14 22/22] KVM: selftests: Add TDX lifecycle test

In reply to Lisa Wang
Quoted 18 lines · Lisa Wang

From: Sagi Shahar <[email protected]>

Adding a test to verify TDX lifecycle by creating a simple TDX VM.

---
 tools/testing/selftests/kvm/Makefile.kvm           |  1 +
 .../testing/selftests/kvm/include/x86/processor.h  |  1 +
 .../selftests/kvm/include/x86/tdx/tdx_util.h       |  5 ++++
 tools/testing/selftests/kvm/x86/tdx_vm_test.c      | 33 ++++++++++++++++++++++
 4 files changed, 40 insertions(+)
tools/testing/selftests/kvm/Makefile.kvm +0 -0
index 28b33ed8c2e6..3561f9284e6f 100644

tools/testing/selftests/kvm/Makefile.kvm TEST_GEN_PROGS_x86 += rseq_test · line 170

Quoted 4 lines
tools/testing/selftests/kvm/Makefile.kvm +1 -0
@@ -167,6 +167,7 @@ TEST_GEN_PROGS_x86 += rseq_test
 TEST_GEN_PROGS_x86 += steal_time
 TEST_GEN_PROGS_x86 += system_counter_offset_test
 TEST_GEN_PROGS_x86 += pre_fault_memory_test
+TEST_GEN_PROGS_x86 += x86/tdx_vm_test

Nit: move this up to keep the x86/* grouping

Quoted 3 lines
tools/testing/selftests/kvm/Makefile.kvm +0 -0
 # Compiled outputs used by test targets
 TEST_GEN_PROGS_EXTENDED_x86 += x86/nx_huge_pages_test
#98 lore raw
In reply to Lisa Wang
Quoted 5 lines · Lisa Wang

v14 revision for TDX KVM selftests based on kvm/next and guest_memfd:
In-place conversion support[1]. This series needs some changes from in-place conversion v9. For ease of testing, an extra hack commit has been added. The complete code is available at:
https://github.com/googleprodkernel/linux-cc/commits/tdx-selftests-v14

Are you able to get sashiko review on this? Or did you internally somehow? It seems the public sashiko cannot figure out this stack.

#99 lore raw
Quoted 9 lines · Rick Edgecombe
Quoted 5 lines · Lisa Wang

v14 revision for TDX KVM selftests based on kvm/next and guest_memfd:
In-place conversion support[1]. This series needs some changes from in-place conversion v9. For ease of testing, an extra hack commit has been added. The complete code is available at:
https://github.com/googleprodkernel/linux-cc/commits/tdx-selftests-v14

Are you able to get sashiko review on this? Or did you internally somehow? It seems the public sashiko cannot figure out this stack.

Actually why is this on top of in-place conversion? I thought the idea was to break the series apart to get something basic upstream:
https://lore.kernel.org/all/[email protected]/

Adding in-place conversion tests seems to be going in the opposite direction.

Also, the branch does not build for me:
/usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:26:
undefined reference to `TD_BOOT_PARAMETERS_PER_VCPU' /usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:30:
undefined reference to `TD_PER_VCPU_PARAMETERS_ESP_GVA' /usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:33:
undefined reference to `TD_BOOT_PARAMETERS_GDT'
/usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:37:
undefined reference to `TD_BOOT_PARAMETERS_IDT'
/usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:44:
undefined reference to `TD_BOOT_PARAMETERS_CR4'
/usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:46:
undefined reference to `TD_BOOT_PARAMETERS_CR3'
/usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:48:
undefined reference to `TD_BOOT_PARAMETERS_CR0'
/usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:54:
undefined reference to `TD_PER_VCPU_PARAMETERS_GUEST_CODE'
collect2: error: ld returned 1 exit status
make: *** [Makefile.kvm:351:
.../linux/tools/testing/selftests/kvm/demand_paging_test] Error 1
make: *** Waiting for unfinished jobs....
/usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.o: in function `td_boot':
.../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:24: undefined reference to `SIZEOF_TD_PER_VCPU_PARAMETERS'
/usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:26:
undefined reference to `TD_BOOT_PARAMETERS_PER_VCPU' /usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:30:
undefined reference to `TD_PER_VCPU_PARAMETERS_ESP_GVA' /usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:33:
undefined reference to `TD_BOOT_PARAMETERS_GDT'
/usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:37:
undefined reference to `TD_BOOT_PARAMETERS_IDT'
/usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:44:
undefined reference to `TD_BOOT_PARAMETERS_CR4'
/usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:46:
undefined reference to `TD_BOOT_PARAMETERS_CR3'
/usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:48:
undefined reference to `TD_BOOT_PARAMETERS_CR0'
/usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:54:
undefined reference to `TD_PER_VCPU_PARAMETERS_GUEST_CODE'
collect2: error: ld returned 1 exit status
make: *** [Makefile.kvm:351:
.../linux/tools/testing/selftests/kvm/dirty_log_test] Error 1

If I apply these patches on 8cf3969a0b7d4 from the branch, it builds though. It looks like a few changes are missing from the branch:

(quoted diff) +0 -0

b/tools/testing/selftests/kvm/guest_memfd_test.c
index d6528c6f5e031..b099b09cbae3d 100644

tools/testing/selftests/kvm/guest_memfd_test.c +1 -1
@@ -14,7 +14,7 @@
 #include <linux/bitmap.h>
 #include <linux/falloc.h>
 #include <linux/sizes.h>
-#include <sys/mman.h>
+#include <linux/mman.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 
(quoted diff) +0 -0

b/tools/testing/selftests/kvm/lib/x86/tdx/td_boot_offsets.c index b81cd16198ecf..7f76a3585b99a 100644

tools/testing/selftests/kvm/lib/x86/tdx/td_boot_offsets.c +1 -1
@@ -5,7 +5,7 @@
 
 #include "tdx/td_boot.h"
 
-static void __used common(void)
+static void __attribute__((used)) common(void)
 {
        OFFSET(TD_BOOT_PARAMETERS_CR0, td_boot_parameters, cr0);
        OFFSET(TD_BOOT_PARAMETERS_CR3, td_boot_parameters, cr3);
#100 lore raw
Quoted 8 lines · Rick P
Quoted 7 lines · Rick Edgecombe
Quoted 5 lines · Lisa Wang

v14 revision for TDX KVM selftests based on kvm/next and guest_memfd:
In-place conversion support[1]. This series needs some changes from in-place conversion v9. For ease of testing, an extra hack commit has been added. The complete code is available at:
https://github.com/googleprodkernel/linux-cc/commits/tdx-selftests-v14

Quoted 9 lines
Quoted 2 lines

Are you able to get sashiko review on this? Or did you internally somehow? It seems the public sashiko cannot figure out this stack.

Actually why is this on top of in-place conversion? I thought the idea was to break the series apart to get something basic upstream:
https://lore.kernel.org/all/[email protected]/

Adding in-place conversion tests seems to be going in the opposite direction.

We wanted to have this series based on in-place conversion since non-in-place conversion is on a deprecation path. Also, it makes the set up easier :)

At this point I don't think this series is going to make it for 7.3, so at best it makes it for 7.4 (together with in-place conversion), which I think works out?

Sean, let us know what you think about scheduling. We can rebase without in-place conversion if necessary.

Quoted 55 lines

Also, the branch does not build for me:
/usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:26:
undefined reference to `TD_BOOT_PARAMETERS_PER_VCPU' /usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:30:
undefined reference to `TD_PER_VCPU_PARAMETERS_ESP_GVA' /usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:33:
undefined reference to `TD_BOOT_PARAMETERS_GDT'
/usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:37:
undefined reference to `TD_BOOT_PARAMETERS_IDT'
/usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:44:
undefined reference to `TD_BOOT_PARAMETERS_CR4'
/usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:46:
undefined reference to `TD_BOOT_PARAMETERS_CR3'
/usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:48:
undefined reference to `TD_BOOT_PARAMETERS_CR0'
/usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:54:
undefined reference to `TD_PER_VCPU_PARAMETERS_GUEST_CODE'
collect2: error: ld returned 1 exit status
make: *** [Makefile.kvm:351:
.../linux/tools/testing/selftests/kvm/demand_paging_test] Error 1
make: *** Waiting for unfinished jobs....
/usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.o: in function `td_boot':
.../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:24: undefined reference to `SIZEOF_TD_PER_VCPU_PARAMETERS'
/usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:26:
undefined reference to `TD_BOOT_PARAMETERS_PER_VCPU' /usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:30:
undefined reference to `TD_PER_VCPU_PARAMETERS_ESP_GVA' /usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:33:
undefined reference to `TD_BOOT_PARAMETERS_GDT'
/usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:37:
undefined reference to `TD_BOOT_PARAMETERS_IDT'
/usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:44:
undefined reference to `TD_BOOT_PARAMETERS_CR4'
/usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:46:
undefined reference to `TD_BOOT_PARAMETERS_CR3'
/usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:48:
undefined reference to `TD_BOOT_PARAMETERS_CR0'
/usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:54:
undefined reference to `TD_PER_VCPU_PARAMETERS_GUEST_CODE'
collect2: error: ld returned 1 exit status
make: *** [Makefile.kvm:351:
.../linux/tools/testing/selftests/kvm/dirty_log_test] Error 1

If I apply these patches on 8cf3969a0b7d4 from the branch, it builds though. It looks like a few changes are missing from the branch:

tools/testing/selftests/kvm/guest_memfd_test.c +0 -0
index d6528c6f5e031..b099b09cbae3d 100644

tools/testing/selftests/kvm/guest_memfd_test.c line 17

Quoted 5 lines
tools/testing/selftests/kvm/guest_memfd_test.c +1 -1
@@ -14,7 +14,7 @@
 #include <linux/bitmap.h>
 #include <linux/falloc.h>
 #include <linux/sizes.h>
-#include <sys/mman.h>
+#include <linux/mman.h>

I recall a similar fix [1] you posted, I think both Lisa and I are relying on some LSP auto-including, we'll adopt <linux/mman.h>. Let me know you know a good way to configure the auto-includes.

[1] https://lore.kernel.org/all/[email protected]/

What compiler/version are you using? We'd like to reproduce.

Quoted 9 lines
tools/testing/selftests/kvm/guest_memfd_test.c +0 -0
 #include <sys/types.h>
 #include <sys/stat.h>
 
tools/testing/selftests/kvm/lib/x86/tdx/td_boot_offsets.c +0 -0
index b81cd16198ecf..7f76a3585b99a 100644

tools/testing/selftests/kvm/lib/x86/tdx/td_boot_offsets.c line 8

Quoted 5 lines
tools/testing/selftests/kvm/lib/x86/tdx/td_boot_offsets.c +1 -1
@@ -5,7 +5,7 @@
 
 #include "tdx/td_boot.h"
 
-static void __used common(void)
+static void __attribute__((used)) common(void)

Not sure about this, I'll let Lisa look at this in more detail. I wonder if declaring __used is necessary at all.

Quoted 3 lines
tools/testing/selftests/kvm/lib/x86/tdx/td_boot_offsets.c +0 -0
 {
        OFFSET(TD_BOOT_PARAMETERS_CR0, td_boot_parameters, cr0);
        OFFSET(TD_BOOT_PARAMETERS_CR3, td_boot_parameters, cr3);
#101 lore raw
Quoted 17 lines · Ackerley Tng
Quoted 15 lines · Rick P
Quoted 9 lines · Rick Edgecombe
Quoted 5 lines · Lisa Wang

v14 revision for TDX KVM selftests based on kvm/next and guest_memfd:
In-place conversion support[1]. This series needs some changes from in-place conversion v9. For ease of testing, an extra hack commit has been added. The complete code is available at:
https://github.com/googleprodkernel/linux-cc/commits/tdx-selftests-v14

Are you able to get sashiko review on this? Or did you internally somehow? It seems the public sashiko cannot figure out this stack.

Actually why is this on top of in-place conversion? I thought the idea was to break the series apart to get something basic upstream:
https://lore.kernel.org/all/[email protected]/

Quoted 6 lines
Quoted 3 lines

Adding in-place conversion tests seems to be going in the opposite direction.

We wanted to have this series based on in-place conversion since non-in-place conversion is on a deprecation path.

Hmm, what kind of timeline do you see for the old interface actually getting yanked out of the kernel? I was imagining it would be there for many years at minimum, in which case it would need tests too.

The level of dependencies we have on coco stuff is pretty unfortunate I think. Considering these tests should have ideally quickly followed the TDX base upstreaming, it would be nice if we could avoid it here.

Quoted 1 line

Also, it makes the set up easier :)

Hmm, yea maybe. I'm just wishing to unblock this finally. And so it seems like adding new roadblocks.

Quoted 73 lines

At this point I don't think this series is going to make it for 7.3, so at best it makes it for 7.4 (together with in-place conversion), which I think works out?

Sean, let us know what you think about scheduling. We can rebase without in-place conversion if necessary.

Quoted 55 lines

Also, the branch does not build for me:
/usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:26:
undefined reference to `TD_BOOT_PARAMETERS_PER_VCPU' /usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:30:
undefined reference to `TD_PER_VCPU_PARAMETERS_ESP_GVA' /usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:33:
undefined reference to `TD_BOOT_PARAMETERS_GDT'
/usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:37:
undefined reference to `TD_BOOT_PARAMETERS_IDT'
/usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:44:
undefined reference to `TD_BOOT_PARAMETERS_CR4'
/usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:46:
undefined reference to `TD_BOOT_PARAMETERS_CR3'
/usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:48:
undefined reference to `TD_BOOT_PARAMETERS_CR0'
/usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:54:
undefined reference to `TD_PER_VCPU_PARAMETERS_GUEST_CODE'
collect2: error: ld returned 1 exit status
make: *** [Makefile.kvm:351:
.../linux/tools/testing/selftests/kvm/demand_paging_test] Error 1
make: *** Waiting for unfinished jobs....
/usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.o: in function `td_boot':
.../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:24: undefined reference to `SIZEOF_TD_PER_VCPU_PARAMETERS'
/usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:26:
undefined reference to `TD_BOOT_PARAMETERS_PER_VCPU' /usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:30:
undefined reference to `TD_PER_VCPU_PARAMETERS_ESP_GVA' /usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:33:
undefined reference to `TD_BOOT_PARAMETERS_GDT'
/usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:37:
undefined reference to `TD_BOOT_PARAMETERS_IDT'
/usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:44:
undefined reference to `TD_BOOT_PARAMETERS_CR4'
/usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:46:
undefined reference to `TD_BOOT_PARAMETERS_CR3'
/usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:48:
undefined reference to `TD_BOOT_PARAMETERS_CR0'
/usr/bin/ld: .../linux/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S:54:
undefined reference to `TD_PER_VCPU_PARAMETERS_GUEST_CODE'
collect2: error: ld returned 1 exit status
make: *** [Makefile.kvm:351:
.../linux/tools/testing/selftests/kvm/dirty_log_test] Error 1

If I apply these patches on 8cf3969a0b7d4 from the branch, it builds though. It looks like a few changes are missing from the branch:

tools/testing/selftests/kvm/guest_memfd_test.c +0 -0
index d6528c6f5e031..b099b09cbae3d 100644

tools/testing/selftests/kvm/guest_memfd_test.c line 17

Quoted 5 lines
tools/testing/selftests/kvm/guest_memfd_test.c +1 -1
@@ -14,7 +14,7 @@
  #include <linux/bitmap.h>
  #include <linux/falloc.h>
  #include <linux/sizes.h>
-#include <sys/mman.h>
+#include <linux/mman.h>

I recall a similar fix [1] you posted, I think both Lisa and I are relying on some LSP auto-including, we'll adopt <linux/mman.h>. Let me know you know a good way to configure the auto-includes.

Quoted 4 lines

[1]
https://lore.kernel.org/all/[email protected]/

What compiler/version are you using? We'd like to reproduce.

I don't have access to that computer today. I'll report back when I do. But it seems the issue was caught and fixed in these posted patches. Just not in the linked branch. So somehow it was noticed?

Quoted 22 lines
Quoted 10 lines
tools/testing/selftests/kvm/guest_memfd_test.c +0 -0
  #include <sys/types.h>
  #include <sys/stat.h>
 
tools/testing/selftests/kvm/lib/x86/tdx/td_boot_offsets.c +0 -0
index b81cd16198ecf..7f76a3585b99a 100644

tools/testing/selftests/kvm/lib/x86/tdx/td_boot_offsets.c line 8

Quoted 5 lines
tools/testing/selftests/kvm/lib/x86/tdx/td_boot_offsets.c +1 -1
@@ -5,7 +5,7 @@
 
  #include "tdx/td_boot.h"
 
-static void __used common(void)
+static void __attribute__((used)) common(void)

Not sure about this, I'll let Lisa look at this in more detail. I wonder if declaring __used is necessary at all.

Quoted 3 lines
tools/testing/selftests/kvm/lib/x86/tdx/td_boot_offsets.c +0 -0
  {
         OFFSET(TD_BOOT_PARAMETERS_CR0, td_boot_parameters, cr0);
         OFFSET(TD_BOOT_PARAMETERS_CR3, td_boot_parameters, cr3);
#102 lore raw
Quoted 11 lines · Rick P
Quoted 10 lines
Quoted 7 lines

Actually why is this on top of in-place conversion? I thought the idea was to break the series apart to get something basic upstream:
https://lore.kernel.org/all/[email protected]/

Adding in-place conversion tests seems to be going in the opposite direction.

We wanted to have this series based on in-place conversion since non-in-place conversion is on a deprecation path.

Quoted 7 lines

Hmm, what kind of timeline do you see for the old interface actually getting yanked out of the kernel? I was imagining it would be there for many years at minimum, in which case it would need tests too.

The level of dependencies we have on coco stuff is pretty unfortunate I think. Considering these tests should have ideally quickly followed the TDX base upstreaming, it would be nice if we could avoid it here.

I saw the new version is also based on in-place conversion (which is now headed upstream, congrats!). But can we talk about the legacy deprecation? Is there a plan to actually deprecate and remove it, or even move qemu off of the old interface?

Otherwise, I disagree with the plan to drop the tests for the old interface. That said I'd would be thrilled to have anything upstream, so if the set can be smaller or faster that way, then I agree with the change.

But in general, I'd like to know the plan for the old interface.

#103 lore raw
Quoted 20 lines · Rick P
Quoted 11 lines · Rick P
Quoted 10 lines
Quoted 7 lines

Actually why is this on top of in-place conversion? I thought the idea was to break the series apart to get something basic upstream:
https://lore.kernel.org/all/[email protected]/

Adding in-place conversion tests seems to be going in the opposite direction.

We wanted to have this series based on in-place conversion since non-in-place conversion is on a deprecation path.

Quoted 7 lines

Hmm, what kind of timeline do you see for the old interface actually getting yanked out of the kernel? I was imagining it would be there for many years at minimum, in which case it would need tests too.

The level of dependencies we have on coco stuff is pretty unfortunate I think. Considering these tests should have ideally quickly followed the TDX base upstreaming, it would be nice if we could avoid it here.

Quoted 8 lines

I saw the new version is also based on in-place conversion (which is now headed upstream, congrats!). But can we talk about the legacy deprecation? Is there a plan to actually deprecate and remove it, or even move qemu off of the old interface?

Otherwise, I disagree with the plan to drop the tests for the old interface. That said I'd would be thrilled to have anything upstream, so if the set can be smaller or faster that way, then I agree with the change.

The v15 actually supports both: out-of-place conversion (per-VM memory attributes) and in-place conversion.

Quoted 1 line

But in general, I'd like to know the plan for the old interface.

Sean talked something about it:
https://lore.kernel.org/all/[email protected]/

#104 lore raw
Quoted 7 lines · Xiaoyao Li
Quoted 4 lines

Otherwise, I disagree with the plan to drop the tests for the old interface. That said I'd would be thrilled to have anything upstream, so if the set can be
smaller or faster that way, then I agree with the change.

The v15 actually supports both: out-of-place conversion (per-VM memory attributes) and in-place conversion.

Oh, hmm... I'll take a look. Previously the direction was to break it apart into smaller pieces. So I would have assumed that would have meant supporting only one in the initial set.

Quoted 5 lines
Quoted 2 lines

But in general, I'd like to know the plan for the old interface.

Sean talked something about it:
https://lore.kernel.org/all/[email protected]/

author
#105 lore raw
In reply to Edgecombe, Rick P

tools/testing/selftests/kvm/lib/x86/tdx/td_boot_offsets.c line 11

Quoted 12 lines · Rick P
tools/testing/selftests/kvm/lib/x86/tdx/td_boot_offsets.c +1 -1
@@ -5,7 +5,7 @@
 
 #include "tdx/td_boot.h"
 
-static void __used common(void)
+static void __attribute__((used)) common(void)
 {
        OFFSET(TD_BOOT_PARAMETERS_CR0, td_boot_parameters, cr0);
        OFFSET(TD_BOOT_PARAMETERS_CR3, td_boot_parameters, cr3);

Thanks for testing this! The patch series posted here on the mailing list is correct, but I made the mistake when pushing the code to the branch.

I have just force-pushed the fix to the branch. It should compile correctly now. Could you please pull the latest version and give it another try?

Lisa