x86: svm: Add test for L2 change of CR4.OSXSAVE

If L1 allows L2 to modify CR4.OSXSAVE, then L0 kvm recalculates the
guest's CPUID.01H:ECX.OSXSAVE bit when the L2 guest changes
CR4.OSXSAVE via MOV-to-CR4. Verify that kvm also recalculates this
CPUID bit when loading L1's CR4 from the save.cr4 field of the
hsave area.

Signed-off-by: Jim Mattson <jmattson@google.com>
Reviewed-by: Ricardo Koller <ricarkol@google.com>
Reviewed-by: Peter Shier <pshier@google.com>
Message-Id: <20201029171024.486256-2-jmattson@google.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
diff --git a/x86/svm_tests.c b/x86/svm_tests.c
index 3b0424a..e2455c8 100644
--- a/x86/svm_tests.c
+++ b/x86/svm_tests.c
@@ -1917,6 +1917,40 @@
  * v2 tests
  */
 
+/*
+ * Ensure that kvm recalculates the L1 guest's CPUID.01H:ECX.OSXSAVE
+ * after VM-exit from an L2 guest that sets CR4.OSXSAVE to a different
+ * value than in L1.
+ */
+
+static void svm_cr4_osxsave_test_guest(struct svm_test *test)
+{
+	write_cr4(read_cr4() & ~X86_CR4_OSXSAVE);
+}
+
+static void svm_cr4_osxsave_test(void)
+{
+	if (!this_cpu_has(X86_FEATURE_XSAVE)) {
+		report_skip("XSAVE not detected");
+		return;
+	}
+
+	if (!(read_cr4() & X86_CR4_OSXSAVE)) {
+		unsigned long cr4 = read_cr4() | X86_CR4_OSXSAVE;
+
+		write_cr4(cr4);
+		vmcb->save.cr4 = cr4;
+	}
+
+	report(cpuid_osxsave(), "CPUID.01H:ECX.XSAVE set before VMRUN");
+
+	test_set_guest(svm_cr4_osxsave_test_guest);
+	report(svm_vmrun() == SVM_EXIT_VMMCALL,
+	       "svm_cr4_osxsave_test_guest finished with VMMCALL");
+
+	report(cpuid_osxsave(), "CPUID.01H:ECX.XSAVE set after VMRUN");
+}
+
 static void basic_guest_main(struct svm_test *test)
 {
 }
@@ -2301,6 +2335,7 @@
     { "reg_corruption", default_supported, reg_corruption_prepare,
       default_prepare_gif_clear, reg_corruption_test,
       reg_corruption_finished, reg_corruption_check },
+    TEST(svm_cr4_osxsave_test),
     TEST(svm_guest_state_test),
     { NULL, NULL, NULL, NULL, NULL, NULL, NULL }
 };