Re: [PATCH v2] KVM: selftests: Add a test for gPAT handling in L2

From: Yosry Ahmed

Date: Thu May 21 2026 - 00:15:12 EST


On Thu, May 21, 2026 at 02:34:48AM +0000, Yosry Ahmed wrote:
[..]
> +
> +static void l2_guest_code(void)
> +{
> + u64 expected_initial_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_initial_pat);
> + GUEST_SYNC(1);
> + GUEST_ASSERT_EQ(rdmsr(MSR_IA32_CR_PAT), expected_initial_pat);
> +
> + wrmsr(MSR_IA32_CR_PAT, L2_PAT_MODIFIED);

expected_initial_pat (or more maybe just expected_pat now) should be
updated here.

> +
> + 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);
> + }

..and we should skip over VMMCALL here.

> + }
> +
> + GUEST_DONE();
> +}

[..]

> +int main(int argc, char *argv[])
> +{
> + TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_SVM));
> + TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_NPT));
> + 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);
> +
> + NESTED_PAT_TEST("Invalid gPAT", l1_guest_code_invalid_gpat,
> + NPT_ENABLED, NO_SAVE_RESTORE, 1);
> +
> + NESTED_PAT_TEST("Nested NPT enabled", l1_guest_code,
> + NPT_DISABLED, NO_SAVE_RESTORE, 1);
> +
> + NESTED_PAT_TEST("Nested NPT enabled", l1_guest_code,
> + NPT_ENABLED, NO_SAVE_RESTORE, 1);
> +
> + NESTED_PAT_TEST("Save/Restore", l1_guest_code,
> + NPT_ENABLED, DO_SAVE_RESTORE, 1);
> +
> + NESTED_PAT_TEST("Multile VM-Entries", l1_guest_code,
> + NPT_ENABLED, NO_SAVE_RESTORE, 1);

..and I didn't notice any of the above because I am passing in
nr_iterations=1 here lol.

This diff fixes it, let me know if you want a v3:

diff --git a/tools/testing/selftests/kvm/x86/svm_nested_pat_test.c b/tools/testing/selftests/kvm/x86/svm_nested_pat_test.c
index b9301b3e3f4bd..d9d78db847d61 100644
--- a/tools/testing/selftests/kvm/x86/svm_nested_pat_test.c
+++ b/tools/testing/selftests/kvm/x86/svm_nested_pat_test.c
@@ -38,15 +38,16 @@ int nr_iterations;

static void l2_guest_code(void)
{
- u64 expected_initial_pat = npt_enabled ? L2_VMCB12_PAT : L1_PAT_VALUE;
+ 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_initial_pat);
+ GUEST_ASSERT_EQ(rdmsr(MSR_IA32_CR_PAT), expected_pat);
GUEST_SYNC(1);
- GUEST_ASSERT_EQ(rdmsr(MSR_IA32_CR_PAT), expected_initial_pat);
+ 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);
@@ -85,6 +86,7 @@ static void l1_guest_code(struct svm_test_data *svm)
} else {
GUEST_ASSERT_EQ(rdmsr(MSR_IA32_CR_PAT), L2_PAT_MODIFIED);
}
+ vmcb->save.rip += 3; /* VMMCALL */
}

GUEST_DONE();