From: Joerg Roedel <[email protected]> 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 <[email protected]> --- 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

