From: Sagi Shahar <sagis@google.com>
The test reads CPUID values from inside a TD VM and compare them
to expected values.
The test targets CPUID values which are virtualized as "As Configured",
"As Configured (if Native)", "Calculated", "Fixed" and "Native"
according to the TDX spec.
Signed-off-by: Sagi Shahar <sagis@google.com>
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
Changes RFCv2 -> RFCv3
+ Manually inlined cpuid function in cpuid test. This highlights the
purpose of this test - to test the result of the cpuid instruction.
+ Replace find_cpuid_entry with kvm_get_supported_cpuid_entry from
tools/testing/selftests/kvm/lib/x86_64/processor.c
---
.../kvm/include/x86_64/tdx/test_util.h | 9 ++
.../selftests/kvm/lib/x86_64/tdx/test_util.c | 11 ++
.../selftests/kvm/x86_64/tdx_vm_tests.c | 106 ++++++++++++++++++
3 files changed, 126 insertions(+)
--
2.39.0.246.g2a6d74b583-goog
@@ -9,6 +9,9 @@
#define TDX_TEST_SUCCESS_PORT 0x30
#define TDX_TEST_SUCCESS_SIZE 4
+#define TDX_TEST_REPORT_PORT 0x31
+#define TDX_TEST_REPORT_SIZE 4
+
/**
* Assert that some IO operation involving tdg_vp_vmcall_instruction_io() was
* called in the guest.
@@ -102,4 +105,10 @@ void tdx_test_fatal(uint64_t error_code);
*/
void tdx_test_fatal_with_data(uint64_t error_code, uint64_t data_gpa);
+/**
+ * Report a 32 bit value from the guest to user space using TDG.VP.VMCALL
+ * <Instruction.IO> call. Data is reported on port TDX_TEST_REPORT_PORT.
+ */
+uint64_t tdx_test_report_to_user_space(uint32_t data);
+
#endif // SELFTEST_TDX_TEST_UTIL_H
@@ -42,3 +42,14 @@ void tdx_test_fatal(uint64_t error_code)
{
tdx_test_fatal_with_data(error_code, 0);
}
+
+uint64_t tdx_test_report_to_user_space(uint32_t data)
+{
+ /* Upcast data to match tdg_vp_vmcall_instruction_io signature */
+ uint64_t data_64 = data;
+
+ return tdg_vp_vmcall_instruction_io(TDX_TEST_REPORT_PORT,
+ TDX_TEST_REPORT_SIZE,
+ TDG_VP_VMCALL_INSTRUCTION_IO_WRITE,
+ &data_64);
+}
@@ -2,6 +2,7 @@
#include <signal.h>
#include "kvm_util_base.h"
+#include "processor.h"
#include "tdx/tdcall.h"
#include "tdx/tdx.h"
#include "tdx/tdx_util.h"
@@ -154,6 +155,110 @@ void verify_td_ioexit(void)
printf("\t ... PASSED\n");
}
+/*
+ * Verifies CPUID functionality by reading CPUID values in guest. The guest
+ * will then send the values to userspace using an IO write to be checked
+ * against the expected values.
+ */
+void guest_code_cpuid(void)
+{
+ uint64_t err;
+ uint32_t ebx, ecx;
+
+ /* Read CPUID leaf 0x1 */
+ asm volatile (
+ "cpuid"
+ : "=b" (ebx), "=c" (ecx)
+ : "a" (0x1)
+ : "edx");
+
+ err = tdx_test_report_to_user_space(ebx);
+ if (err)
+ tdx_test_fatal(err);
+
+ err = tdx_test_report_to_user_space(ecx);
+ if (err)
+ tdx_test_fatal(err);
+
+ tdx_test_success();
+}
+
+void verify_td_cpuid(void)
+{
+ struct kvm_vm *vm;
+ struct kvm_vcpu *vcpu;
+
+ uint32_t ebx, ecx;
+ const struct kvm_cpuid_entry2 *cpuid_entry;
+ uint32_t guest_clflush_line_size;
+ uint32_t guest_max_addressable_ids, host_max_addressable_ids;
+ uint32_t guest_sse3_enabled;
+ uint32_t guest_fma_enabled;
+ uint32_t guest_initial_apic_id;
+
+ vm = td_create();
+ td_initialize(vm, VM_MEM_SRC_ANONYMOUS, 0);
+ vcpu = td_vcpu_add(vm, 0, guest_code_cpuid);
+ td_finalize(vm);
+
+ printf("Verifying TD CPUID:\n");
+
+ /* Wait for guest to report ebx value */
+ vcpu_run(vcpu);
+ TDX_TEST_CHECK_GUEST_FAILURE(vcpu);
+ TDX_TEST_ASSERT_IO(vcpu, TDX_TEST_REPORT_PORT, 4,
+ TDG_VP_VMCALL_INSTRUCTION_IO_WRITE);
+ ebx = *(uint32_t *)((void *)vcpu->run + vcpu->run->io.data_offset);
+
+ /* Wait for guest to report either ecx value or error */
+ vcpu_run(vcpu);
+ TDX_TEST_CHECK_GUEST_FAILURE(vcpu);
+ TDX_TEST_ASSERT_IO(vcpu, TDX_TEST_REPORT_PORT, 4,
+ TDG_VP_VMCALL_INSTRUCTION_IO_WRITE);
+ ecx = *(uint32_t *)((void *)vcpu->run + vcpu->run->io.data_offset);
+
+ /* Wait for guest to complete execution */
+ vcpu_run(vcpu);
+ TDX_TEST_CHECK_GUEST_FAILURE(vcpu);
+ TDX_TEST_ASSERT_SUCCESS(vcpu);
+
+ /* Verify the CPUID values we got from the guest. */
+ printf("\t ... Verifying CPUID values from guest\n");
+
+ /* Get KVM CPUIDs for reference */
+ cpuid_entry = kvm_get_supported_cpuid_entry(1);
+ TEST_ASSERT(cpuid_entry, "CPUID entry missing\n");
+
+ host_max_addressable_ids = (cpuid_entry->ebx >> 16) & 0xFF;
+
+ guest_sse3_enabled = ecx & 0x1; // Native
+ guest_clflush_line_size = (ebx >> 8) & 0xFF; // Fixed
+ guest_max_addressable_ids = (ebx >> 16) & 0xFF; // As Configured
+ guest_fma_enabled = (ecx >> 12) & 0x1; // As Configured (if Native)
+ guest_initial_apic_id = (ebx >> 24) & 0xFF; // Calculated
+
+ ASSERT_EQ(guest_sse3_enabled, 1);
+ ASSERT_EQ(guest_clflush_line_size, 8);
+ ASSERT_EQ(guest_max_addressable_ids, host_max_addressable_ids);
+
+ /* TODO: This only tests the native value. To properly test
+ * "As Configured (if Native)" we need to override this value
+ * in the TD params
+ */
+ ASSERT_EQ(guest_fma_enabled, 1);
+
+ /* TODO: guest_initial_apic_id is calculated based on the number of
+ * VCPUs in the TD. From the spec: "Virtual CPU index, starting from 0
+ * and allocated sequentially on each successful TDH.VP.INIT"
+ * To test non-trivial values we either need a TD with multiple VCPUs
+ * or to pick a different calculated value.
+ */
+ ASSERT_EQ(guest_initial_apic_id, 0);
+
+ kvm_vm_free(vm);
+ printf("\t ... PASSED\n");
+}
+
int main(int argc, char **argv)
{
setbuf(stdout, NULL);
@@ -166,6 +271,7 @@ int main(int argc, char **argv)
run_in_new_process(&verify_td_lifecycle);
run_in_new_process(&verify_report_fatal_error);
run_in_new_process(&verify_td_ioexit);
+ run_in_new_process(&verify_td_cpuid);
return 0;
}