xref: /linux/tools/testing/selftests/kvm/x86/svm_nested_pat_test.c (revision b2128290c29902315e632ea59e0504d6bc9e9b42)
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 L2_GUEST_STACK_SIZE 256
29 
30 #define PAT_DEFAULT		0x0007040600070406ULL
31 #define L1_PAT_VALUE		0x0007040600070404ULL  /* Change PA0 to WT */
32 #define L2_VMCB12_PAT		0x0606060606060606ULL  /* All WB */
33 #define L2_PAT_MODIFIED		0x0606060606060604ULL  /* Change PA0 to WT */
34 #define INVALID_PAT_VALUE	0x0808080808080808ULL  /* 8 is reserved */
35 
36 bool npt_enabled;
37 int nr_iterations;
38 
39 static void l2_guest_code(void)
40 {
41 	u64 expected_pat = npt_enabled ? L2_VMCB12_PAT : L1_PAT_VALUE;
42 	int i;
43 
44 	for (i = 0; i < nr_iterations; i++) {
45 		GUEST_ASSERT_EQ(rdmsr(MSR_IA32_CR_PAT), expected_pat);
46 		GUEST_SYNC(1);
47 		GUEST_ASSERT_EQ(rdmsr(MSR_IA32_CR_PAT), expected_pat);
48 
49 		wrmsr(MSR_IA32_CR_PAT, L2_PAT_MODIFIED);
50 		expected_pat = L2_PAT_MODIFIED;
51 
52 		GUEST_ASSERT_EQ(rdmsr(MSR_IA32_CR_PAT), L2_PAT_MODIFIED);
53 		GUEST_SYNC(2);
54 		GUEST_ASSERT_EQ(rdmsr(MSR_IA32_CR_PAT), L2_PAT_MODIFIED);
55 
56 		vmmcall();
57 	}
58 }
59 
60 static void l1_guest_code(struct svm_test_data *svm)
61 {
62 	unsigned long l2_guest_stack[L2_GUEST_STACK_SIZE];
63 	struct vmcb *vmcb = svm->vmcb;
64 	int i;
65 
66 	wrmsr(MSR_IA32_CR_PAT, L1_PAT_VALUE);
67 	GUEST_ASSERT_EQ(rdmsr(MSR_IA32_CR_PAT), L1_PAT_VALUE);
68 
69 	generic_svm_setup(svm, l2_guest_code, &l2_guest_stack[L2_GUEST_STACK_SIZE]);
70 
71 	vmcb->save.g_pat = L2_VMCB12_PAT;
72 	vmcb->control.intercept &= ~(1ULL << INTERCEPT_MSR_PROT);
73 
74 	for (i = 0; i < nr_iterations; i++) {
75 		run_guest(vmcb, svm->vmcb_gpa);
76 
77 		GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_VMMCALL);
78 
79 		/*
80 		 * If NPT is enabled by L1, L2 has a unique PAT and L1's PAT is
81 		 * unchanged. Otherwise, PAT is shared between L1 and L2.
82 		 */
83 		if (npt_enabled) {
84 			GUEST_ASSERT_EQ(vmcb->save.g_pat, L2_PAT_MODIFIED);
85 			GUEST_ASSERT_EQ(rdmsr(MSR_IA32_CR_PAT), L1_PAT_VALUE);
86 		} else {
87 			GUEST_ASSERT_EQ(rdmsr(MSR_IA32_CR_PAT), L2_PAT_MODIFIED);
88 		}
89 		vmcb->save.rip += 3; /* skip over VMMCALL */
90 	}
91 
92 	GUEST_DONE();
93 }
94 
95 static void l1_guest_code_invalid_gpat(struct svm_test_data *svm)
96 {
97 	unsigned long l2_guest_stack[L2_GUEST_STACK_SIZE];
98 	struct vmcb *vmcb = svm->vmcb;
99 
100 	/* VMRUN should fail without running L2 */
101 	generic_svm_setup(svm, NULL, &l2_guest_stack[L2_GUEST_STACK_SIZE]);
102 
103 	vmcb->save.g_pat = INVALID_PAT_VALUE;
104 	run_guest(vmcb, svm->vmcb_gpa);
105 
106 	GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_ERR);
107 	GUEST_DONE();
108 }
109 
110 static void run_test(void *guest_code, bool do_save_restore, int nr_iters)
111 {
112 	struct kvm_x86_state *state;
113 	struct kvm_vcpu *vcpu;
114 	struct kvm_vm *vm;
115 	struct ucall uc;
116 	gva_t svm_gva;
117 
118 	vm = vm_create_with_one_vcpu(&vcpu, guest_code);
119 	vm_enable_cap(vm, KVM_CAP_DISABLE_QUIRKS2,
120 		      KVM_X86_QUIRK_NESTED_SVM_SHARED_PAT);
121 
122 	if (npt_enabled)
123 		vm_enable_npt(vm);
124 
125 	vcpu_alloc_svm(vm, &svm_gva);
126 
127 	if (npt_enabled)
128 		tdp_identity_map_default_memslots(vm);
129 
130 	vcpu_args_set(vcpu, 1, svm_gva);
131 
132 	nr_iterations = nr_iters;
133 	sync_global_to_guest(vm, npt_enabled);
134 	sync_global_to_guest(vm, nr_iterations);
135 
136 	for (;;) {
137 		vcpu_run(vcpu);
138 		TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
139 
140 		switch (get_ucall(vcpu, &uc)) {
141 		case UCALL_ABORT:
142 			REPORT_GUEST_ASSERT(uc);
143 			/* NOT REACHED */
144 		case UCALL_SYNC:
145 			if (do_save_restore) {
146 				state = vcpu_save_state(vcpu);
147 				kvm_vm_release(vm);
148 				vcpu = vm_recreate_with_one_vcpu(vm);
149 				vm_enable_cap(vm, KVM_CAP_DISABLE_QUIRKS2,
150 					      KVM_X86_QUIRK_NESTED_SVM_SHARED_PAT);
151 				vcpu_load_state(vcpu, state);
152 				kvm_x86_state_cleanup(state);
153 			}
154 			break;
155 		case UCALL_DONE:
156 			kvm_vm_free(vm);
157 			return;
158 		default:
159 			TEST_FAIL("Unknown ucall %lu", uc.cmd);
160 		}
161 	}
162 }
163 
164 #define gpat_test(test_name, guest_code, npt_setting)			\
165 do {									\
166 	npt_setting;							\
167 									\
168 	if (npt_enabled && !kvm_cpu_has(X86_FEATURE_NPT)) {		\
169 		pr_info("Skipping: " test_name " (no NPT support)\n");	\
170 		break;							\
171 	}								\
172 									\
173 	pr_info("Testing: " test_name "\n");				\
174 	run_test(guest_code, false, 1);					\
175 									\
176 	if (guest_code == l1_guest_code) {				\
177 		pr_info("Testing: " test_name " Save/Restore\n");	\
178 		run_test(guest_code, true, 1);				\
179 									\
180 		pr_info("Testing: " test_name " Multiple VMRUNs\n");	\
181 		run_test(guest_code, false, 10);			\
182 	}								\
183 } while (0)
184 
185 int main(int argc, char *argv[])
186 {
187 	TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_SVM));
188 	TEST_REQUIRE(kvm_has_cap(KVM_CAP_NESTED_STATE));
189 	TEST_REQUIRE(kvm_check_cap(KVM_CAP_DISABLE_QUIRKS2) &
190 		     KVM_X86_QUIRK_NESTED_SVM_SHARED_PAT);
191 
192 	gpat_test("Invalid gPAT", l1_guest_code_invalid_gpat, npt_enabled = true);
193 	gpat_test("Nested NPT enabled", l1_guest_code, npt_enabled = true);
194 	gpat_test("Nested NPT disabled", l1_guest_code, npt_enabled = false);
195 	return 0;
196 }
197