summaryrefslogtreecommitdiff
path: root/tools/testing/selftests/kvm/x86/svm_nested_pat_test.c
blob: 92da8ff34da19031e7085513a4c8b0a010650e65 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (C) 2026, Google LLC.
 *
 * Test that KVM correctly virtualizes the PAT MSR and VMCB g_pat field
 * for nested SVM guests:
 *
 * o With nested NPT disabled:
 *     - L1 and L2 share the same PAT
 *     - The vmcb12.g_pat is ignored
 * o With nested NPT enabled:
 *     - Invalid g_pat in vmcb12 should cause VMEXIT_INVALID
 *     - L2 should see vmcb12.g_pat via RDMSR, not L1's PAT
 *     - L2's writes to PAT should be saved to vmcb12 on exit
 *     - L1's PAT should be restored after #VMEXIT from L2
 *     - State save/restore should preserve both L1's and L2's PAT values
 */
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "test_util.h"
#include "kvm_util.h"
#include "processor.h"
#include "svm_util.h"

#define L2_GUEST_STACK_SIZE 256

#define PAT_DEFAULT		0x0007040600070406ULL
#define L1_PAT_VALUE		0x0007040600070404ULL  /* Change PA0 to WT */
#define L2_VMCB12_PAT		0x0606060606060606ULL  /* All WB */
#define L2_PAT_MODIFIED		0x0606060606060604ULL  /* Change PA0 to WT */
#define INVALID_PAT_VALUE	0x0808080808080808ULL  /* 8 is reserved */

bool npt_enabled;
int nr_iterations;

static void l2_guest_code(void)
{
	u64 expected_pat = npt_enabled ? L2_VMCB12_PAT : L1_PAT_VALUE;
	int i;

	for (i = 0; i < nr_iterations; i++) {
		GUEST_ASSERT_EQ(rdmsr(MSR_IA32_CR_PAT), expected_pat);
		GUEST_SYNC(1);
		GUEST_ASSERT_EQ(rdmsr(MSR_IA32_CR_PAT), expected_pat);

		wrmsr(MSR_IA32_CR_PAT, L2_PAT_MODIFIED);
		expected_pat = L2_PAT_MODIFIED;

		GUEST_ASSERT_EQ(rdmsr(MSR_IA32_CR_PAT), L2_PAT_MODIFIED);
		GUEST_SYNC(2);
		GUEST_ASSERT_EQ(rdmsr(MSR_IA32_CR_PAT), L2_PAT_MODIFIED);

		vmmcall();
	}
}

static void l1_guest_code(struct svm_test_data *svm)
{
	unsigned long l2_guest_stack[L2_GUEST_STACK_SIZE];
	struct vmcb *vmcb = svm->vmcb;
	int i;

	wrmsr(MSR_IA32_CR_PAT, L1_PAT_VALUE);
	GUEST_ASSERT_EQ(rdmsr(MSR_IA32_CR_PAT), L1_PAT_VALUE);

	generic_svm_setup(svm, l2_guest_code, &l2_guest_stack[L2_GUEST_STACK_SIZE]);

	vmcb->save.g_pat = L2_VMCB12_PAT;
	vmcb->control.intercept &= ~(1ULL << INTERCEPT_MSR_PROT);

	for (i = 0; i < nr_iterations; i++) {
		run_guest(vmcb, svm->vmcb_gpa);

		GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_VMMCALL);

		/*
		 * If NPT is enabled by L1, L2 has a unique PAT and L1's PAT is
		 * unchanged. Otherwise, PAT is shared between L1 and L2.
		 */
		if (npt_enabled) {
			GUEST_ASSERT_EQ(vmcb->save.g_pat, L2_PAT_MODIFIED);
			GUEST_ASSERT_EQ(rdmsr(MSR_IA32_CR_PAT), L1_PAT_VALUE);
		} else {
			GUEST_ASSERT_EQ(rdmsr(MSR_IA32_CR_PAT), L2_PAT_MODIFIED);
		}
		vmcb->save.rip += 3; /* skip over VMMCALL */
	}

	GUEST_DONE();
}

static void l1_guest_code_invalid_gpat(struct svm_test_data *svm)
{
	unsigned long l2_guest_stack[L2_GUEST_STACK_SIZE];
	struct vmcb *vmcb = svm->vmcb;

	/* VMRUN should fail without running L2 */
	generic_svm_setup(svm, NULL, &l2_guest_stack[L2_GUEST_STACK_SIZE]);

	vmcb->save.g_pat = INVALID_PAT_VALUE;
	run_guest(vmcb, svm->vmcb_gpa);

	GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_ERR);
	GUEST_DONE();
}

static void run_test(void *guest_code, bool do_save_restore, int nr_iters)
{
	struct kvm_x86_state *state;
	struct kvm_vcpu *vcpu;
	struct kvm_vm *vm;
	struct ucall uc;
	gva_t svm_gva;

	vm = vm_create_with_one_vcpu(&vcpu, guest_code);
	vm_enable_cap(vm, KVM_CAP_DISABLE_QUIRKS2,
		      KVM_X86_QUIRK_NESTED_SVM_SHARED_PAT);

	if (npt_enabled)
		vm_enable_npt(vm);

	vcpu_alloc_svm(vm, &svm_gva);

	if (npt_enabled)
		tdp_identity_map_default_memslots(vm);

	vcpu_args_set(vcpu, 1, svm_gva);

	nr_iterations = nr_iters;
	sync_global_to_guest(vm, npt_enabled);
	sync_global_to_guest(vm, nr_iterations);

	for (;;) {
		vcpu_run(vcpu);
		TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);

		switch (get_ucall(vcpu, &uc)) {
		case UCALL_ABORT:
			REPORT_GUEST_ASSERT(uc);
			/* NOT REACHED */
		case UCALL_SYNC:
			if (do_save_restore) {
				state = vcpu_save_state(vcpu);
				kvm_vm_release(vm);
				vcpu = vm_recreate_with_one_vcpu(vm);
				vm_enable_cap(vm, KVM_CAP_DISABLE_QUIRKS2,
					      KVM_X86_QUIRK_NESTED_SVM_SHARED_PAT);
				vcpu_load_state(vcpu, state);
				kvm_x86_state_cleanup(state);
			}
			break;
		case UCALL_DONE:
			kvm_vm_free(vm);
			return;
		default:
			TEST_FAIL("Unknown ucall %lu", uc.cmd);
		}
	}
}

#define gpat_test(test_name, guest_code, npt_setting)			\
do {									\
	npt_setting;							\
									\
	if (npt_enabled && !kvm_cpu_has(X86_FEATURE_NPT)) {		\
		pr_info("Skipping: " test_name " (no NPT support)\n");	\
		break;							\
	}								\
									\
	pr_info("Testing: " test_name "\n");				\
	run_test(guest_code, false, 1);					\
									\
	if (guest_code == l1_guest_code) {				\
		pr_info("Testing: " test_name " Save/Restore\n");	\
		run_test(guest_code, true, 1);				\
									\
		pr_info("Testing: " test_name " Multiple VMRUNs\n");	\
		run_test(guest_code, false, 10);			\
	}								\
} while (0)

int main(int argc, char *argv[])
{
	TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_SVM));
	TEST_REQUIRE(kvm_has_cap(KVM_CAP_NESTED_STATE));
	TEST_REQUIRE(kvm_check_cap(KVM_CAP_DISABLE_QUIRKS2) &
		     KVM_X86_QUIRK_NESTED_SVM_SHARED_PAT);

	gpat_test("Invalid gPAT", l1_guest_code_invalid_gpat, npt_enabled = true);
	gpat_test("Nested NPT enabled", l1_guest_code, npt_enabled = true);
	gpat_test("Nested NPT disabled", l1_guest_code, npt_enabled = false);
	return 0;
}