root/tools/testing/selftests/kvm/x86/svm_nested_pat_test.c
// 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;
}