KVM: selftests: Add test to verify KVM's supported XCR0
authorAaron Lewis <aaronlewis@google.com>
Wed, 5 Apr 2023 00:45:20 +0000 (17:45 -0700)
committerSean Christopherson <seanjc@google.com>
Tue, 11 Apr 2023 17:19:04 +0000 (10:19 -0700)
Check both architectural rules and KVM's ABI for KVM_GET_SUPPORTED_CPUID
to ensure the supported xfeatures[1] don't violate any of them.

The architectural rules[2] and KVM's contract with userspace ensure for a
given feature, e.g. sse, avx, amx, etc... their associated xfeatures are
either all sets or none of them are set, and any dependencies are enabled
if needed.

[1] EDX:EAX of CPUID.(EAX=0DH,ECX=0)
[2] SDM vol 1, 13.3 ENABLING THE XSAVE FEATURE SET AND XSAVE-ENABLED
    FEATURES

Cc: Mingwei Zhang <mizhang@google.com>
Signed-off-by: Aaron Lewis <aaronlewis@google.com>
[sean: expand comments, use a fancy X86_PROPERTY]
Reviewed-by: Aaron Lewis <aaronlewis@google.com>
Tested-by: Aaron Lewis <aaronlewis@google.com>
Link: https://lore.kernel.org/r/20230405004520.421768-7-seanjc@google.com
Signed-off-by: Sean Christopherson <seanjc@google.com>
tools/testing/selftests/kvm/Makefile
tools/testing/selftests/kvm/include/x86_64/processor.h
tools/testing/selftests/kvm/x86_64/xcr0_cpuid_test.c [new file with mode: 0644]

index 84a627c43795669a7839b93d4dc99c6773e7eeac..18cadc6697983dd56e26283d1a1cda17aea41762 100644 (file)
@@ -105,6 +105,7 @@ TEST_GEN_PROGS_x86_64 += x86_64/vmx_tsc_adjust_test
 TEST_GEN_PROGS_x86_64 += x86_64/vmx_nested_tsc_scaling_test
 TEST_GEN_PROGS_x86_64 += x86_64/xapic_ipi_test
 TEST_GEN_PROGS_x86_64 += x86_64/xapic_state_test
+TEST_GEN_PROGS_x86_64 += x86_64/xcr0_cpuid_test
 TEST_GEN_PROGS_x86_64 += x86_64/xss_msr_test
 TEST_GEN_PROGS_x86_64 += x86_64/debug_regs
 TEST_GEN_PROGS_x86_64 += x86_64/tsc_msrs_test
index 187309f3e7e9726600bebee7ec033274279ddb0d..70c5469e4023659083e1b584714074533bdbd003 100644 (file)
@@ -241,8 +241,11 @@ struct kvm_x86_cpu_property {
 #define X86_PROPERTY_PMU_NR_GP_COUNTERS                KVM_X86_CPU_PROPERTY(0xa, 0, EAX, 8, 15)
 #define X86_PROPERTY_PMU_EBX_BIT_VECTOR_LENGTH KVM_X86_CPU_PROPERTY(0xa, 0, EAX, 24, 31)
 
+#define X86_PROPERTY_SUPPORTED_XCR0_LO         KVM_X86_CPU_PROPERTY(0xd,  0, EAX,  0, 31)
 #define X86_PROPERTY_XSTATE_MAX_SIZE_XCR0      KVM_X86_CPU_PROPERTY(0xd,  0, EBX,  0, 31)
 #define X86_PROPERTY_XSTATE_MAX_SIZE           KVM_X86_CPU_PROPERTY(0xd,  0, ECX,  0, 31)
+#define X86_PROPERTY_SUPPORTED_XCR0_HI         KVM_X86_CPU_PROPERTY(0xd,  0, EDX,  0, 31)
+
 #define X86_PROPERTY_XSTATE_TILE_SIZE          KVM_X86_CPU_PROPERTY(0xd, 18, EAX,  0, 31)
 #define X86_PROPERTY_XSTATE_TILE_OFFSET                KVM_X86_CPU_PROPERTY(0xd, 18, EBX,  0, 31)
 #define X86_PROPERTY_AMX_MAX_PALETTE_TABLES    KVM_X86_CPU_PROPERTY(0x1d, 0, EAX,  0, 31)
@@ -681,6 +684,15 @@ static inline bool this_pmu_has(struct kvm_x86_pmu_feature feature)
               !this_cpu_has(feature.anti_feature);
 }
 
+static __always_inline uint64_t this_cpu_supported_xcr0(void)
+{
+       if (!this_cpu_has_p(X86_PROPERTY_SUPPORTED_XCR0_LO))
+               return 0;
+
+       return this_cpu_property(X86_PROPERTY_SUPPORTED_XCR0_LO) |
+              ((uint64_t)this_cpu_property(X86_PROPERTY_SUPPORTED_XCR0_HI) << 32);
+}
+
 typedef u32            __attribute__((vector_size(16))) sse128_t;
 #define __sse128_u     union { sse128_t vec; u64 as_u64[2]; u32 as_u32[4]; }
 #define sse128_lo(x)   ({ __sse128_u t; t.vec = x; t.as_u64[0]; })
@@ -1104,6 +1116,14 @@ static inline uint8_t wrmsr_safe(uint32_t msr, uint64_t val)
        return kvm_asm_safe("wrmsr", "a"(val & -1u), "d"(val >> 32), "c"(msr));
 }
 
+static inline uint8_t xsetbv_safe(uint32_t index, uint64_t value)
+{
+       u32 eax = value;
+       u32 edx = value >> 32;
+
+       return kvm_asm_safe("xsetbv", "a" (eax), "d" (edx), "c" (index));
+}
+
 bool kvm_is_tdp_enabled(void);
 
 uint64_t *__vm_get_page_table_entry(struct kvm_vm *vm, uint64_t vaddr,
diff --git a/tools/testing/selftests/kvm/x86_64/xcr0_cpuid_test.c b/tools/testing/selftests/kvm/x86_64/xcr0_cpuid_test.c
new file mode 100644 (file)
index 0000000..905bd5a
--- /dev/null
@@ -0,0 +1,132 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * XCR0 cpuid test
+ *
+ * Copyright (C) 2022, Google LLC.
+ */
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+
+#include "test_util.h"
+
+#include "kvm_util.h"
+#include "processor.h"
+
+/*
+ * Assert that architectural dependency rules are satisfied, e.g. that AVX is
+ * supported if and only if SSE is supported.
+ */
+#define ASSERT_XFEATURE_DEPENDENCIES(supported_xcr0, xfeatures, dependencies)    \
+do {                                                                             \
+       uint64_t __supported = (supported_xcr0) & ((xfeatures) | (dependencies)); \
+                                                                                 \
+       GUEST_ASSERT_3((__supported & (xfeatures)) != (xfeatures) ||              \
+                      __supported == ((xfeatures) | (dependencies)),             \
+                      __supported, (xfeatures), (dependencies));                 \
+} while (0)
+
+/*
+ * Assert that KVM reports a sane, usable as-is XCR0.  Architecturally, a CPU
+ * isn't strictly required to _support_ all XFeatures related to a feature, but
+ * at the same time XSETBV will #GP if bundled XFeatures aren't enabled and
+ * disabled coherently.  E.g. a CPU can technically enumerate supported for
+ * XTILE_CFG but not XTILE_DATA, but attempting to enable XTILE_CFG without
+ * XTILE_DATA will #GP.
+ */
+#define ASSERT_ALL_OR_NONE_XFEATURE(supported_xcr0, xfeatures)         \
+do {                                                                   \
+       uint64_t __supported = (supported_xcr0) & (xfeatures);          \
+                                                                       \
+       GUEST_ASSERT_2(!__supported || __supported == (xfeatures),      \
+                      __supported, (xfeatures));                       \
+} while (0)
+
+static void guest_code(void)
+{
+       uint64_t xcr0_reset;
+       uint64_t supported_xcr0;
+       int i, vector;
+
+       set_cr4(get_cr4() | X86_CR4_OSXSAVE);
+
+       xcr0_reset = xgetbv(0);
+       supported_xcr0 = this_cpu_supported_xcr0();
+
+       GUEST_ASSERT(xcr0_reset == XFEATURE_MASK_FP);
+
+       /* Check AVX */
+       ASSERT_XFEATURE_DEPENDENCIES(supported_xcr0,
+                                    XFEATURE_MASK_YMM,
+                                    XFEATURE_MASK_SSE);
+
+       /* Check MPX */
+       ASSERT_ALL_OR_NONE_XFEATURE(supported_xcr0,
+                                   XFEATURE_MASK_BNDREGS | XFEATURE_MASK_BNDCSR);
+
+       /* Check AVX-512 */
+       ASSERT_XFEATURE_DEPENDENCIES(supported_xcr0,
+                                    XFEATURE_MASK_AVX512,
+                                    XFEATURE_MASK_SSE | XFEATURE_MASK_YMM);
+       ASSERT_ALL_OR_NONE_XFEATURE(supported_xcr0,
+                                   XFEATURE_MASK_AVX512);
+
+       /* Check AMX */
+       ASSERT_ALL_OR_NONE_XFEATURE(supported_xcr0,
+                                   XFEATURE_MASK_XTILE);
+
+       vector = xsetbv_safe(0, supported_xcr0);
+       GUEST_ASSERT_2(!vector, supported_xcr0, vector);
+
+       for (i = 0; i < 64; i++) {
+               if (supported_xcr0 & BIT_ULL(i))
+                       continue;
+
+               vector = xsetbv_safe(0, supported_xcr0 | BIT_ULL(i));
+               GUEST_ASSERT_3(vector == GP_VECTOR, supported_xcr0, vector, BIT_ULL(i));
+       }
+
+       GUEST_DONE();
+}
+
+int main(int argc, char *argv[])
+{
+       struct kvm_vcpu *vcpu;
+       struct kvm_run *run;
+       struct kvm_vm *vm;
+       struct ucall uc;
+
+       TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_XSAVE));
+
+       vm = vm_create_with_one_vcpu(&vcpu, guest_code);
+       run = vcpu->run;
+
+       vm_init_descriptor_tables(vm);
+       vcpu_init_descriptor_tables(vcpu);
+
+       while (1) {
+               vcpu_run(vcpu);
+
+               TEST_ASSERT(run->exit_reason == KVM_EXIT_IO,
+                           "Unexpected exit reason: %u (%s),\n",
+                           run->exit_reason,
+                           exit_reason_str(run->exit_reason));
+
+               switch (get_ucall(vcpu, &uc)) {
+               case UCALL_ABORT:
+                       REPORT_GUEST_ASSERT_3(uc, "0x%lx 0x%lx 0x%lx");
+                       break;
+               case UCALL_DONE:
+                       goto done;
+               default:
+                       TEST_FAIL("Unknown ucall %lu", uc.cmd);
+               }
+       }
+
+done:
+       kvm_vm_free(vm);
+       return 0;
+}