This is v9 of the TDX selftests.
Thanks everyone for the thorough review on v8 [1]. I tried addressing all the comments. I'm terribly sorry if I missed something.
The original v8 series [1] was split to make reviewing the test framework changes easier. This series includes the original patches up to the TDX lifecycle test which is the first TDX selftest in the series.
This series is based on v6.17-rc2
Changes from v8: - Rebased on top of v6.17-rc2 - Drop several patches which are no longer needed now that TDX support is integrated into the common flow. - Split several patches to make reviewing easier. - Massive refactor compared to v8 to pull TDX special handling into __vm_create() and vm_vcpu_add() instead of creating separate functions for TDX. - Use kbuild to expose values from c to assembly code. - Move setup of the reset vectors to c code as suggested by Sean. - Drop redundant cpuid masking functions which are no longer necessary. - Initialize TDX protected pages one at a time instead of allocating large chinks of memory. - Add UCALL support for TDX to align with the rest of the selftests. - Minor fixes to kselftest_harness.h and virt_map() that were identified as part of this work.
[1] https://lore.kernel.org/lkml/20250807201628.1185915-1-sagis@google.com/
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: Add support for TDX TDCALL from guest
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
Sagi Shahar (13): KVM: selftests: Include overflow.h instead of redefining is_signed_type() KVM: selftests: Allocate pgd in virt_map() as necessary KVM: selftests: Expose functions to get default sregs values KVM: selftests: Expose function to allocate guest vCPU stack KVM: selftests: Expose segment definitons to assembly files KVM: selftests: Add kbuild definitons KVM: selftests: Define structs to pass parameters to TDX boot code KVM: selftests: Set up TDX boot code region KVM: selftests: Set up TDX boot parameters region KVM: selftests: Add helper to initialize TDX VM KVM: selftests: Hook TDX support to vm and vcpu creation KVM: selftests: Add wrapper for TDX MMIO from guest KVM: selftests: Add TDX lifecycle test
tools/include/linux/kbuild.h | 18 + tools/testing/selftests/kselftest_harness.h | 3 +- tools/testing/selftests/kvm/Makefile.kvm | 32 ++ .../selftests/kvm/include/x86/processor.h | 8 + .../selftests/kvm/include/x86/processor_asm.h | 12 + .../selftests/kvm/include/x86/tdx/td_boot.h | 81 ++++ .../kvm/include/x86/tdx/td_boot_asm.h | 16 + .../selftests/kvm/include/x86/tdx/tdcall.h | 34 ++ .../selftests/kvm/include/x86/tdx/tdx.h | 14 + .../selftests/kvm/include/x86/tdx/tdx_util.h | 86 ++++ .../testing/selftests/kvm/include/x86/ucall.h | 4 +- tools/testing/selftests/kvm/lib/kvm_util.c | 25 +- .../testing/selftests/kvm/lib/x86/processor.c | 122 ++++-- .../selftests/kvm/lib/x86/tdx/td_boot.S | 60 +++ .../kvm/lib/x86/tdx/td_boot_offsets.c | 21 + .../selftests/kvm/lib/x86/tdx/tdcall.S | 93 +++++ .../kvm/lib/x86/tdx/tdcall_offsets.c | 16 + tools/testing/selftests/kvm/lib/x86/tdx/tdx.c | 22 + .../selftests/kvm/lib/x86/tdx/tdx_util.c | 391 ++++++++++++++++++ tools/testing/selftests/kvm/lib/x86/ucall.c | 45 +- tools/testing/selftests/kvm/x86/tdx_vm_test.c | 31 ++ 21 files changed, 1095 insertions(+), 39 deletions(-) create mode 100644 tools/include/linux/kbuild.h create mode 100644 tools/testing/selftests/kvm/include/x86/processor_asm.h create mode 100644 tools/testing/selftests/kvm/include/x86/tdx/td_boot.h create mode 100644 tools/testing/selftests/kvm/include/x86/tdx/td_boot_asm.h create mode 100644 tools/testing/selftests/kvm/include/x86/tdx/tdcall.h create mode 100644 tools/testing/selftests/kvm/include/x86/tdx/tdx.h create mode 100644 tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h create mode 100644 tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S create mode 100644 tools/testing/selftests/kvm/lib/x86/tdx/td_boot_offsets.c create mode 100644 tools/testing/selftests/kvm/lib/x86/tdx/tdcall.S create mode 100644 tools/testing/selftests/kvm/lib/x86/tdx/tdcall_offsets.c create mode 100644 tools/testing/selftests/kvm/lib/x86/tdx/tdx.c create mode 100644 tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c create mode 100644 tools/testing/selftests/kvm/x86/tdx_vm_test.c
Redefinition of is_signed_type() causes compilation warning for tests which use kselftest_harness. Replace the definition with linux/overflow.h
Signed-off-by: Sagi Shahar sagis@google.com --- tools/testing/selftests/kselftest_harness.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/tools/testing/selftests/kselftest_harness.h b/tools/testing/selftests/kselftest_harness.h index 2925e47db995..a580a0d33c65 100644 --- a/tools/testing/selftests/kselftest_harness.h +++ b/tools/testing/selftests/kselftest_harness.h @@ -56,6 +56,7 @@ #include <asm/types.h> #include <ctype.h> #include <errno.h> +#include <linux/overflow.h> #include <linux/unistd.h> #include <poll.h> #include <stdbool.h> @@ -751,8 +752,6 @@ for (; _metadata->trigger; _metadata->trigger = \ __bail(_assert, _metadata))
-#define is_signed_type(var) (!!(((__typeof__(var))(-1)) < (__typeof__(var))1)) - #define __EXPECT(_expected, _expected_str, _seen, _seen_str, _t, _assert) do { \ /* Avoid multiple evaluation of the cases */ \ __typeof__(_expected) __exp = (_expected); \
On Wed, Aug 20, 2025, Sagi Shahar wrote:
Redefinition of is_signed_type() causes compilation warning for tests which use kselftest_harness. Replace the definition with linux/overflow.h
Heh, tried that:
https://lore.kernel.org/all/18f2ea68-0f7c-465e-917e-e079335995c1@sirena.org....
There's now a fix in kvm-x86/fixes and thus kvm-x86/next, so if you base something off kvm-x86/next, the warnings should be gone.
Need to add the selftest folks.
+ linux-kselftest@vger.kernel.org + Kees Cook kees@kernel.org + Shuah Khan shuah@kernel.org
Sagi Shahar wrote:
Redefinition of is_signed_type() causes compilation warning for tests which use kselftest_harness. Replace the definition with linux/overflow.h
Signed-off-by: Sagi Shahar sagis@google.com
Thanks! I've seen this as well and it fixes the warning for me as well. It might be worth picking up separate from this series depending on what the selftest folks say.
Tested-by: Ira Weiny ira.weiny@intel.com Reviewed-by: Ira Weiny ira.weiny@intel.com
tools/testing/selftests/kselftest_harness.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/tools/testing/selftests/kselftest_harness.h b/tools/testing/selftests/kselftest_harness.h index 2925e47db995..a580a0d33c65 100644 --- a/tools/testing/selftests/kselftest_harness.h +++ b/tools/testing/selftests/kselftest_harness.h @@ -56,6 +56,7 @@ #include <asm/types.h> #include <ctype.h> #include <errno.h> +#include <linux/overflow.h> #include <linux/unistd.h> #include <poll.h> #include <stdbool.h> @@ -751,8 +752,6 @@ for (; _metadata->trigger; _metadata->trigger = \ __bail(_assert, _metadata)) -#define is_signed_type(var) (!!(((__typeof__(var))(-1)) < (__typeof__(var))1))
#define __EXPECT(_expected, _expected_str, _seen, _seen_str, _t, _assert) do { \ /* Avoid multiple evaluation of the cases */ \ __typeof__(_expected) __exp = (_expected); \ -- 2.51.0.rc1.193.gad69d77794-goog
On Thu, Aug 21, 2025, Ira Weiny wrote:
Need to add the selftest folks.
- linux-kselftest@vger.kernel.org
- Kees Cook kees@kernel.org
- Shuah Khan shuah@kernel.org
Sagi Shahar wrote:
Redefinition of is_signed_type() causes compilation warning for tests which use kselftest_harness. Replace the definition with linux/overflow.h
Signed-off-by: Sagi Shahar sagis@google.com
Thanks! I've seen this as well and it fixes the warning for me as well. It might be worth picking up separate from this series depending on what the selftest folks say.
Again[1], I already have a fix applied and will send it to Paolo today. And simply including overflow.h doesn't work[2] because not all selftests add tools/include to their include path.
I appreciate the enthusiastic though!
[1] https://lore.kernel.org/all/aKcqRFWuGZQQ3v3y@google.com [2] https://lore.kernel.org/all/18f2ea68-0f7c-465e-917e-e079335995c1@sirena.org....
Tested-by: Ira Weiny ira.weiny@intel.com Reviewed-by: Ira Weiny ira.weiny@intel.com
tools/testing/selftests/kselftest_harness.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/tools/testing/selftests/kselftest_harness.h b/tools/testing/selftests/kselftest_harness.h index 2925e47db995..a580a0d33c65 100644 --- a/tools/testing/selftests/kselftest_harness.h +++ b/tools/testing/selftests/kselftest_harness.h @@ -56,6 +56,7 @@ #include <asm/types.h> #include <ctype.h> #include <errno.h> +#include <linux/overflow.h> #include <linux/unistd.h> #include <poll.h> #include <stdbool.h> @@ -751,8 +752,6 @@ for (; _metadata->trigger; _metadata->trigger = \ __bail(_assert, _metadata)) -#define is_signed_type(var) (!!(((__typeof__(var))(-1)) < (__typeof__(var))1))
#define __EXPECT(_expected, _expected_str, _seen, _seen_str, _t, _assert) do { \ /* Avoid multiple evaluation of the cases */ \ __typeof__(_expected) __exp = (_expected); \ -- 2.51.0.rc1.193.gad69d77794-goog
If virt_map() is called before any call to ____vm_vaddr_alloc() it will create the mapping using an invalid pgd.
Add call to virt_pgd_alloc() as part of virt_map() before creating the mapping, similarly to ____vm_vaddr_alloc()
Signed-off-by: Sagi Shahar sagis@google.com --- tools/testing/selftests/kvm/lib/kvm_util.c | 1 + 1 file changed, 1 insertion(+)
diff --git a/tools/testing/selftests/kvm/lib/kvm_util.c b/tools/testing/selftests/kvm/lib/kvm_util.c index c3f5142b0a54..b4c8702ba4bd 100644 --- a/tools/testing/selftests/kvm/lib/kvm_util.c +++ b/tools/testing/selftests/kvm/lib/kvm_util.c @@ -1609,6 +1609,7 @@ void virt_map(struct kvm_vm *vm, uint64_t vaddr, uint64_t paddr, TEST_ASSERT(vaddr + size > vaddr, "Vaddr overflow"); TEST_ASSERT(paddr + size > paddr, "Paddr overflow");
+ virt_pgd_alloc(vm); while (npages--) { virt_pg_map(vm, vaddr, paddr); sparsebit_set(vm->vpages_mapped, vaddr >> vm->page_shift);
Sagi Shahar wrote:
If virt_map() is called before any call to ____vm_vaddr_alloc() it will create the mapping using an invalid pgd.
Add call to virt_pgd_alloc() as part of virt_map() before creating the mapping, similarly to ____vm_vaddr_alloc()
Signed-off-by: Sagi Shahar sagis@google.com
Reviewed-by: Ira Weiny ira.weiny@intel.com
tools/testing/selftests/kvm/lib/kvm_util.c | 1 + 1 file changed, 1 insertion(+)
diff --git a/tools/testing/selftests/kvm/lib/kvm_util.c b/tools/testing/selftests/kvm/lib/kvm_util.c index c3f5142b0a54..b4c8702ba4bd 100644 --- a/tools/testing/selftests/kvm/lib/kvm_util.c +++ b/tools/testing/selftests/kvm/lib/kvm_util.c @@ -1609,6 +1609,7 @@ void virt_map(struct kvm_vm *vm, uint64_t vaddr, uint64_t paddr, TEST_ASSERT(vaddr + size > vaddr, "Vaddr overflow"); TEST_ASSERT(paddr + size > paddr, "Paddr overflow");
- virt_pgd_alloc(vm); while (npages--) { virt_pg_map(vm, vaddr, paddr); sparsebit_set(vm->vpages_mapped, vaddr >> vm->page_shift);
-- 2.51.0.rc1.193.gad69d77794-goog
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.
Signed-off-by: Sagi Shahar sagis@google.com --- .../selftests/kvm/include/x86/processor.h | 6 +++ .../testing/selftests/kvm/lib/x86/processor.c | 41 +++++++++++++++---- 2 files changed, 40 insertions(+), 7 deletions(-)
diff --git a/tools/testing/selftests/kvm/include/x86/processor.h b/tools/testing/selftests/kvm/include/x86/processor.h index 2efb05c2f2fb..5c16507f9b2d 100644 --- a/tools/testing/selftests/kvm/include/x86/processor.h +++ b/tools/testing/selftests/kvm/include/x86/processor.h @@ -1026,6 +1026,12 @@ static inline struct kvm_cpuid2 *allocate_kvm_cpuid2(int nr_entries)
void vcpu_init_cpuid(struct kvm_vcpu *vcpu, const struct kvm_cpuid2 *cpuid);
+uint16_t kvm_get_default_idt_limit(void); +uint16_t kvm_get_default_gdt_limit(void); +uint64_t kvm_get_default_cr0(void); +uint64_t kvm_get_default_cr4(void); +uint64_t kvm_get_default_efer(void); + static inline void vcpu_get_cpuid(struct kvm_vcpu *vcpu) { vcpu_ioctl(vcpu, KVM_GET_CPUID2, vcpu->cpuid); diff --git a/tools/testing/selftests/kvm/lib/x86/processor.c b/tools/testing/selftests/kvm/lib/x86/processor.c index d4c19ac885a9..b2a4b11ac8c0 100644 --- a/tools/testing/selftests/kvm/lib/x86/processor.c +++ b/tools/testing/selftests/kvm/lib/x86/processor.c @@ -488,6 +488,35 @@ static void kvm_seg_set_tss_64bit(vm_vaddr_t base, struct kvm_segment *segp) segp->present = 1; }
+uint16_t kvm_get_default_idt_limit(void) +{ + return NUM_INTERRUPTS * sizeof(struct idt_entry) - 1; +} + +uint16_t kvm_get_default_gdt_limit(void) +{ + return getpagesize() - 1; +} + +uint64_t kvm_get_default_cr0(void) +{ + return X86_CR0_PE | X86_CR0_NE | X86_CR0_PG; +} + +uint64_t kvm_get_default_cr4(void) +{ + uint64_t cr4 = X86_CR4_PAE | X86_CR4_OSFXSR; + + if (kvm_cpu_has(X86_FEATURE_XSAVE)) + cr4 |= X86_CR4_OSXSAVE; + return cr4; +} + +uint64_t kvm_get_default_efer(void) +{ + return EFER_LME | EFER_LMA | EFER_NX; +} + static void vcpu_init_sregs(struct kvm_vm *vm, struct kvm_vcpu *vcpu) { struct kvm_sregs sregs; @@ -498,15 +527,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.gdt.limit = kvm_get_default_gdt_limit();
- 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; - sregs.efer |= (EFER_LME | EFER_LMA | EFER_NX); + sregs.cr0 = kvm_get_default_cr0(); + sregs.cr4 |= kvm_get_default_cr4(); + sregs.efer |= kvm_get_default_efer();
kvm_seg_set_unusable(&sregs.ldt); kvm_seg_set_kernel_code_64bit(&sregs.cs);
TDX guests' registers cannot be initialized directly using vcpu_regs_set(), hence the stack pointer needs to be initialized by the guest itself, running boot code beginning at the reset vector.
Expose the function to allocate the guest stack so that TDX initialization code can allocate it itself and skip the allocation in vm_arch_vcpu_add() in that case.
Signed-off-by: Sagi Shahar sagis@google.com --- .../selftests/kvm/include/x86/processor.h | 2 ++ tools/testing/selftests/kvm/lib/x86/processor.c | 17 ++++++++++++----- 2 files changed, 14 insertions(+), 5 deletions(-)
diff --git a/tools/testing/selftests/kvm/include/x86/processor.h b/tools/testing/selftests/kvm/include/x86/processor.h index 5c16507f9b2d..8fcc5118683e 100644 --- a/tools/testing/selftests/kvm/include/x86/processor.h +++ b/tools/testing/selftests/kvm/include/x86/processor.h @@ -1111,6 +1111,8 @@ static inline void vcpu_clear_cpuid_feature(struct kvm_vcpu *vcpu, vcpu_set_or_clear_cpuid_feature(vcpu, feature, false); }
+vm_vaddr_t kvm_allocate_vcpu_stack(struct kvm_vm *vm); + uint64_t vcpu_get_msr(struct kvm_vcpu *vcpu, uint64_t msr_index); int _vcpu_set_msr(struct kvm_vcpu *vcpu, uint64_t msr_index, uint64_t msr_value);
diff --git a/tools/testing/selftests/kvm/lib/x86/processor.c b/tools/testing/selftests/kvm/lib/x86/processor.c index b2a4b11ac8c0..1eae92957456 100644 --- a/tools/testing/selftests/kvm/lib/x86/processor.c +++ b/tools/testing/selftests/kvm/lib/x86/processor.c @@ -687,12 +687,9 @@ void vcpu_arch_set_entry_point(struct kvm_vcpu *vcpu, void *guest_code) vcpu_regs_set(vcpu, ®s); }
-struct kvm_vcpu *vm_arch_vcpu_add(struct kvm_vm *vm, uint32_t vcpu_id) +vm_vaddr_t kvm_allocate_vcpu_stack(struct kvm_vm *vm) { - struct kvm_mp_state mp_state; - struct kvm_regs regs; vm_vaddr_t stack_vaddr; - struct kvm_vcpu *vcpu;
stack_vaddr = __vm_vaddr_alloc(vm, DEFAULT_STACK_PGS * getpagesize(), DEFAULT_GUEST_STACK_VADDR_MIN, @@ -713,6 +710,15 @@ struct kvm_vcpu *vm_arch_vcpu_add(struct kvm_vm *vm, uint32_t vcpu_id) "__vm_vaddr_alloc() did not provide a page-aligned address"); stack_vaddr -= 8;
+ return stack_vaddr; +} + +struct kvm_vcpu *vm_arch_vcpu_add(struct kvm_vm *vm, uint32_t vcpu_id) +{ + struct kvm_mp_state mp_state; + struct kvm_regs regs; + struct kvm_vcpu *vcpu; + vcpu = __vm_vcpu_add(vm, vcpu_id); vcpu_init_cpuid(vcpu, kvm_get_supported_cpuid()); vcpu_init_sregs(vm, vcpu); @@ -721,7 +727,8 @@ struct kvm_vcpu *vm_arch_vcpu_add(struct kvm_vm *vm, uint32_t vcpu_id) /* Setup guest general purpose registers */ vcpu_regs_get(vcpu, ®s); regs.rflags = regs.rflags | 0x2; - regs.rsp = stack_vaddr; + if (vm->type != KVM_X86_TDX_VM) + regs.rsp = kvm_allocate_vcpu_stack(vm); vcpu_regs_set(vcpu, ®s);
/* Setup the MP state */
Sagi Shahar wrote:
[snip]
diff --git a/tools/testing/selftests/kvm/lib/x86/processor.c b/tools/testing/selftests/kvm/lib/x86/processor.c index b2a4b11ac8c0..1eae92957456 100644 --- a/tools/testing/selftests/kvm/lib/x86/processor.c +++ b/tools/testing/selftests/kvm/lib/x86/processor.c @@ -687,12 +687,9 @@ void vcpu_arch_set_entry_point(struct kvm_vcpu *vcpu, void *guest_code) vcpu_regs_set(vcpu, ®s); } -struct kvm_vcpu *vm_arch_vcpu_add(struct kvm_vm *vm, uint32_t vcpu_id) +vm_vaddr_t kvm_allocate_vcpu_stack(struct kvm_vm *vm) {
- struct kvm_mp_state mp_state;
- struct kvm_regs regs; vm_vaddr_t stack_vaddr;
- struct kvm_vcpu *vcpu;
stack_vaddr = __vm_vaddr_alloc(vm, DEFAULT_STACK_PGS * getpagesize(), DEFAULT_GUEST_STACK_VADDR_MIN, @@ -713,6 +710,15 @@ struct kvm_vcpu *vm_arch_vcpu_add(struct kvm_vm *vm, uint32_t vcpu_id) "__vm_vaddr_alloc() did not provide a page-aligned address"); stack_vaddr -= 8;
- return stack_vaddr;
+}
+struct kvm_vcpu *vm_arch_vcpu_add(struct kvm_vm *vm, uint32_t vcpu_id) +{
- struct kvm_mp_state mp_state;
- struct kvm_regs regs;
- struct kvm_vcpu *vcpu;
- vcpu = __vm_vcpu_add(vm, vcpu_id); vcpu_init_cpuid(vcpu, kvm_get_supported_cpuid()); vcpu_init_sregs(vm, vcpu);
@@ -721,7 +727,8 @@ struct kvm_vcpu *vm_arch_vcpu_add(struct kvm_vm *vm, uint32_t vcpu_id) /* Setup guest general purpose registers */ vcpu_regs_get(vcpu, ®s); regs.rflags = regs.rflags | 0x2;
- regs.rsp = stack_vaddr;
- if (vm->type != KVM_X86_TDX_VM)
regs.rsp = kvm_allocate_vcpu_stack(vm);
At this point in the series vm->type can't be KVM_X86_TDX_VM correct?
So that makes this safe during bisect?
Ira
vcpu_regs_set(vcpu, ®s); /* Setup the MP state */ -- 2.51.0.rc1.193.gad69d77794-goog
On Thu, Aug 21, 2025 at 4:58 PM Ira Weiny ira.weiny@intel.com wrote:
Sagi Shahar wrote:
[snip]
diff --git a/tools/testing/selftests/kvm/lib/x86/processor.c b/tools/testing/selftests/kvm/lib/x86/processor.c index b2a4b11ac8c0..1eae92957456 100644 --- a/tools/testing/selftests/kvm/lib/x86/processor.c +++ b/tools/testing/selftests/kvm/lib/x86/processor.c @@ -687,12 +687,9 @@ void vcpu_arch_set_entry_point(struct kvm_vcpu *vcpu, void *guest_code) vcpu_regs_set(vcpu, ®s); }
-struct kvm_vcpu *vm_arch_vcpu_add(struct kvm_vm *vm, uint32_t vcpu_id) +vm_vaddr_t kvm_allocate_vcpu_stack(struct kvm_vm *vm) {
struct kvm_mp_state mp_state;
struct kvm_regs regs; vm_vaddr_t stack_vaddr;
struct kvm_vcpu *vcpu; stack_vaddr = __vm_vaddr_alloc(vm, DEFAULT_STACK_PGS * getpagesize(), DEFAULT_GUEST_STACK_VADDR_MIN,
@@ -713,6 +710,15 @@ struct kvm_vcpu *vm_arch_vcpu_add(struct kvm_vm *vm, uint32_t vcpu_id) "__vm_vaddr_alloc() did not provide a page-aligned address"); stack_vaddr -= 8;
return stack_vaddr;
+}
+struct kvm_vcpu *vm_arch_vcpu_add(struct kvm_vm *vm, uint32_t vcpu_id) +{
struct kvm_mp_state mp_state;
struct kvm_regs regs;
struct kvm_vcpu *vcpu;
vcpu = __vm_vcpu_add(vm, vcpu_id); vcpu_init_cpuid(vcpu, kvm_get_supported_cpuid()); vcpu_init_sregs(vm, vcpu);
@@ -721,7 +727,8 @@ struct kvm_vcpu *vm_arch_vcpu_add(struct kvm_vm *vm, uint32_t vcpu_id) /* Setup guest general purpose registers */ vcpu_regs_get(vcpu, ®s); regs.rflags = regs.rflags | 0x2;
regs.rsp = stack_vaddr;
if (vm->type != KVM_X86_TDX_VM)
regs.rsp = kvm_allocate_vcpu_stack(vm);
At this point in the series vm->type can't be KVM_X86_TDX_VM correct?
So that makes this safe during bisect?
I double checked and no one is creating VMs with KVM_X86_TDX_VM. The first test that sets KVM_X86_TDX_VM is the last patch in the series.
Ira
vcpu_regs_set(vcpu, ®s); /* Setup the MP state */
-- 2.51.0.rc1.193.gad69d77794-goog
From: Isaku Yamahata isaku.yamahata@intel.com
Let kvm_init_vm_address_properties() initialize vm->arch.{s_bit, tag_mask} similar to SEV.
TDX sets the shared bit based on the guest physical address width and currently supports 48 and 52 widths.
Co-developed-by: Adrian Hunter adrian.hunter@intel.com Signed-off-by: Adrian Hunter adrian.hunter@intel.com Signed-off-by: Isaku Yamahata isaku.yamahata@intel.com Co-developed-by: Sagi Shahar sagis@google.com Signed-off-by: Sagi Shahar sagis@google.com --- .../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(-) create mode 100644 tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h
diff --git a/tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h b/tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h new file mode 100644 index 000000000000..286d5e3c24b1 --- /dev/null +++ b/tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h @@ -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 diff --git a/tools/testing/selftests/kvm/lib/x86/processor.c b/tools/testing/selftests/kvm/lib/x86/processor.c index 1eae92957456..6dbf40cbbc2a 100644 --- a/tools/testing/selftests/kvm/lib/x86/processor.c +++ b/tools/testing/selftests/kvm/lib/x86/processor.c @@ -8,6 +8,7 @@ #include "kvm_util.h" #include "processor.h" #include "sev.h" +#include "tdx/tdx_util.h"
#ifndef NUM_INTERRUPTS #define NUM_INTERRUPTS 256 @@ -1190,12 +1191,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) { + uint32_t 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 = 1ULL << (gpa_bits - 1); + vm->gpa_tag_mask = vm->arch.s_bit; } }
Sagi Shahar wrote:
From: Isaku Yamahata isaku.yamahata@intel.com
Let kvm_init_vm_address_properties() initialize vm->arch.{s_bit, tag_mask} similar to SEV.
TDX sets the shared bit based on the guest physical address width and currently supports 48 and 52 widths.
Co-developed-by: Adrian Hunter adrian.hunter@intel.com Signed-off-by: Adrian Hunter adrian.hunter@intel.com Signed-off-by: Isaku Yamahata isaku.yamahata@intel.com Co-developed-by: Sagi Shahar sagis@google.com Signed-off-by: Sagi Shahar sagis@google.com
.../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(-) create mode 100644 tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h
diff --git a/tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h b/tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h new file mode 100644 index 000000000000..286d5e3c24b1 --- /dev/null +++ b/tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h @@ -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;
+}
NIT: Might have been better to define this in 4/19 and use it there. But looks like that logic is changed later on somewhere. So... meh.
Ira
[snip]
On Thu, Aug 21, 2025 at 5:03 PM Ira Weiny ira.weiny@intel.com wrote:
Sagi Shahar wrote:
From: Isaku Yamahata isaku.yamahata@intel.com
Let kvm_init_vm_address_properties() initialize vm->arch.{s_bit, tag_mask} similar to SEV.
TDX sets the shared bit based on the guest physical address width and currently supports 48 and 52 widths.
Co-developed-by: Adrian Hunter adrian.hunter@intel.com Signed-off-by: Adrian Hunter adrian.hunter@intel.com Signed-off-by: Isaku Yamahata isaku.yamahata@intel.com Co-developed-by: Sagi Shahar sagis@google.com Signed-off-by: Sagi Shahar sagis@google.com
.../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(-) create mode 100644 tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h
diff --git a/tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h b/tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h new file mode 100644 index 000000000000..286d5e3c24b1 --- /dev/null +++ b/tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h @@ -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;
+}
NIT: Might have been better to define this in 4/19 and use it there. But looks like that logic is changed later on somewhere. So... meh.
We can skip the check for KVM_X86_TDX_VM entirely in 4/19 since like you said, it's replaced with something different later on.
Ira
[snip]
Move kernel segment definitons to a separate file which can be included from assembly files.
Signed-off-by: Sagi Shahar sagis@google.com --- .../selftests/kvm/include/x86/processor_asm.h | 12 ++++++++++++ tools/testing/selftests/kvm/lib/x86/processor.c | 5 +---- 2 files changed, 13 insertions(+), 4 deletions(-) create mode 100644 tools/testing/selftests/kvm/include/x86/processor_asm.h
diff --git a/tools/testing/selftests/kvm/include/x86/processor_asm.h b/tools/testing/selftests/kvm/include/x86/processor_asm.h new file mode 100644 index 000000000000..7e5386a85ca8 --- /dev/null +++ b/tools/testing/selftests/kvm/include/x86/processor_asm.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Used for storing defines used by both processor.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 diff --git a/tools/testing/selftests/kvm/lib/x86/processor.c b/tools/testing/selftests/kvm/lib/x86/processor.c index 6dbf40cbbc2a..4802fc81bea7 100644 --- a/tools/testing/selftests/kvm/lib/x86/processor.c +++ b/tools/testing/selftests/kvm/lib/x86/processor.c @@ -7,6 +7,7 @@ #include "test_util.h" #include "kvm_util.h" #include "processor.h" +#include "processor_asm.h" #include "sev.h" #include "tdx/tdx_util.h"
@@ -14,10 +15,6 @@ #define NUM_INTERRUPTS 256 #endif
-#define KERNEL_CS 0x8 -#define KERNEL_DS 0x10 -#define KERNEL_TSS 0x18 - vm_vaddr_t exception_handlers; bool host_cpu_is_amd; bool host_cpu_is_intel;
NIT:
Sagi Shahar wrote:
Move kernel segment definitons to a separate file which can be included
^^^^^^^^^^ definitions
And in the subject.
Otherwise seems reasonable.
Reviewed-by: Ira Weiny ira.weiny@intel.com
from assembly files.
Signed-off-by: Sagi Shahar sagis@google.com
.../selftests/kvm/include/x86/processor_asm.h | 12 ++++++++++++ tools/testing/selftests/kvm/lib/x86/processor.c | 5 +---- 2 files changed, 13 insertions(+), 4 deletions(-) create mode 100644 tools/testing/selftests/kvm/include/x86/processor_asm.h
diff --git a/tools/testing/selftests/kvm/include/x86/processor_asm.h b/tools/testing/selftests/kvm/include/x86/processor_asm.h new file mode 100644 index 000000000000..7e5386a85ca8 --- /dev/null +++ b/tools/testing/selftests/kvm/include/x86/processor_asm.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/*
- Used for storing defines used by both processor.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 diff --git a/tools/testing/selftests/kvm/lib/x86/processor.c b/tools/testing/selftests/kvm/lib/x86/processor.c index 6dbf40cbbc2a..4802fc81bea7 100644 --- a/tools/testing/selftests/kvm/lib/x86/processor.c +++ b/tools/testing/selftests/kvm/lib/x86/processor.c @@ -7,6 +7,7 @@ #include "test_util.h" #include "kvm_util.h" #include "processor.h" +#include "processor_asm.h" #include "sev.h" #include "tdx/tdx_util.h" @@ -14,10 +15,6 @@ #define NUM_INTERRUPTS 256 #endif -#define KERNEL_CS 0x8 -#define KERNEL_DS 0x10 -#define KERNEL_TSS 0x18
vm_vaddr_t exception_handlers; bool host_cpu_is_amd; bool host_cpu_is_intel; -- 2.51.0.rc1.193.gad69d77794-goog
Add kbuild.h that can be used by files under tools/
Definitions are taken from the original definitions at include/linux/kbuild.h
This is needed to expose values from c code to assembly code.
Signed-off-by: Sagi Shahar sagis@google.com --- tools/include/linux/kbuild.h | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 tools/include/linux/kbuild.h
diff --git a/tools/include/linux/kbuild.h b/tools/include/linux/kbuild.h new file mode 100644 index 000000000000..62e20ba9380e --- /dev/null +++ b/tools/include/linux/kbuild.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __TOOLS_LINUX_KBUILD_H +#define __TOOLS_LINUX_KBUILD_H + +#include <stddef.h> + +#define DEFINE(sym, val) \ + asm volatile("\n.ascii "->" #sym " %0 " #val """ : : "i" (val)) + +#define BLANK() asm volatile("\n.ascii "->"" : : ) + +#define OFFSET(sym, str, mem) \ + DEFINE(sym, offsetof(struct str, mem)) + +#define COMMENT(x) \ + asm volatile("\n.ascii "->#" x """) + +#endif /* __TOOLS_LINUX_KBUILD_H */
TDX registers are inaccesible to KVM. Therefore we need a different mechanism to load boot parameters for TDX code. TDX boot code will read the registers values from memory and set the registers manually.
This patch defines the data structures used to communicate between c code and the TDX assembly boot code which will be added in a later patch.
Use kbuild.h to expose the offsets into the structs from c code to assembly code.
Co-developed-by: Ackerley Tng ackerleytng@google.com Signed-off-by: Ackerley Tng ackerleytng@google.com Signed-off-by: Sagi Shahar sagis@google.com --- tools/testing/selftests/kvm/Makefile.kvm | 18 +++++ .../selftests/kvm/include/x86/tdx/td_boot.h | 76 +++++++++++++++++++ .../kvm/lib/x86/tdx/td_boot_offsets.c | 21 +++++ 3 files changed, 115 insertions(+) create mode 100644 tools/testing/selftests/kvm/include/x86/tdx/td_boot.h create mode 100644 tools/testing/selftests/kvm/lib/x86/tdx/td_boot_offsets.c
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm index f6fe7a07a0a2..f4686445c197 100644 --- a/tools/testing/selftests/kvm/Makefile.kvm +++ b/tools/testing/selftests/kvm/Makefile.kvm @@ -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 @@ -229,6 +231,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 @@ -281,6 +287,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)) @@ -307,6 +314,7 @@ $(SPLIT_TEST_GEN_OBJ): $(OUTPUT)/$(ARCH)/%.o: $(ARCH)/%.c
EXTRA_CLEAN += $(GEN_HDRS) \ $(LIBKVM_OBJS) \ + $(LIBKVM_ASM_DEFS_OBJ) \ $(SPLIT_TEST_GEN_OBJ) \ $(TEST_DEP_FILES) \ $(TEST_GEN_OBJ) \ @@ -318,18 +326,28 @@ $(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. $(LIBKVM_STRING_OBJ): $(OUTPUT)/%.o: %.c $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c -ffreestanding $< -o $@
+$(OUTPUT)/include/x86/tdx/td_boot_offsets.h: $(OUTPUT)/lib/x86/tdx/td_boot_offsets.s FORCE + $(call filechk,offsets,__TDX_BOOT_OFFSETS_H__) + +EXTRA_CLEAN += $(OUTPUT)/include/x86/tdx/td_boot_offsets.h + $(shell mkdir -p $(sort $(dir $(TEST_GEN_PROGS)))) $(SPLIT_TEST_GEN_OBJ): $(GEN_HDRS) $(TEST_GEN_PROGS): $(LIBKVM_OBJS) $(TEST_GEN_PROGS_EXTENDED): $(LIBKVM_OBJS) $(TEST_GEN_OBJ): $(GEN_HDRS)
+FORCE: + cscope: include_paths = $(LINUX_TOOL_INCLUDE) $(LINUX_HDR_PATH) include lib .. cscope: $(RM) cscope.* diff --git a/tools/testing/selftests/kvm/include/x86/tdx/td_boot.h b/tools/testing/selftests/kvm/include/x86/tdx/td_boot.h new file mode 100644 index 000000000000..5cce671586e9 --- /dev/null +++ b/tools/testing/selftests/kvm/include/x86/tdx/td_boot.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef SELFTEST_TDX_TD_BOOT_H +#define SELFTEST_TDX_TD_BOOT_H + +#include <stdint.h> + +#include <linux/compiler.h> +#include <linux/sizes.h> + +/* + * Layout for boot section (not to scale) + * + * GPA + * _________________________________ 0x1_0000_0000 (4GB) + * | Boot code trampoline | + * |___________________________|____ 0x0_ffff_fff0: Reset vector (16B below 4GB) + * | Boot code | + * |___________________________|____ td_boot will be copied here, so that the + * | | jmp to td_boot is exactly at the reset vector + * | Empty space | + * | | + * |───────────────────────────| + * | | + * | | + * | Boot parameters | + * | | + * | | + * |___________________________|____ 0x0_ffff_0000: TD_BOOT_PARAMETERS_GPA + */ +#define FOUR_GIGABYTES_GPA (SZ_4G) + +/* + * The exact memory layout for LGDT or LIDT instructions. + */ +struct __packed td_boot_parameters_dtr { + uint16_t limit; + uint32_t base; +}; + +/* + * Allows each vCPU to be initialized with different eip and esp. + * + * __packed is used since the offsets are hardcoded in td_boot.S + * + * TODO: Replace hardcoded offsets with OFFSET(). This requires getting the + * neccesry Kbuild scripts working in KVM selftests. + */ +struct td_per_vcpu_parameters { + uint32_t esp_gva; + uint64_t guest_code; +}; + +/* + * Boot parameters for the TD. + * + * Unlike a regular VM, KVM cannot set registers such as esp, eip, etc + * before boot, so to run selftests, these registers' values have to be + * initialized by the TD. + * + * This struct is loaded in TD private memory at TD_BOOT_PARAMETERS_GPA. + * + * The TD boot code will read off parameters from this struct and set up the + * vCPU for executing selftests. + * + * __packed is used since the offsets are hardcoded in td_boot.S + */ +struct td_boot_parameters { + uint32_t cr0; + uint32_t cr3; + uint32_t cr4; + struct td_boot_parameters_dtr gdtr; + struct td_boot_parameters_dtr idtr; + struct td_per_vcpu_parameters per_vcpu[]; +}; + +#endif /* SELFTEST_TDX_TD_BOOT_H */ diff --git a/tools/testing/selftests/kvm/lib/x86/tdx/td_boot_offsets.c b/tools/testing/selftests/kvm/lib/x86/tdx/td_boot_offsets.c new file mode 100644 index 000000000000..7f76a3585b99 --- /dev/null +++ b/tools/testing/selftests/kvm/lib/x86/tdx/td_boot_offsets.c @@ -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)); +}
From: Erdem Aktas erdemaktas@google.com
Add code to boot a TDX test VM. Since TDX registers are inaccesible to KVM, the boot code loads the relevant values from memory into the registers before jumping to the guest code.
Signed-off-by: Erdem Aktas erdemaktas@google.com Co-developed-by: Ackerley Tng ackerleytng@google.com Signed-off-by: Ackerley Tng ackerleytng@google.com Co-developed-by: Sagi Shahar sagis@google.com Signed-off-by: Sagi Shahar sagis@google.com --- tools/testing/selftests/kvm/Makefile.kvm | 3 + .../selftests/kvm/include/x86/tdx/td_boot.h | 5 ++ .../kvm/include/x86/tdx/td_boot_asm.h | 16 +++++ .../selftests/kvm/lib/x86/tdx/td_boot.S | 60 +++++++++++++++++++ 4 files changed, 84 insertions(+) create mode 100644 tools/testing/selftests/kvm/include/x86/tdx/td_boot_asm.h create mode 100644 tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm index f4686445c197..03754ce2e983 100644 --- a/tools/testing/selftests/kvm/Makefile.kvm +++ b/tools/testing/selftests/kvm/Makefile.kvm @@ -31,6 +31,7 @@ LIBKVM_x86 += lib/x86/sev.c LIBKVM_x86 += lib/x86/svm.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 @@ -335,6 +336,8 @@ $(LIBKVM_ASM_DEFS_OBJ): $(OUTPUT)/%.s: %.c FORCE $(LIBKVM_STRING_OBJ): $(OUTPUT)/%.o: %.c $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c -ffreestanding $< -o $@
+$(OUTPUT)/lib/x86/tdx/td_boot.o: $(OUTPUT)/include/x86/tdx/td_boot_offsets.h + $(OUTPUT)/include/x86/tdx/td_boot_offsets.h: $(OUTPUT)/lib/x86/tdx/td_boot_offsets.s FORCE $(call filechk,offsets,__TDX_BOOT_OFFSETS_H__)
diff --git a/tools/testing/selftests/kvm/include/x86/tdx/td_boot.h b/tools/testing/selftests/kvm/include/x86/tdx/td_boot.h index 5cce671586e9..65ccc65efaeb 100644 --- a/tools/testing/selftests/kvm/include/x86/tdx/td_boot.h +++ b/tools/testing/selftests/kvm/include/x86/tdx/td_boot.h @@ -73,4 +73,9 @@ 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 /* SELFTEST_TDX_TD_BOOT_H */ diff --git a/tools/testing/selftests/kvm/include/x86/tdx/td_boot_asm.h b/tools/testing/selftests/kvm/include/x86/tdx/td_boot_asm.h new file mode 100644 index 000000000000..10b4b527595c --- /dev/null +++ b/tools/testing/selftests/kvm/include/x86/tdx/td_boot_asm.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef SELFTEST_TDX_TD_BOOT_ASM_H +#define SELFTEST_TDX_TD_BOOT_ASM_H + +/* + * GPA where TD boot parameters will be loaded. + * + * 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 + +#endif // SELFTEST_TDX_TD_BOOT_ASM_H diff --git a/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S b/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S new file mode 100644 index 000000000000..7aa33caa9a78 --- /dev/null +++ b/tools/testing/selftests/kvm/lib/x86/tdx/td_boot.S @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include "tdx/td_boot_asm.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. */ + cli + + /* Paging is off. */ + + 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
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 vectors 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.
Suggested-by: Sean Christopherson seanjc@google.com Co-developed-by: Erdem Aktas erdemaktas@google.com Signed-off-by: Erdem Aktas erdemaktas@google.com Signed-off-by: Sagi Shahar sagis@google.com --- tools/testing/selftests/kvm/Makefile.kvm | 1 + .../selftests/kvm/include/x86/tdx/tdx_util.h | 2 + .../selftests/kvm/lib/x86/tdx/tdx_util.c | 54 +++++++++++++++++++ 3 files changed, 57 insertions(+) create mode 100644 tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm index 03754ce2e983..c42b579fb7c5 100644 --- a/tools/testing/selftests/kvm/Makefile.kvm +++ b/tools/testing/selftests/kvm/Makefile.kvm @@ -31,6 +31,7 @@ LIBKVM_x86 += lib/x86/sev.c LIBKVM_x86 += lib/x86/svm.c LIBKVM_x86 += lib/x86/ucall.c LIBKVM_x86 += lib/x86/vmx.c +LIBKVM_x86 += lib/x86/tdx/tdx_util.c LIBKVM_x86 += lib/x86/tdx/td_boot.S
LIBKVM_arm64 += lib/arm64/gic.c diff --git a/tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h b/tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h index 286d5e3c24b1..ec05bcd59145 100644 --- a/tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h +++ b/tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h @@ -11,4 +11,6 @@ static inline bool is_tdx_vm(struct kvm_vm *vm) return vm->type == KVM_X86_TDX_VM; }
+void vm_tdx_setup_boot_code_region(struct kvm_vm *vm); + #endif // SELFTESTS_TDX_TDX_UTIL_H diff --git a/tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c b/tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c new file mode 100644 index 000000000000..15833b9eb5d5 --- /dev/null +++ b/tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include <stdint.h> + +#include "kvm_util.h" +#include "processor.h" +#include "tdx/td_boot.h" +#include "tdx/tdx_util.h" + +/* Arbitrarily selected to avoid overlaps with anything else */ +#define TD_BOOT_CODE_SLOT 20 + +#define X86_RESET_VECTOR 0xfffffff0ul +#define X86_RESET_VECTOR_SIZE 16 + +void vm_tdx_setup_boot_code_region(struct kvm_vm *vm) +{ + size_t total_code_size = TD_BOOT_CODE_SIZE + X86_RESET_VECTOR_SIZE; + vm_paddr_t boot_code_gpa = X86_RESET_VECTOR - TD_BOOT_CODE_SIZE; + vm_paddr_t alloc_gpa = round_down(boot_code_gpa, PAGE_SIZE); + size_t nr_pages = DIV_ROUND_UP(total_code_size, PAGE_SIZE); + vm_paddr_t gpa; + uint8_t *hva; + + vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, + alloc_gpa, + TD_BOOT_CODE_SLOT, nr_pages, + KVM_MEM_GUEST_MEMFD); + + 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 + * 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 < 256, + "TD boot code not addressable by 'JMP rel8'"); + hva[0] = 0xeb; + hva[1] = 256 - 2 - TD_BOOT_CODE_SIZE; + hva[2] = 0xcc; +}
Allocate memory for TDX boot parameters and define the utility functions necessary to fill this memory with the boot parameters.
Co-developed-by: Ackerley Tng ackerleytng@google.com Signed-off-by: Ackerley Tng ackerleytng@google.com Signed-off-by: Sagi Shahar sagis@google.com --- .../selftests/kvm/include/x86/tdx/tdx_util.h | 4 + .../selftests/kvm/lib/x86/tdx/tdx_util.c | 73 +++++++++++++++++++ 2 files changed, 77 insertions(+)
diff --git a/tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h b/tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h index ec05bcd59145..dafdc7e46abe 100644 --- a/tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h +++ b/tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h @@ -12,5 +12,9 @@ static inline bool is_tdx_vm(struct kvm_vm *vm) }
void vm_tdx_setup_boot_code_region(struct kvm_vm *vm); +void vm_tdx_setup_boot_parameters_region(struct kvm_vm *vm, uint32_t nr_runnable_vcpus); +void vm_tdx_load_common_boot_parameters(struct kvm_vm *vm); +void vm_tdx_load_vcpu_boot_parameters(struct kvm_vm *vm, struct kvm_vcpu *vcpu); +void vm_tdx_set_vcpu_entry_point(struct kvm_vcpu *vcpu, void *guest_code);
#endif // SELFTESTS_TDX_TDX_UTIL_H diff --git a/tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c b/tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c index 15833b9eb5d5..52dc25e0cce4 100644 --- a/tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +++ b/tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c @@ -5,10 +5,12 @@ #include "kvm_util.h" #include "processor.h" #include "tdx/td_boot.h" +#include "tdx/td_boot_asm.h" #include "tdx/tdx_util.h"
/* 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 @@ -52,3 +54,74 @@ void vm_tdx_setup_boot_code_region(struct kvm_vm *vm) hva[1] = 256 - 2 - TD_BOOT_CODE_SIZE; hva[2] = 0xcc; } + +void vm_tdx_setup_boot_parameters_region(struct kvm_vm *vm, uint32_t 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); + vm_paddr_t gpa; + + vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, + TD_BOOT_PARAMETERS_GPA, + TD_BOOT_PARAMETERS_SLOT, npages, + KVM_MEM_GUEST_MEMFD); + 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 vm_tdx_load_common_boot_parameters(struct kvm_vm *vm) +{ + struct td_boot_parameters *params = + addr_gpa2hva(vm, TD_BOOT_PARAMETERS_GPA); + uint32_t cr4; + + TEST_ASSERT_EQ(vm->mode, VM_MODE_PXXV48_4K); + + cr4 = kvm_get_default_cr4(); + + /* TDX spec 11.6.2: CR4 bit MCE is fixed to 1 */ + cr4 |= X86_CR4_MCE; + + /* Set this because UEFI also sets this up, to handle XMM exceptions */ + cr4 |= X86_CR4_OSXMMEXCPT; + + /* 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(); + params->cr3 = vm->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"); +} + +void vm_tdx_load_vcpu_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 = + ¶ms->per_vcpu[vcpu->id]; + + vcpu_params->esp_gva = kvm_allocate_vcpu_stack(vm); +} + +void vm_tdx_set_vcpu_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 = ¶ms->per_vcpu[vcpu->id]; + + vcpu_params->guest_code = (uint64_t)guest_code; +}
KVM_TDX_INIT_VM needs to be called after KVM_CREATE_VM and before creating any VCPUs, thus before KVM_SET_CPUID2. KVM_TDX_INIT_VM accepts the CPUID values directly.
Since KVM_GET_CPUID2 can't be used at this point, calculate the CPUID values manually by using kvm_get_supported_cpuid() and filter the returned CPUIDs against the supported CPUID values read from the TDX module.
Co-developed-by: Isaku Yamahata isaku.yamahata@intel.com Signed-off-by: Isaku Yamahata isaku.yamahata@intel.com Co-developed-by: Rick Edgecombe rick.p.edgecombe@intel.com Signed-off-by: Rick Edgecombe rick.p.edgecombe@intel.com Signed-off-by: Sagi Shahar sagis@google.com --- .../selftests/kvm/include/x86/tdx/tdx_util.h | 54 +++++++ .../selftests/kvm/lib/x86/tdx/tdx_util.c | 132 ++++++++++++++++++ 2 files changed, 186 insertions(+)
diff --git a/tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h b/tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h index dafdc7e46abe..a2509959c7ce 100644 --- a/tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h +++ b/tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h @@ -11,6 +11,60 @@ static inline bool is_tdx_vm(struct kvm_vm *vm) return vm->type == KVM_X86_TDX_VM; }
+/* + * TDX ioctls + */ + +#define __vm_tdx_vm_ioctl(vm, cmd, metadata, arg) \ +({ \ + int r; \ + \ + union { \ + struct kvm_tdx_cmd c; \ + unsigned long raw; \ + } tdx_cmd = { .c = { \ + .id = (cmd), \ + .flags = (uint32_t)(metadata), \ + .data = (uint64_t)(arg), \ + } }; \ + \ + r = __vm_ioctl(vm, KVM_MEMORY_ENCRYPT_OP, &tdx_cmd.raw); \ + r ?: tdx_cmd.c.hw_error; \ +}) + +#define vm_tdx_vm_ioctl(vm, cmd, flags, arg) \ +({ \ + int ret = __vm_tdx_vm_ioctl(vm, cmd, flags, arg); \ + \ + __TEST_ASSERT_VM_VCPU_IOCTL(!ret, #cmd, ret, vm); \ +}) + +#define __vm_tdx_vcpu_ioctl(vcpu, cmd, metadata, arg) \ +({ \ + int r; \ + \ + union { \ + struct kvm_tdx_cmd c; \ + unsigned long raw; \ + } tdx_cmd = { .c = { \ + .id = (cmd), \ + .flags = (uint32_t)(metadata), \ + .data = (uint64_t)(arg), \ + } }; \ + \ + r = __vcpu_ioctl(vcpu, KVM_MEMORY_ENCRYPT_OP, &tdx_cmd.raw); \ + r ?: tdx_cmd.c.hw_error; \ +}) + +#define vm_tdx_vcpu_ioctl(vcpu, cmd, flags, arg) \ +({ \ + int ret = __vm_tdx_vcpu_ioctl(vcpu, cmd, flags, arg); \ + \ + __TEST_ASSERT_VM_VCPU_IOCTL(!ret, #cmd, ret, (vcpu)->vm); \ +}) + +void vm_tdx_init_vm(struct kvm_vm *vm, uint64_t attributes); + void vm_tdx_setup_boot_code_region(struct kvm_vm *vm); void vm_tdx_setup_boot_parameters_region(struct kvm_vm *vm, uint32_t nr_runnable_vcpus); void vm_tdx_load_common_boot_parameters(struct kvm_vm *vm); diff --git a/tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c b/tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c index 52dc25e0cce4..3869756a5641 100644 --- a/tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +++ b/tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c @@ -125,3 +125,135 @@ void vm_tdx_set_vcpu_entry_point(struct kvm_vcpu *vcpu, void *guest_code)
vcpu_params->guest_code = (uint64_t)guest_code; } + +static struct kvm_tdx_capabilities *tdx_read_capabilities(struct kvm_vm *vm) +{ + struct kvm_tdx_capabilities *tdx_cap = NULL; + int nr_cpuid_configs = 4; + int rc = -1; + int i; + + do { + nr_cpuid_configs *= 2; + + tdx_cap = realloc(tdx_cap, sizeof(*tdx_cap) + + sizeof(tdx_cap->cpuid) + + (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 = __vm_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; +} + +static struct kvm_cpuid_entry2 *tdx_find_cpuid_config(struct kvm_tdx_capabilities *cap, + uint32_t leaf, uint32_t sub_leaf) +{ + struct kvm_cpuid_entry2 *config; + uint32_t i; + + for (i = 0; i < cap->cpuid.nent; i++) { + config = &cap->cpuid.entries[i]; + + if (config->function == leaf && config->index == sub_leaf) + return config; + } + + return NULL; +} + +/* + * Filter CPUID based on TDX supported capabilities + * + * Input Args: + * vm - Virtual Machine + * cpuid_data - CPUID fileds to filter + * + * Output Args: None + * + * Return: None + * + * For each CPUID leaf, filter out non-supported bits based on the capabilities reported + * by the TDX module + */ +static void vm_tdx_filter_cpuid(struct kvm_vm *vm, + struct kvm_cpuid2 *cpuid_data) +{ + struct kvm_tdx_capabilities *tdx_cap; + 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 = tdx_find_cpuid_config(tdx_cap, 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++; + } + + free(tdx_cap); +} + +void vm_tdx_init_vm(struct kvm_vm *vm, uint64_t 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(MAX_NR_CPUID_ENTRIES); + memcpy(cpuid, tmp, kvm_cpuid2_size(tmp->nent)); + vm_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; + + vm_tdx_vm_ioctl(vm, KVM_TDX_INIT_VM, 0, init_vm); + + free(init_vm); +}
From: Isaku Yamahata isaku.yamahata@intel.com
This also exercises the KVM_TDX_CAPABILITIES ioctl.
Signed-off-by: Isaku Yamahata isaku.yamahata@intel.com Co-developed-by: Sagi Shahar sagis@google.com Signed-off-by: Sagi Shahar sagis@google.com --- .../selftests/kvm/lib/x86/tdx/tdx_util.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+)
diff --git a/tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c b/tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c index 3869756a5641..d8eab99d9333 100644 --- a/tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +++ b/tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c @@ -232,6 +232,21 @@ static void vm_tdx_filter_cpuid(struct kvm_vm *vm, free(tdx_cap); }
+static void tdx_check_attributes(struct kvm_vm *vm, uint64_t attributes) +{ + struct kvm_tdx_capabilities *tdx_cap; + + tdx_cap = tdx_read_capabilities(vm); + + /* TDX spec: any bits 0 in supported_attrs must be 0 in attributes */ + TEST_ASSERT_EQ(attributes & ~tdx_cap->supported_attrs, 0); + + /* TDX spec: any bits 1 in attributes must be 1 in supported_attrs */ + TEST_ASSERT_EQ(attributes & tdx_cap->supported_attrs, attributes); + + free(tdx_cap); +} + void vm_tdx_init_vm(struct kvm_vm *vm, uint64_t attributes) { struct kvm_tdx_init_vm *init_vm; @@ -251,6 +266,8 @@ void vm_tdx_init_vm(struct kvm_vm *vm, uint64_t attributes) memcpy(&init_vm->cpuid, cpuid, kvm_cpuid2_size(cpuid->nent)); free(cpuid);
+ tdx_check_attributes(vm, attributes); + init_vm->attributes = attributes;
vm_tdx_vm_ioctl(vm, KVM_TDX_INIT_VM, 0, init_vm);
From: Ackerley Tng ackerleytng@google.com
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.
Signed-off-by: Ackerley Tng ackerleytng@google.com Co-developed-by: Erdem Aktas erdemaktas@google.com Signed-off-by: Erdem Aktas erdemaktas@google.com Co-developed-by: Sagi Shahar sagis@google.com Signed-off-by: Sagi Shahar sagis@google.com --- .../selftests/kvm/include/x86/tdx/tdx_util.h | 2 + .../selftests/kvm/lib/x86/tdx/tdx_util.c | 97 +++++++++++++++++++ 2 files changed, 99 insertions(+)
diff --git a/tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h b/tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h index a2509959c7ce..2467b6c35557 100644 --- a/tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h +++ b/tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h @@ -71,4 +71,6 @@ void vm_tdx_load_common_boot_parameters(struct kvm_vm *vm); void vm_tdx_load_vcpu_boot_parameters(struct kvm_vm *vm, struct kvm_vcpu *vcpu); void vm_tdx_set_vcpu_entry_point(struct kvm_vcpu *vcpu, void *guest_code);
+void vm_tdx_finalize(struct kvm_vm *vm); + #endif // SELFTESTS_TDX_TDX_UTIL_H diff --git a/tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c b/tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c index d8eab99d9333..4024587ed3c2 100644 --- a/tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +++ b/tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c @@ -274,3 +274,100 @@ void vm_tdx_init_vm(struct kvm_vm *vm, uint64_t attributes)
free(init_vm); } + +static void tdx_init_mem_region(struct kvm_vm *vm, void *source_pages, + uint64_t gpa, uint64_t size) +{ + uint32_t metadata = KVM_TDX_MEASURE_MEMORY_REGION; + struct kvm_tdx_init_mem_region mem_region = { + .source_addr = (uint64_t)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((mem_region.nr_pages > 0) && + ((mem_region.nr_pages * PAGE_SIZE) == size), + "Cannot add partial pages to the guest memory.\n"); + TEST_ASSERT(((uint64_t)source_pages & (PAGE_SIZE - 1)) == 0, + "Source memory buffer is not page aligned\n"); + vm_tdx_vcpu_ioctl(vcpu, KVM_TDX_INIT_MEM_REGION, metadata, &mem_region); +} + +static void tdx_init_pages(struct kvm_vm *vm, void *hva, uint64_t gpa, + uint64_t size) +{ + void *scratch_page = calloc(1, PAGE_SIZE); + uint64_t nr_pages = size / PAGE_SIZE; + int i; + + TEST_ASSERT(scratch_page, + "Could not allocate memory for loading memory region"); + + for (i = 0; i < nr_pages; i++) { + memcpy(scratch_page, hva, PAGE_SIZE); + + tdx_init_mem_region(vm, scratch_page, gpa, PAGE_SIZE); + + hva += PAGE_SIZE; + gpa += PAGE_SIZE; + } + + free(scratch_page); +} + +static void load_td_private_memory(struct kvm_vm *vm) +{ + struct userspace_mem_region *region; + int ctr; + + hash_for_each(vm->regions.slot_hash, ctr, region, slot_node) { + const struct sparsebit *protected_pages = region->protected_phy_pages; + const vm_paddr_t gpa_base = region->region.guest_phys_addr; + const uint64_t hva_base = region->region.userspace_addr; + const sparsebit_idx_t lowest_page_in_region = gpa_base >> vm->page_shift; + + sparsebit_idx_t i; + sparsebit_idx_t j; + + if (!sparsebit_any_set(protected_pages)) + continue; + + sparsebit_for_each_set_range(protected_pages, i, j) { + const uint64_t size_to_load = (j - i + 1) * vm->page_size; + const uint64_t offset = + (i - lowest_page_in_region) * vm->page_size; + const uint64_t hva = hva_base + offset; + const uint64_t gpa = gpa_base + offset; + + vm_set_memory_attributes(vm, gpa, size_to_load, + KVM_MEMORY_ATTRIBUTE_PRIVATE); + + /* + * Here, memory is being loaded from hva to gpa. If the memory + * mapped to hva is also used to back gpa, then a copy has to be + * made just for loading, since KVM_TDX_INIT_MEM_REGION ioctl + * cannot encrypt memory in place. + * + * To determine if memory mapped to hva is also used to back + * gpa, use a heuristic: + * + * If this memslot has guest_memfd, then this memslot should + * have memory backed from two sources: hva for shared memory + * and gpa will be backed by guest_memfd. + */ + if (region->region.guest_memfd == -1) + tdx_init_pages(vm, (void *)hva, gpa, size_to_load); + else + tdx_init_mem_region(vm, (void *)hva, gpa, size_to_load); + } + } +} + +void vm_tdx_finalize(struct kvm_vm *vm) +{ + load_td_private_memory(vm); + vm_tdx_vm_ioctl(vm, KVM_TDX_FINALIZE_VM, 0, NULL); +}
TDX require special handling for VM and VCPU initialization for various reasons: - Special ioctlss for creating VM and VCPU. - TDX registers are inaccessible to KVM. - TDX require special boot code trampoline for loading parameters. - TDX only supports KVM_CAP_SPLIT_IRQCHIP.
Hook this special handling into __vm_create() and vm_arch_vcpu_add() using the utility functions added in previous patches.
Signed-off-by: Sagi Shahar sagis@google.com --- tools/testing/selftests/kvm/lib/kvm_util.c | 24 ++++++++- .../testing/selftests/kvm/lib/x86/processor.c | 49 ++++++++++++++----- 2 files changed, 61 insertions(+), 12 deletions(-)
diff --git a/tools/testing/selftests/kvm/lib/kvm_util.c b/tools/testing/selftests/kvm/lib/kvm_util.c index b4c8702ba4bd..d9f0ff97770d 100644 --- a/tools/testing/selftests/kvm/lib/kvm_util.c +++ b/tools/testing/selftests/kvm/lib/kvm_util.c @@ -4,6 +4,7 @@ * * Copyright (C) 2018, Google LLC. */ +#include "tdx/tdx_util.h" #include "test_util.h" #include "kvm_util.h" #include "processor.h" @@ -465,7 +466,7 @@ void kvm_set_files_rlimit(uint32_t 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 @@ -499,6 +500,12 @@ struct kvm_vm *__vm_create(struct vm_shape shape, uint32_t nr_runnable_vcpus, for (i = 0; i < NR_MEM_REGIONS; i++) vm->memslots[i] = 0;
+ if (is_tdx_vm(vm)) { + /* Setup additional mem regions for TDX. */ + vm_tdx_setup_boot_code_region(vm); + vm_tdx_setup_boot_parameters_region(vm, nr_runnable_vcpus); + } + kvm_vm_elf_load(vm, program_invocation_name);
/* @@ -1728,11 +1735,26 @@ void *addr_gpa2alias(struct kvm_vm *vm, vm_paddr_t gpa) return (void *) ((uintptr_t) region->host_alias + offset); }
+static bool is_split_irqchip_required(struct kvm_vm *vm) +{ +#ifdef __x86_64__ + return is_tdx_vm(vm); +#else + return false; +#endif +} + /* Create an interrupt controller chip for the specified VM. */ void vm_create_irqchip(struct kvm_vm *vm) { int r;
+ if (is_split_irqchip_required(vm)) { + vm_enable_cap(vm, KVM_CAP_SPLIT_IRQCHIP, 24); + vm->has_irqchip = true; + return; + } + /* * Allocate a fully in-kernel IRQ chip by default, but fall back to a * split model (x86 only) if that fails (KVM x86 allows compiling out diff --git a/tools/testing/selftests/kvm/lib/x86/processor.c b/tools/testing/selftests/kvm/lib/x86/processor.c index 4802fc81bea7..5cf14f09c1b6 100644 --- a/tools/testing/selftests/kvm/lib/x86/processor.c +++ b/tools/testing/selftests/kvm/lib/x86/processor.c @@ -670,6 +670,11 @@ void kvm_arch_vm_post_create(struct kvm_vm *vm) vm_sev_ioctl(vm, KVM_SEV_INIT2, &init); }
+ if (is_tdx_vm(vm)) { + vm_tdx_init_vm(vm, 0); + vm_tdx_load_common_boot_parameters(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."); guest_tsc_khz = r; @@ -680,9 +685,13 @@ void vcpu_arch_set_entry_point(struct kvm_vcpu *vcpu, void *guest_code) { struct kvm_regs regs;
- vcpu_regs_get(vcpu, ®s); - regs.rip = (unsigned long) guest_code; - vcpu_regs_set(vcpu, ®s); + if (is_tdx_vm(vcpu->vm)) + vm_tdx_set_vcpu_entry_point(vcpu, guest_code); + else { + vcpu_regs_get(vcpu, ®s); + regs.rip = (unsigned long) guest_code; + vcpu_regs_set(vcpu, ®s); + } }
vm_vaddr_t kvm_allocate_vcpu_stack(struct kvm_vm *vm) @@ -711,6 +720,19 @@ vm_vaddr_t kvm_allocate_vcpu_stack(struct kvm_vm *vm) return stack_vaddr; }
+static void vm_tdx_vcpu_add(struct kvm_vm *vm, struct kvm_vcpu *vcpu) +{ + struct kvm_cpuid2 *cpuid; + + cpuid = allocate_kvm_cpuid2(MAX_NR_CPUID_ENTRIES); + vm_tdx_vcpu_ioctl(vcpu, KVM_TDX_GET_CPUID, 0, cpuid); + vcpu_init_cpuid(vcpu, cpuid); + free(cpuid); + vm_tdx_vcpu_ioctl(vcpu, KVM_TDX_INIT_VCPU, 0, NULL); + + vm_tdx_load_vcpu_boot_parameters(vm, vcpu); +} + struct kvm_vcpu *vm_arch_vcpu_add(struct kvm_vm *vm, uint32_t vcpu_id) { struct kvm_mp_state mp_state; @@ -718,16 +740,21 @@ struct kvm_vcpu *vm_arch_vcpu_add(struct kvm_vm *vm, uint32_t vcpu_id) struct kvm_vcpu *vcpu;
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, ®s); - regs.rflags = regs.rflags | 0x2; - if (vm->type != KVM_X86_TDX_VM) + if (is_tdx_vm(vm)) { + vm_tdx_vcpu_add(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, ®s); + regs.rflags = regs.rflags | 0x2; regs.rsp = kvm_allocate_vcpu_stack(vm); - vcpu_regs_set(vcpu, ®s); + vcpu_regs_set(vcpu, ®s); + }
/* Setup the MP state */ mp_state.mp_state = 0;
From: Erdem Aktas erdemaktas@google.com
Add support for TDX guests to issue TDCALLs to the TDX module.
Signed-off-by: Erdem Aktas erdemaktas@google.com Co-developed-by: Sagi Shahar sagis@google.com Signed-off-by: Sagi Shahar sagis@google.com --- tools/testing/selftests/kvm/Makefile.kvm | 8 ++ .../selftests/kvm/include/x86/tdx/tdcall.h | 34 +++++++ .../selftests/kvm/lib/x86/tdx/tdcall.S | 93 +++++++++++++++++++ .../kvm/lib/x86/tdx/tdcall_offsets.c | 16 ++++ 4 files changed, 151 insertions(+) create mode 100644 tools/testing/selftests/kvm/include/x86/tdx/tdcall.h create mode 100644 tools/testing/selftests/kvm/lib/x86/tdx/tdcall.S create mode 100644 tools/testing/selftests/kvm/lib/x86/tdx/tdcall_offsets.c
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm index c42b579fb7c5..1f541c0d4fe1 100644 --- a/tools/testing/selftests/kvm/Makefile.kvm +++ b/tools/testing/selftests/kvm/Makefile.kvm @@ -20,6 +20,7 @@ LIBKVM += lib/userfaultfd_util.c LIBKVM_STRING += lib/string_override.c
LIBKVM_ASM_DEFS += lib/x86/tdx/td_boot_offsets.c +LIBKVM_ASM_DEFS += lib/x86/tdx/tdcall_offsets.c
LIBKVM_x86 += lib/x86/apic.c LIBKVM_x86 += lib/x86/handlers.S @@ -33,6 +34,7 @@ LIBKVM_x86 += lib/x86/ucall.c LIBKVM_x86 += lib/x86/vmx.c LIBKVM_x86 += lib/x86/tdx/tdx_util.c LIBKVM_x86 += lib/x86/tdx/td_boot.S +LIBKVM_x86 += lib/x86/tdx/tdcall.S
LIBKVM_arm64 += lib/arm64/gic.c LIBKVM_arm64 += lib/arm64/gic_v3.c @@ -342,7 +344,13 @@ $(OUTPUT)/lib/x86/tdx/td_boot.o: $(OUTPUT)/include/x86/tdx/td_boot_offsets.h $(OUTPUT)/include/x86/tdx/td_boot_offsets.h: $(OUTPUT)/lib/x86/tdx/td_boot_offsets.s FORCE $(call filechk,offsets,__TDX_BOOT_OFFSETS_H__)
+$(OUTPUT)/lib/x86/tdx/tdcall.o: $(OUTPUT)/include/x86/tdx/tdcall_offsets.h + +$(OUTPUT)/include/x86/tdx/tdcall_offsets.h: $(OUTPUT)/lib/x86/tdx/tdcall_offsets.s FORCE + $(call filechk,offsets,__TDCALL__OFFSETS_H__) + EXTRA_CLEAN += $(OUTPUT)/include/x86/tdx/td_boot_offsets.h +EXTRA_CLEAN += $(OUTPUT)/include/x86/tdx/tdcall_offsets.h
$(shell mkdir -p $(sort $(dir $(TEST_GEN_PROGS)))) $(SPLIT_TEST_GEN_OBJ): $(GEN_HDRS) diff --git a/tools/testing/selftests/kvm/include/x86/tdx/tdcall.h b/tools/testing/selftests/kvm/include/x86/tdx/tdcall.h new file mode 100644 index 000000000000..60c70646f876 --- /dev/null +++ b/tools/testing/selftests/kvm/include/x86/tdx/tdcall.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Adapted from arch/x86/include/asm/shared/tdx.h */ + +#ifndef SELFTESTS_TDX_TDCALL_H +#define SELFTESTS_TDX_TDCALL_H + +#include <linux/bits.h> + +#define TDX_TDCALL_HAS_OUTPUT BIT(0) + +#ifndef __ASSEMBLY__ + +#include <linux/types.h> + +/* + * Used in __tdx_tdcall() to pass down and get back registers' values of + * the TDCALL instruction when requesting services from the VMM. + * + * This is a software only structure and not part of the TDX module/VMM ABI. + */ +struct tdx_tdcall_args { + u64 r10; + u64 r11; + u64 r12; + u64 r13; + u64 r14; + u64 r15; +}; + +/* Used to request services from the VMM */ +u64 __tdx_tdcall(struct tdx_tdcall_args *args, unsigned long flags); + +#endif // __ASSEMBLY__ +#endif // SELFTESTS_TDX_TDCALL_H diff --git a/tools/testing/selftests/kvm/lib/x86/tdx/tdcall.S b/tools/testing/selftests/kvm/lib/x86/tdx/tdcall.S new file mode 100644 index 000000000000..05869e86b9d8 --- /dev/null +++ b/tools/testing/selftests/kvm/lib/x86/tdx/tdcall.S @@ -0,0 +1,93 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Adapted from arch/x86/virt/vmx/tdx/tdxcall.S */ + +#ifndef __ASSEMBLY__ +#define __ASSEMBLY__ +#endif + +#include <linux/bits.h> +#include "tdx/tdcall.h" +#include "tdx/tdcall_offsets.h" + +/* + * TDCALL is supported in Binutils >= 2.36, add it for older version. + */ +#define tdcall .byte 0x66,0x0f,0x01,0xcc + +/* + * Bitmasks of exposed registers (with VMM). + */ +#define TDX_R10 BIT(10) +#define TDX_R11 BIT(11) +#define TDX_R12 BIT(12) +#define TDX_R13 BIT(13) +#define TDX_R14 BIT(14) +#define TDX_R15 BIT(15) + +/* + * These registers are clobbered to hold arguments for each + * TDVMCALL. They are safe to expose to the VMM. + * Each bit in this mask represents a register ID. Bit field + * details can be found in TDX GHCI specification, section + * titled "TDCALL [TDG.VP.VMCALL] leaf". + */ +#define TDVMCALL_EXPOSE_REGS_MASK \ + (TDX_R10 | TDX_R11 | TDX_R12 | TDX_R13 | TDX_R14 | TDX_R15) + +.code64 +.section .text + +.globl __tdx_tdcall +.type __tdx_tdcall, @function +__tdx_tdcall: + /* Set up stack frame */ + push %rbp + movq %rsp, %rbp + + /* Save callee-saved GPRs as mandated by the x86_64 ABI */ + push %r15 + push %r14 + push %r13 + push %r12 + + /* Mangle function call ABI into TDCALL ABI: */ + /* Set TDCALL leaf ID (TDVMCALL (0)) in RAX */ + xor %eax, %eax + + /* Copy tdcall registers from arg struct: */ + movq TDX_TDCALL_R10(%rdi), %r10 + movq TDX_TDCALL_R11(%rdi), %r11 + movq TDX_TDCALL_R12(%rdi), %r12 + movq TDX_TDCALL_R13(%rdi), %r13 + movq TDX_TDCALL_R14(%rdi), %r14 + movq TDX_TDCALL_R15(%rdi), %r15 + + movl $TDVMCALL_EXPOSE_REGS_MASK, %ecx + + tdcall + + /* TDVMCALL leaf return code is in R10 */ + movq %r10, %rax + + /* Copy tdcall result registers to arg struct if needed */ + testq $TDX_TDCALL_HAS_OUTPUT, %rsi + jz .Lout + + movq %r10, TDX_TDCALL_R10(%rdi) + movq %r11, TDX_TDCALL_R11(%rdi) + movq %r12, TDX_TDCALL_R12(%rdi) + movq %r13, TDX_TDCALL_R13(%rdi) + movq %r14, TDX_TDCALL_R14(%rdi) + movq %r15, TDX_TDCALL_R15(%rdi) +.Lout: + /* Restore callee-saved GPRs as mandated by the x86_64 ABI */ + pop %r12 + pop %r13 + pop %r14 + pop %r15 + + pop %rbp + ret + +/* Disable executable stack */ +.section .note.GNU-stack,"",%progbits diff --git a/tools/testing/selftests/kvm/lib/x86/tdx/tdcall_offsets.c b/tools/testing/selftests/kvm/lib/x86/tdx/tdcall_offsets.c new file mode 100644 index 000000000000..dcd4457be6e5 --- /dev/null +++ b/tools/testing/selftests/kvm/lib/x86/tdx/tdcall_offsets.c @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0 +#define COMPILE_OFFSETS + +#include <linux/kbuild.h> + +#include "tdx/tdcall.h" + +static void __attribute__((used)) common(void) +{ + OFFSET(TDX_TDCALL_R10, tdx_tdcall_args, r10); + OFFSET(TDX_TDCALL_R11, tdx_tdcall_args, r11); + OFFSET(TDX_TDCALL_R12, tdx_tdcall_args, r12); + OFFSET(TDX_TDCALL_R13, tdx_tdcall_args, r13); + OFFSET(TDX_TDCALL_R14, tdx_tdcall_args, r14); + OFFSET(TDX_TDCALL_R15, tdx_tdcall_args, r15); +}
Add utility function to issue MMIO TDCALL from TDX guests.
Signed-off-by: Sagi Shahar sagis@google.com --- tools/testing/selftests/kvm/Makefile.kvm | 1 + .../selftests/kvm/include/x86/tdx/tdx.h | 14 ++++++++++++ tools/testing/selftests/kvm/lib/x86/tdx/tdx.c | 22 +++++++++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 tools/testing/selftests/kvm/include/x86/tdx/tdx.h create mode 100644 tools/testing/selftests/kvm/lib/x86/tdx/tdx.c
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm index 1f541c0d4fe1..8d1aaebd746e 100644 --- a/tools/testing/selftests/kvm/Makefile.kvm +++ b/tools/testing/selftests/kvm/Makefile.kvm @@ -35,6 +35,7 @@ LIBKVM_x86 += lib/x86/vmx.c LIBKVM_x86 += lib/x86/tdx/tdx_util.c LIBKVM_x86 += lib/x86/tdx/td_boot.S LIBKVM_x86 += lib/x86/tdx/tdcall.S +LIBKVM_x86 += lib/x86/tdx/tdx.c
LIBKVM_arm64 += lib/arm64/gic.c LIBKVM_arm64 += lib/arm64/gic_v3.c diff --git a/tools/testing/selftests/kvm/include/x86/tdx/tdx.h b/tools/testing/selftests/kvm/include/x86/tdx/tdx.h new file mode 100644 index 000000000000..22b096402998 --- /dev/null +++ b/tools/testing/selftests/kvm/include/x86/tdx/tdx.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef SELFTESTS_TDX_TDX_H +#define SELFTESTS_TDX_TDX_H + +#include <stdint.h> + +/* MMIO direction */ +#define MMIO_READ 0 +#define MMIO_WRITE 1 + +uint64_t tdg_vp_vmcall_ve_request_mmio_write(uint64_t address, uint64_t size, + uint64_t data_in); + +#endif // SELFTESTS_TDX_TDX_H diff --git a/tools/testing/selftests/kvm/lib/x86/tdx/tdx.c b/tools/testing/selftests/kvm/lib/x86/tdx/tdx.c new file mode 100644 index 000000000000..12df30ac1ceb --- /dev/null +++ b/tools/testing/selftests/kvm/lib/x86/tdx/tdx.c @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include "tdx/tdcall.h" +#include "tdx/tdx.h" + +#define TDG_VP_VMCALL 0 + +#define TDG_VP_VMCALL_VE_REQUEST_MMIO 48 + +uint64_t tdg_vp_vmcall_ve_request_mmio_write(uint64_t address, uint64_t size, + uint64_t data_in) +{ + struct tdx_tdcall_args args = { + .r11 = TDG_VP_VMCALL_VE_REQUEST_MMIO, + .r12 = size, + .r13 = MMIO_WRITE, + .r14 = address, + .r15 = data_in, + }; + + return __tdx_tdcall(&args, 0); +}
From: Ackerley Tng ackerleytng@google.com
ucalls for non-Coco VMs work by having the guest write to the rdi register, then perform an io instruction to exit to the host. The host then reads rdi using kvm_get_regs().
CPU registers can't be read using kvm_get_regs() for TDX, so TDX guests use MMIO to pass the struct ucall's hva to the host. MMIO was chosen because it is one of the simplest (hence unlikely to fail) mechanisms that support passing 8 bytes from guest to host.
Signed-off-by: Ackerley Tng ackerleytng@google.com Co-developed-by: Sagi Shahar sagis@google.com Signed-off-by: Sagi Shahar sagis@google.com --- .../testing/selftests/kvm/include/x86/ucall.h | 4 +- tools/testing/selftests/kvm/lib/x86/ucall.c | 45 ++++++++++++++++--- 2 files changed, 41 insertions(+), 8 deletions(-)
diff --git a/tools/testing/selftests/kvm/include/x86/ucall.h b/tools/testing/selftests/kvm/include/x86/ucall.h index d3825dcc3cd9..0494a4a21557 100644 --- a/tools/testing/selftests/kvm/include/x86/ucall.h +++ b/tools/testing/selftests/kvm/include/x86/ucall.h @@ -6,8 +6,6 @@
#define UCALL_EXIT_REASON KVM_EXIT_IO
-static inline void ucall_arch_init(struct kvm_vm *vm, vm_paddr_t mmio_gpa) -{ -} +void ucall_arch_init(struct kvm_vm *vm, vm_paddr_t mmio_gpa);
#endif diff --git a/tools/testing/selftests/kvm/lib/x86/ucall.c b/tools/testing/selftests/kvm/lib/x86/ucall.c index 1265cecc7dd1..0ad24baaa3c4 100644 --- a/tools/testing/selftests/kvm/lib/x86/ucall.c +++ b/tools/testing/selftests/kvm/lib/x86/ucall.c @@ -5,11 +5,34 @@ * Copyright (C) 2018, Red Hat, Inc. */ #include "kvm_util.h" +#include "tdx/tdx.h"
#define UCALL_PIO_PORT ((uint16_t)0x1000)
+static uint8_t vm_type; +static vm_paddr_t host_ucall_mmio_gpa; +static vm_paddr_t ucall_mmio_gpa; + +void ucall_arch_init(struct kvm_vm *vm, vm_paddr_t mmio_gpa) +{ + vm_type = vm->type; + sync_global_to_guest(vm, vm_type); + + host_ucall_mmio_gpa = ucall_mmio_gpa = mmio_gpa; + + if (vm_type == KVM_X86_TDX_VM) + ucall_mmio_gpa |= vm->arch.s_bit; + + sync_global_to_guest(vm, ucall_mmio_gpa); +} + void ucall_arch_do_ucall(vm_vaddr_t uc) { + if (vm_type == KVM_X86_TDX_VM) { + tdg_vp_vmcall_ve_request_mmio_write(ucall_mmio_gpa, 8, 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,11 +69,23 @@ void *ucall_arch_get_ucall(struct kvm_vcpu *vcpu) { struct kvm_run *run = vcpu->run;
- if (run->exit_reason == KVM_EXIT_IO && run->io.port == UCALL_PIO_PORT) { - struct kvm_regs regs; + switch (vm_type) { + case KVM_X86_TDX_VM: + if (vcpu->run->exit_reason == KVM_EXIT_MMIO && + vcpu->run->mmio.phys_addr == host_ucall_mmio_gpa && + vcpu->run->mmio.len == 8 && vcpu->run->mmio.is_write) { + uint64_t data = *(uint64_t *)vcpu->run->mmio.data; + + return (void *)data; + } + return NULL; + default: + if (run->exit_reason == KVM_EXIT_IO && run->io.port == UCALL_PIO_PORT) { + struct kvm_regs regs;
- vcpu_regs_get(vcpu, ®s); - return (void *)regs.rdi; + vcpu_regs_get(vcpu, ®s); + return (void *)regs.rdi; + } + return NULL; } - return NULL; }
Adding a test to verify TDX lifecycle by creating a simple TD.
Signed-off-by: Sagi Shahar sagis@google.com --- tools/testing/selftests/kvm/Makefile.kvm | 1 + .../selftests/kvm/include/x86/tdx/tdx_util.h | 10 ++++++ .../selftests/kvm/lib/x86/tdx/tdx_util.c | 18 +++++++++++ tools/testing/selftests/kvm/x86/tdx_vm_test.c | 31 +++++++++++++++++++ 4 files changed, 60 insertions(+) create mode 100644 tools/testing/selftests/kvm/x86/tdx_vm_test.c
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm index 8d1aaebd746e..86c101fbe1a0 100644 --- a/tools/testing/selftests/kvm/Makefile.kvm +++ b/tools/testing/selftests/kvm/Makefile.kvm @@ -155,6 +155,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 diff --git a/tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h b/tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h index 2467b6c35557..775ca249f74d 100644 --- a/tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h +++ b/tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h @@ -11,6 +11,14 @@ static inline bool is_tdx_vm(struct kvm_vm *vm) return vm->type == KVM_X86_TDX_VM; }
+/* + * Verify that TDX is supported by KVM. + */ +static inline bool is_tdx_enabled(void) +{ + return !!(kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(KVM_X86_TDX_VM)); +} + /* * TDX ioctls */ @@ -72,5 +80,7 @@ void vm_tdx_load_vcpu_boot_parameters(struct kvm_vm *vm, struct kvm_vcpu *vcpu); void vm_tdx_set_vcpu_entry_point(struct kvm_vcpu *vcpu, void *guest_code);
void vm_tdx_finalize(struct kvm_vm *vm); +struct kvm_vm *vm_tdx_create_with_one_vcpu(void *guest_code, + struct kvm_vcpu **vcpu);
#endif // SELFTESTS_TDX_TDX_UTIL_H diff --git a/tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c b/tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c index 4024587ed3c2..8b18f1a8da62 100644 --- a/tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c +++ b/tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c @@ -371,3 +371,21 @@ void vm_tdx_finalize(struct kvm_vm *vm) load_td_private_memory(vm); vm_tdx_vm_ioctl(vm, KVM_TDX_FINALIZE_VM, 0, NULL); } + +struct kvm_vm *vm_tdx_create_with_one_vcpu(void *guest_code, + struct kvm_vcpu **vcpu) +{ + struct vm_shape shape = { + .mode = VM_MODE_DEFAULT, + .type = KVM_X86_TDX_VM, + }; + struct kvm_vm *vm; + struct kvm_vcpu *vcpus[1]; + + vm = __vm_create_with_vcpus(shape, 1, 0, guest_code, vcpus); + *vcpu = vcpus[0]; + + vm_tdx_finalize(vm); + + return vm; +} diff --git a/tools/testing/selftests/kvm/x86/tdx_vm_test.c b/tools/testing/selftests/kvm/x86/tdx_vm_test.c new file mode 100644 index 000000000000..a9ee489eea1a --- /dev/null +++ b/tools/testing/selftests/kvm/x86/tdx_vm_test.c @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#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_tdx_create_with_one_vcpu(guest_code_lifecycle, &vcpu); + + 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_enabled()); + return test_harness_run(argc, argv); +}
linux-kselftest-mirror@lists.linaro.org