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