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