From: "Jörg Rödel" <joro@8bytes.org>
To: Paolo Bonzini <pbonzini@redhat.com>,
Sean Christopherson <seanjc@google.com>
Cc: Michael Roth <michael.roth@amd.com>,
Liam Merwick <liam.merwick@oracle.com>,
Vishal Annapurve <vannapurve@google.com>,
Ninad Naik <ninadnaik07@gmail.com>,
Joerg Roedel <joerg.roedel@amd.com>,
Tom Lendacky <thomas.lendacky@amd.com>,
James Bottomley <James.Bottomley@HansenPartnership.com>,
kvm@vger.kernel.org, linux-doc@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org,
coconut-svsm@lists.linux.dev
Subject: [PATCH v2 7/8] KVM: selftests: Test the SNP APIC-ID-list GHCB request
Date: Tue, 8 Sep 2026 12:33:37 +0200 [thread overview]
Message-ID: <20260908103338.427254-8-joro@8bytes.org> (raw)
In-Reply-To: <20260908103338.427254-1-joro@8bytes.org>
From: Joerg Roedel <joerg.roedel@amd.com>
Exercise the GET_AP_APIC_IDS NAE event from an SNP guest using a test-local
GHCB encoder. Verify the exact count and APIC IDs, missing RAX validity,
zero-sized requests, invalid GPAs, and the 1024-vCPU descriptor boundary
when supported.
Assisted-by: LLM
Signed-off-by: Joerg Roedel <joerg.roedel@amd.com>
---
tools/testing/selftests/kvm/Makefile.kvm | 1 +
.../selftests/kvm/x86/sev_snp_apic_id_test.c | 180 ++++++++++++++++++
2 files changed, 181 insertions(+)
create mode 100644 tools/testing/selftests/kvm/x86/sev_snp_apic_id_test.c
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 96bab7002d39..92eb3dff13ff 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -149,6 +149,7 @@ TEST_GEN_PROGS_x86 += x86/xen_vmcall_test
TEST_GEN_PROGS_x86 += x86/sev_dbg_test
TEST_GEN_PROGS_x86 += x86/sev_init2_tests
TEST_GEN_PROGS_x86 += x86/sev_migrate_tests
+TEST_GEN_PROGS_x86 += x86/sev_snp_apic_id_test
TEST_GEN_PROGS_x86 += x86/sev_smoke_test
TEST_GEN_PROGS_x86 += x86/amx_test
TEST_GEN_PROGS_x86 += x86/max_vcpuid_cap_test
diff --git a/tools/testing/selftests/kvm/x86/sev_snp_apic_id_test.c b/tools/testing/selftests/kvm/x86/sev_snp_apic_id_test.c
new file mode 100644
index 000000000000..8ad9c15091c2
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86/sev_snp_apic_id_test.c
@@ -0,0 +1,180 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <stddef.h>
+#include <stdint.h>
+
+#include "kvm_util.h"
+#include "processor.h"
+#include "sev.h"
+#include "svm_util.h"
+
+#define GHCB_SAVE_RAX_OFFSET 0x1f8
+#define GHCB_SAVE_SW_EXIT_CODE_OFFSET 0x390
+#define GHCB_SAVE_SW_EXIT_INFO_1_OFFSET 0x398
+#define GHCB_SAVE_SW_EXIT_INFO_2_OFFSET 0x3a0
+#define GHCB_SAVE_VALID_BITMAP_OFFSET 0x3f0
+
+#define GHCB_MSR_REG_GPA_REQ 0x012
+#define GHCB_MSR_REG_GPA_RESP 0x013
+#define GHCB_MSR_INFO_MASK GENMASK_ULL(11, 0)
+
+#define GHCB_HV_RESP_MALFORMED_INPUT 2
+#define GHCB_ERR_MISSING_INPUT 4
+#define GHCB_ERR_INVALID_INPUT 5
+
+struct apic_id_desc {
+ u32 nr_entries;
+ u32 apic_ids[];
+};
+
+struct apic_id_results {
+ u64 missing_info1;
+ u64 missing_info2;
+ u64 zero_info1;
+ u64 zero_info2;
+ u64 zero_rax;
+ u64 invalid_info1;
+ u64 invalid_info2;
+ u64 valid_info1;
+ u64 valid_info2;
+ u32 nr_entries;
+ u32 first_apic_id;
+ u32 last_apic_id;
+};
+
+static void ghcb_set_field(void *ghcb, size_t offset, u64 value, bool valid)
+{
+ u8 *valid_bitmap = ghcb + GHCB_SAVE_VALID_BITMAP_OFFSET;
+
+ *(u64 *)(ghcb + offset) = value;
+ if (valid)
+ valid_bitmap[(offset / sizeof(u64)) / 8] |=
+ BIT((offset / sizeof(u64)) % 8);
+}
+
+static u64 ghcb_get_field(void *ghcb, size_t offset)
+{
+ return *(u64 *)(ghcb + offset);
+}
+
+static void do_get_apic_ids(void *ghcb, gpa_t buffer_gpa, u64 pages,
+ bool rax_valid)
+{
+ memset(ghcb, 0, PAGE_SIZE);
+ ghcb_set_field(ghcb, GHCB_SAVE_SW_EXIT_CODE_OFFSET,
+ SVM_VMGEXIT_GET_APIC_IDS, true);
+ ghcb_set_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_1_OFFSET, buffer_gpa, true);
+ ghcb_set_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_2_OFFSET, 0, true);
+ ghcb_set_field(ghcb, GHCB_SAVE_RAX_OFFSET, pages, rax_valid);
+ vmgexit();
+}
+
+static void guest_code(void *ghcb, gpa_t ghcb_gpa, void *list,
+ gpa_t list_gpa, struct apic_id_results *results,
+ u64 expected_vcpus)
+{
+ struct apic_id_desc *desc = list;
+ u64 msr;
+
+ wrmsr(MSR_AMD64_SEV_ES_GHCB,
+ (ghcb_gpa >> PAGE_SHIFT) << PAGE_SHIFT | GHCB_MSR_REG_GPA_REQ);
+ vmgexit();
+ msr = rdmsr(MSR_AMD64_SEV_ES_GHCB);
+ if ((msr & GHCB_MSR_INFO_MASK) != GHCB_MSR_REG_GPA_RESP)
+ goto terminate;
+
+ wrmsr(MSR_AMD64_SEV_ES_GHCB, ghcb_gpa);
+
+ do_get_apic_ids(ghcb, list_gpa, 1, false);
+ results->missing_info1 = ghcb_get_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_1_OFFSET);
+ results->missing_info2 = ghcb_get_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_2_OFFSET);
+
+ do_get_apic_ids(ghcb, list_gpa, 0, true);
+ results->zero_info1 = ghcb_get_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_1_OFFSET);
+ results->zero_info2 = ghcb_get_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_2_OFFSET);
+ results->zero_rax = ghcb_get_field(ghcb, GHCB_SAVE_RAX_OFFSET);
+
+ do_get_apic_ids(ghcb, BIT_ULL(52), 2, true);
+ results->invalid_info1 = ghcb_get_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_1_OFFSET);
+ results->invalid_info2 = ghcb_get_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_2_OFFSET);
+
+ do_get_apic_ids(ghcb, list_gpa, 2, true);
+ results->valid_info1 = ghcb_get_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_1_OFFSET);
+ results->valid_info2 = ghcb_get_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_2_OFFSET);
+ results->nr_entries = desc->nr_entries;
+ results->first_apic_id = desc->apic_ids[0];
+ results->last_apic_id = desc->apic_ids[expected_vcpus - 1];
+
+terminate:
+ wrmsr(MSR_AMD64_SEV_ES_GHCB, GHCB_MSR_TERM_REQ);
+ vmgexit();
+}
+
+static void run_apic_id_test(unsigned int nr_vcpus)
+{
+ struct apic_id_results *results;
+ struct kvm_vcpu *vcpu;
+ struct kvm_vm *vm;
+ gva_t ghcb_gva, list_gva, results_gva;
+ gpa_t ghcb_gpa, list_gpa;
+ unsigned int i;
+
+ kvm_set_files_rlimit(nr_vcpus);
+ vm = vm_sev_create_with_one_vcpu(KVM_X86_SNP_VM, guest_code, &vcpu);
+ for (i = 1; i < nr_vcpus; i++)
+ __vm_vcpu_add(vm, i);
+
+ ghcb_gva = vm_alloc_shared(vm, PAGE_SIZE, KVM_UTIL_MIN_VADDR,
+ MEM_REGION_TEST_DATA);
+ list_gva = vm_alloc_shared(vm, 2 * PAGE_SIZE, KVM_UTIL_MIN_VADDR,
+ MEM_REGION_TEST_DATA);
+ results_gva = vm_alloc_shared(vm, PAGE_SIZE, KVM_UTIL_MIN_VADDR,
+ MEM_REGION_TEST_DATA);
+ ghcb_gpa = addr_gva2gpa(vm, ghcb_gva);
+ list_gpa = addr_gva2gpa(vm, list_gva);
+ results = addr_gva2hva(vm, results_gva);
+
+ vcpu_args_set(vcpu, 6, ghcb_gva, ghcb_gpa, list_gva, list_gpa,
+ results_gva, nr_vcpus);
+ memset(addr_gva2hva(vm, ghcb_gva), 0, PAGE_SIZE);
+ memset(addr_gva2hva(vm, list_gva), 0, 2 * PAGE_SIZE);
+ memset(results, 0, PAGE_SIZE);
+ vm_sev_launch(vm, snp_default_policy(), NULL);
+
+ vcpu_run(vcpu);
+ TEST_ASSERT_EQ(vcpu->run->exit_reason, KVM_EXIT_SYSTEM_EVENT);
+ TEST_ASSERT_EQ(vcpu->run->system_event.type, KVM_SYSTEM_EVENT_SEV_TERM);
+
+ TEST_ASSERT_EQ(results->missing_info1, GHCB_HV_RESP_MALFORMED_INPUT);
+ TEST_ASSERT_EQ(results->missing_info2, GHCB_ERR_MISSING_INPUT);
+ TEST_ASSERT_EQ(results->zero_info1, 0);
+ TEST_ASSERT_EQ(results->zero_info2, 0);
+ TEST_ASSERT_EQ(results->zero_rax,
+ DIV_ROUND_UP(sizeof(struct apic_id_desc) + nr_vcpus * sizeof(u32),
+ PAGE_SIZE));
+ TEST_ASSERT_EQ(results->invalid_info1, GHCB_HV_RESP_MALFORMED_INPUT);
+ TEST_ASSERT_EQ(results->invalid_info2, GHCB_ERR_INVALID_INPUT);
+ TEST_ASSERT_EQ(results->valid_info1, 0);
+ TEST_ASSERT_EQ(results->valid_info2, 0);
+ TEST_ASSERT_EQ(results->nr_entries, nr_vcpus);
+ TEST_ASSERT_EQ(results->first_apic_id, 0);
+ TEST_ASSERT_EQ(results->last_apic_id, nr_vcpus - 1);
+
+ kvm_vm_free(vm);
+}
+
+int main(int argc, char *argv[])
+{
+ unsigned int max_vcpus;
+
+ TEST_REQUIRE(kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(KVM_X86_SNP_VM));
+ run_apic_id_test(2);
+
+ /* 1024 IDs cross the one-page descriptor boundary. */
+ max_vcpus = kvm_check_cap(KVM_CAP_MAX_VCPUS);
+ if (max_vcpus >= 1024)
+ run_apic_id_test(1024);
+ else
+ pr_info("Skipping vCPU-count boundary test (max vCPUs: %u)\n", max_vcpus);
+
+ return 0;
+}
--
2.53.0
next prev parent reply other threads:[~2026-09-08 10:33 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-08 10:33 [PATCH v2 0/8] KVM: SVM: Support direct setting of VMSA for SEV-SNP guests Jörg Rödel
2026-09-08 10:33 ` [PATCH v2 1/8] KVM: SEV: Document SNP direct VMSA userspace ABI Jörg Rödel
2026-09-08 10:33 ` [PATCH v2 2/8] KVM: SVM: Implement GET_AP_APIC_IDS NAE event Jörg Rödel
2026-09-08 10:51 ` sashiko-bot
2026-09-08 10:33 ` [PATCH v2 3/8] KVM: SVM: Hold SRCU while reloading guest-owned VMSAs Jörg Rödel
2026-09-08 10:33 ` [PATCH v2 4/8] KVM: SEV: Add direct VMSA capability Jörg Rödel
2026-09-08 10:53 ` sashiko-bot
2026-09-08 10:33 ` [PATCH v2 5/8] KVM: SEV: Allow VMSA pages in SNP launch updates Jörg Rödel
2026-09-08 10:33 ` [PATCH v2 6/8] KVM: SEV: Add SNP vCPU state get and set commands Jörg Rödel
2026-09-08 10:49 ` sashiko-bot
2026-09-08 10:33 ` Jörg Rödel [this message]
2026-09-08 10:33 ` [PATCH v2 8/8] KVM: selftests: Test SNP vCPU state and direct VMSA launch Jörg Rödel
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260908103338.427254-8-joro@8bytes.org \
--to=joro@8bytes.org \
--cc=James.Bottomley@HansenPartnership.com \
--cc=coconut-svsm@lists.linux.dev \
--cc=joerg.roedel@amd.com \
--cc=kvm@vger.kernel.org \
--cc=liam.merwick@oracle.com \
--cc=linux-doc@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=michael.roth@amd.com \
--cc=ninadnaik07@gmail.com \
--cc=pbonzini@redhat.com \
--cc=seanjc@google.com \
--cc=thomas.lendacky@amd.com \
--cc=vannapurve@google.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.