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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox