xref: /linux/tools/testing/selftests/kvm/x86/svm_nested_pat_test.c (revision 2bee2e6c983baa3605765621f26173ff0fa40365)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Copyright (C) 2026, Google LLC.
4  *
5  * Test that KVM correctly virtualizes the PAT MSR and VMCB g_pat field
6  * for nested SVM guests:
7  *
8  * o With nested NPT disabled:
9  *     - L1 and L2 share the same PAT
10  *     - The vmcb12.g_pat is ignored
11  * o With nested NPT enabled:
12  *     - Invalid g_pat in vmcb12 should cause VMEXIT_INVALID
13  *     - L2 should see vmcb12.g_pat via RDMSR, not L1's PAT
14  *     - L2's writes to PAT should be saved to vmcb12 on exit
15  *     - L1's PAT should be restored after #VMEXIT from L2
16  *     - State save/restore should preserve both L1's and L2's PAT values
17  */
18 #include <fcntl.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 
23 #include "test_util.h"
24 #include "kvm_util.h"
25 #include "processor.h"
26 #include "svm_util.h"
27 
28 #define PAT_DEFAULT		0x0007040600070406ULL
29 #define L1_PAT_VALUE		0x0007040600070404ULL  /* Change PA0 to WT */
30 #define L2_VMCB12_PAT		0x0606060606060606ULL  /* All WB */
31 #define L2_PAT_MODIFIED		0x0606060606060604ULL  /* Change PA0 to WT */
32 #define INVALID_PAT_VALUE	0x0808080808080808ULL  /* 8 is reserved */
33 
34 bool npt_enabled;
35 int nr_iterations;
36 
37 static void l2_guest_code(void)
38 {
39 	u64 expected_pat = npt_enabled ? L2_VMCB12_PAT : L1_PAT_VALUE;
40 	int i;
41 
42 	for (i = 0; i < nr_iterations; i++) {
43 		GUEST_ASSERT_EQ(rdmsr(MSR_IA32_CR_PAT), expected_pat);
44 		GUEST_SYNC(1);
45 		GUEST_ASSERT_EQ(rdmsr(MSR_IA32_CR_PAT), expected_pat);
46 
47 		wrmsr(MSR_IA32_CR_PAT, L2_PAT_MODIFIED);
48 		expected_pat = L2_PAT_MODIFIED;
49 
50 		GUEST_ASSERT_EQ(rdmsr(MSR_IA32_CR_PAT), L2_PAT_MODIFIED);
51 		GUEST_SYNC(2);
52 		GUEST_ASSERT_EQ(rdmsr(MSR_IA32_CR_PAT), L2_PAT_MODIFIED);
53 
54 		vmmcall();
55 	}
56 }
57 
58 static void l1_guest_code(struct svm_test_data *svm)
59 {
60 	struct vmcb *vmcb = svm->vmcb;
61 	int i;
62 
63 	wrmsr(MSR_IA32_CR_PAT, L1_PAT_VALUE);
64 	GUEST_ASSERT_EQ(rdmsr(MSR_IA32_CR_PAT), L1_PAT_VALUE);
65 
66 	generic_svm_setup(svm, l2_guest_code);
67 
68 	vmcb->save.g_pat = L2_VMCB12_PAT;
69 	vmcb->control.intercept &= ~(1ULL << INTERCEPT_MSR_PROT);
70 
71 	for (i = 0; i < nr_iterations; i++) {
72 		run_guest(vmcb, svm->vmcb_gpa);
73 
74 		GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_VMMCALL);
75 
76 		/*
77 		 * If NPT is enabled by L1, L2 has a unique PAT and L1's PAT is
78 		 * unchanged. Otherwise, PAT is shared between L1 and L2.
79 		 */
80 		if (npt_enabled) {
81 			GUEST_ASSERT_EQ(vmcb->save.g_pat, L2_PAT_MODIFIED);
82 			GUEST_ASSERT_EQ(rdmsr(MSR_IA32_CR_PAT), L1_PAT_VALUE);
83 		} else {
84 			GUEST_ASSERT_EQ(rdmsr(MSR_IA32_CR_PAT), L2_PAT_MODIFIED);
85 		}
86 		vmcb->save.rip += 3; /* skip over VMMCALL */
87 	}
88 
89 	GUEST_DONE();
90 }
91 
92 static void l1_guest_code_invalid_gpat(struct svm_test_data *svm)
93 {
94 	struct vmcb *vmcb = svm->vmcb;
95 
96 	/* VMRUN should fail without running L2 */
97 	generic_svm_setup(svm, NULL);
98 
99 	vmcb->save.g_pat = INVALID_PAT_VALUE;
100 	run_guest(vmcb, svm->vmcb_gpa);
101 
102 	GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_ERR);
103 	GUEST_DONE();
104 }
105 
106 static void run_test(void *guest_code, bool do_save_restore, int nr_iters)
107 {
108 	struct kvm_x86_state *state;
109 	struct kvm_vcpu *vcpu;
110 	struct kvm_vm *vm;
111 	struct ucall uc;
112 	gva_t svm_gva;
113 
114 	vm = vm_create_with_one_vcpu(&vcpu, guest_code);
115 	vm_enable_cap(vm, KVM_CAP_DISABLE_QUIRKS2,
116 		      KVM_X86_QUIRK_NESTED_SVM_SHARED_PAT);
117 
118 	if (npt_enabled)
119 		vm_enable_npt(vm);
120 
121 	vcpu_alloc_svm(vm, &svm_gva);
122 
123 	if (npt_enabled)
124 		tdp_identity_map_default_memslots(vm);
125 
126 	vcpu_args_set(vcpu, 1, svm_gva);
127 
128 	nr_iterations = nr_iters;
129 	sync_global_to_guest(vm, npt_enabled);
130 	sync_global_to_guest(vm, nr_iterations);
131 
132 	for (;;) {
133 		vcpu_run(vcpu);
134 		TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
135 
136 		switch (get_ucall(vcpu, &uc)) {
137 		case UCALL_ABORT:
138 			REPORT_GUEST_ASSERT(uc);
139 			/* NOT REACHED */
140 		case UCALL_SYNC:
141 			if (do_save_restore) {
142 				state = vcpu_save_state(vcpu);
143 				kvm_vm_release(vm);
144 				vcpu = vm_recreate_with_one_vcpu(vm);
145 				vm_enable_cap(vm, KVM_CAP_DISABLE_QUIRKS2,
146 					      KVM_X86_QUIRK_NESTED_SVM_SHARED_PAT);
147 				vcpu_load_state(vcpu, state);
148 				kvm_x86_state_cleanup(state);
149 			}
150 			break;
151 		case UCALL_DONE:
152 			kvm_vm_free(vm);
153 			return;
154 		default:
155 			TEST_FAIL("Unknown ucall %lu", uc.cmd);
156 		}
157 	}
158 }
159 
160 #define gpat_test(test_name, guest_code, npt_setting)			\
161 do {									\
162 	npt_setting;							\
163 									\
164 	if (npt_enabled && !kvm_cpu_has(X86_FEATURE_NPT)) {		\
165 		pr_info("Skipping: " test_name " (no NPT support)\n");	\
166 		break;							\
167 	}								\
168 									\
169 	pr_info("Testing: " test_name "\n");				\
170 	run_test(guest_code, false, 1);					\
171 									\
172 	if (guest_code == l1_guest_code) {				\
173 		pr_info("Testing: " test_name " Save/Restore\n");	\
174 		run_test(guest_code, true, 1);				\
175 									\
176 		pr_info("Testing: " test_name " Multiple VMRUNs\n");	\
177 		run_test(guest_code, false, 10);			\
178 	}								\
179 } while (0)
180 
181 int main(int argc, char *argv[])
182 {
183 	TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_SVM));
184 	TEST_REQUIRE(kvm_has_cap(KVM_CAP_NESTED_STATE));
185 	TEST_REQUIRE(kvm_check_cap(KVM_CAP_DISABLE_QUIRKS2) &
186 		     KVM_X86_QUIRK_NESTED_SVM_SHARED_PAT);
187 
188 	gpat_test("Invalid gPAT", l1_guest_code_invalid_gpat, npt_enabled = true);
189 	gpat_test("Nested NPT enabled", l1_guest_code, npt_enabled = true);
190 	gpat_test("Nested NPT disabled", l1_guest_code, npt_enabled = false);
191 	return 0;
192 }
193