[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20250228093024.114983-26-Neeraj.Upadhyay@amd.com>
Date: Fri, 28 Feb 2025 15:00:18 +0530
From: Neeraj Upadhyay <Neeraj.Upadhyay@....com>
To: <kvm@...r.kernel.org>, <seanjc@...gle.com>, <pbonzini@...hat.com>
CC: <linux-kernel@...r.kernel.org>, <Thomas.Lendacky@....com>,
<nikunj@....com>, <Santosh.Shukla@....com>, <Vasant.Hegde@....com>,
<Suravee.Suthikulpanit@....com>, <bp@...en8.de>, <David.Kaplan@....com>,
<huibo.wang@....com>, <naveen.rao@....com>, <pgonda@...gle.com>,
<linux-kselftest@...r.kernel.org>, <shuah@...nel.org>
Subject: [RFC PATCH 25/31] KVM: selftests: Add Secure AVIC APIC regs test
Add a APIC regs to verify APIC register updates at various
stages in Secure AVIC guest's APIC enablement. In summary,
do below validations:
- Verify that the initial reset of APIC is xapic.
- Values written by SAVIC guest in xapic mode are propagated
to host APIC state.
- APIC regs state updates by host are propagated to guest
in x2apic mode.
- In x2apic mode, APIC regs updates by SAVIC guest are propagated
to host APIC state.
- Post SAVIC enablement, init guest APIC backing page state is same
as the host APIC regs state for unaccelerated registers.
- Post SAVIC enablement, APIC updates done by guest are propagated
to host for unaccelerated APIC registers. For accelerated APIC
registers, updates are not propagated to host.
Signed-off-by: Neeraj Upadhyay <Neeraj.Upadhyay@....com>
---
tools/testing/selftests/kvm/Makefile.kvm | 1 +
.../testing/selftests/kvm/include/x86/savic.h | 1 +
tools/testing/selftests/kvm/lib/x86/savic.c | 13 +-
tools/testing/selftests/kvm/x86/savic_test.c | 462 ++++++++++++++++++
4 files changed, 476 insertions(+), 1 deletion(-)
create mode 100644 tools/testing/selftests/kvm/x86/savic_test.c
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 50bd78e03d9f..51338d1901e0 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -77,6 +77,7 @@ TEST_GEN_PROGS_x86 += x86/pmu_counters_test
TEST_GEN_PROGS_x86 += x86/pmu_event_filter_test
TEST_GEN_PROGS_x86 += x86/private_mem_conversions_test
TEST_GEN_PROGS_x86 += x86/private_mem_kvm_exits_test
+TEST_GEN_PROGS_x86 += x86/savic_test
TEST_GEN_PROGS_x86 += x86/set_boot_cpu_id
TEST_GEN_PROGS_x86 += x86/set_sregs_test
TEST_GEN_PROGS_x86 += x86/smaller_maxphyaddr_emulation_test
diff --git a/tools/testing/selftests/kvm/include/x86/savic.h b/tools/testing/selftests/kvm/include/x86/savic.h
index 238d7450ab6e..cb432eb527b3 100644
--- a/tools/testing/selftests/kvm/include/x86/savic.h
+++ b/tools/testing/selftests/kvm/include/x86/savic.h
@@ -17,4 +17,5 @@ uint64_t savic_hv_read_reg(uint32_t reg);
void savic_enable(void);
int savic_nr_pages_required(uint64_t page_size);
void savic_vc_handler(struct ex_regs *regs);
+struct guest_apic_page *get_guest_apic_page(void);
#endif
diff --git a/tools/testing/selftests/kvm/lib/x86/savic.c b/tools/testing/selftests/kvm/lib/x86/savic.c
index ae48978479bf..d4c9fcf835ad 100644
--- a/tools/testing/selftests/kvm/lib/x86/savic.c
+++ b/tools/testing/selftests/kvm/lib/x86/savic.c
@@ -105,6 +105,11 @@ void set_savic_control_msr(struct guest_apic_page *apic_page, bool enable, bool
wrmsr(MSR_AMD64_SECURE_AVIC_CONTROL, val);
}
+struct guest_apic_page *get_guest_apic_page(void)
+{
+ return &apic_page_pool->guest_apic_page[x2apic_read_reg(APIC_ID)];
+}
+
/*
* Write APIC reg offset in the guest APIC backing page.
*
@@ -176,11 +181,17 @@ static void savic_init_backing_page(struct guest_apic_page *apic_page, uint32_t
regval = savic_hv_read_reg(APIC_LDR);
savic_write_reg(apic_page, APIC_LDR, regval);
- for (i = LVT_THERMAL_MONITOR; i < APIC_MAX_NR_LVT_ENTRIES; i++) {
+ for (i = LVT_TIMER; i < APIC_MAX_NR_LVT_ENTRIES; i++) {
regval = savic_hv_read_reg(APIC_LVTx(i));
savic_write_reg(apic_page, APIC_LVTx(i), regval);
}
+ regval = savic_hv_read_reg(APIC_TMICT);
+ savic_write_reg(apic_page, APIC_TMICT, regval);
+
+ regval = savic_hv_read_reg(APIC_TDCR);
+ savic_write_reg(apic_page, APIC_TDCR, regval);
+
regval = savic_hv_read_reg(APIC_LVT0);
savic_write_reg(apic_page, APIC_LVT0, regval);
diff --git a/tools/testing/selftests/kvm/x86/savic_test.c b/tools/testing/selftests/kvm/x86/savic_test.c
new file mode 100644
index 000000000000..ca1d7352bc3e
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86/savic_test.c
@@ -0,0 +1,462 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+/*
+ * Copyright (C) 2024 Advanced Micro Devices, Inc.
+ *
+ */
+#include <pthread.h>
+
+#include "processor.h"
+#include "apic.h"
+#include "kvm_util.h"
+#include "sev.h"
+#include "test_util.h"
+#include "savic.h"
+
+#define NR_SAVIC_VCPUS 1
+
+static struct kvm_vcpu *vcpus[NR_SAVIC_VCPUS];
+static pthread_t threads[NR_SAVIC_VCPUS];
+
+#define SAVIC_TEST_STATE(STATE) \
+ STATE ## _START, \
+ STATE ## _END
+
+enum savic_test_state {
+ /* Reset state of APIC regs */
+ SAVIC_TEST_STATE(RESET),
+ /* APIC regs state on X2apic enablement */
+ SAVIC_TEST_STATE(X2APIC_ENABLE),
+ /* APIC regs state on Secure AVIC enablement */
+ SAVIC_TEST_STATE(SAVIC_EN),
+};
+
+/* APIC reg values written by host. */
+enum savic_test_host_vals {
+ H_APIC_TASKPRI_VAL = 0x30,
+ H_APIC_LVTT_VAL = APIC_DM_FIXED | 0x30,
+ H_APIC_LVTTHMR_VAL = APIC_DM_FIXED | 0x31,
+ H_APIC_LVTPC_VAL = APIC_DM_FIXED | 0x32,
+ H_APIC_LVT0_VAL = APIC_DM_FIXED | 0x33,
+ H_APIC_LVT1_VAL = APIC_DM_FIXED | 0x34,
+ H_APIC_TMICT_VAL = 0x555555,
+ H_APIC_TDCR_VAL = 0x3,
+};
+
+/* APIC reg values written by guest. */
+enum savic_test_guest_vals {
+ G_APIC_TASKPRI_VAL = 0x40,
+ G_APIC_LVTT_VAL = APIC_DM_FIXED | 0x40,
+ G_APIC_LVTTHMR_VAL = APIC_DM_FIXED | 0x41,
+ G_APIC_LVTPC_VAL = APIC_DM_FIXED | 0x42,
+ G_APIC_LVT0_VAL = APIC_DM_FIXED | 0x43,
+ G_APIC_LVT1_VAL = APIC_DM_FIXED | 0x34,
+ G_APIC_TMICT_VAL = 0xaaaaaa,
+ G_APIC_TDCR_VAL = 0x1,
+};
+
+struct reg_ent {
+ char *regname;
+ uint32_t reg;
+ uint64_t val;
+};
+
+#define CREATE_H_REG_ENTRY(_reg) {\
+ .regname = #_reg, \
+ .reg = _reg, \
+ .val = H_ ## _reg ## _VAL \
+}
+
+#define CREATE_G_REG_ENTRY(_reg) {\
+ .regname = #_reg, \
+ .reg = _reg, \
+ .val = G_ ## _reg ## _VAL \
+}
+
+static struct reg_ent host_apic_regs[] = {
+ CREATE_H_REG_ENTRY(APIC_TASKPRI),
+ CREATE_H_REG_ENTRY(APIC_LVTT),
+ CREATE_H_REG_ENTRY(APIC_LVTTHMR),
+ CREATE_H_REG_ENTRY(APIC_LVTPC),
+ CREATE_H_REG_ENTRY(APIC_LVT0),
+ CREATE_H_REG_ENTRY(APIC_LVT1),
+ CREATE_H_REG_ENTRY(APIC_TMICT),
+ CREATE_H_REG_ENTRY(APIC_TDCR),
+};
+
+static struct reg_ent guest_apic_regs[] = {
+ CREATE_G_REG_ENTRY(APIC_TASKPRI),
+ CREATE_G_REG_ENTRY(APIC_LVTT),
+ CREATE_G_REG_ENTRY(APIC_LVTTHMR),
+ CREATE_G_REG_ENTRY(APIC_LVTPC),
+ CREATE_G_REG_ENTRY(APIC_LVT0),
+ CREATE_G_REG_ENTRY(APIC_LVT1),
+ CREATE_G_REG_ENTRY(APIC_TMICT),
+ CREATE_G_REG_ENTRY(APIC_TDCR),
+};
+
+/* Context which is doing testing of APIC regs values. */
+enum context {
+ CTXT_HOST,
+ CTXT_GUEST,
+};
+
+static struct kvm_lapic_state apic_state[NR_SAVIC_VCPUS];
+
+/*
+ * Write APIC reg from host or guest context.
+ *
+ * @id : vCPU id (required only for host context).
+ * @reg : APIC reg offset.
+ * @ctxt : Context which is doing write operation.
+ * @val : New value to write to the reg.
+ * x2apic : x2apic is enabled.
+ */
+static void write_apic_reg(int id, uint32_t reg, enum context ctxt,
+ uint32_t val, bool x2apic)
+{
+ if (ctxt == CTXT_HOST) {
+ *(uint32_t *)&apic_state[id].regs[reg] = val;
+ } else {
+ if (x2apic)
+ x2apic_write_reg(reg, val);
+ else
+ xapic_write_reg(reg, val);
+ }
+}
+
+/*
+ * Read APIC reg from host or guest context.
+ *
+ * @id : vCPU id (required only for host context).
+ * @reg : APIC reg offset.
+ * @ctxt : Context which is doing read operation.
+ *
+ *
+ * Returns APIC register value.
+ */
+static uint32_t read_apic_reg(int id, uint32_t reg, enum context ctxt, bool x2apic)
+{
+ if (ctxt == CTXT_HOST)
+ return *(uint32_t *)&apic_state[id].regs[reg];
+
+ if (x2apic)
+ return x2apic_read_reg(reg);
+ else
+ return xapic_read_reg(reg);
+}
+
+/*
+ * Set test APIC regs values from host/guest context.
+ *
+ * @id : vCPU ID for which APIC state is set.
+ * @host : Test APIC regs data source values.
+ * @ctxt : Context from which this function is called.
+ * @x2apic : X2apic is enabled.
+ */
+static void set_apic_vals(int id, bool host, enum context ctxt, bool x2apic)
+{
+ struct reg_ent *reg_entries;
+ struct reg_ent ent;
+ int i;
+
+ if (host)
+ reg_entries = host_apic_regs;
+ else
+ reg_entries = guest_apic_regs;
+
+ if (ctxt == CTXT_HOST)
+ memset(&apic_state[id], 0, sizeof(apic_state[i]));
+
+ for (i = 0; i < ARRAY_SIZE(host_apic_regs); i++) {
+ ent = reg_entries[i];
+ write_apic_reg(id, ent.reg, ctxt, ent.val, x2apic);
+ }
+
+ if (ctxt == CTXT_HOST)
+ vcpu_ioctl(vcpus[id], KVM_SET_LAPIC, &apic_state[id]);
+}
+
+#define CTXT_ASSERT(ctxt, reg, exp, val) \
+ do { \
+ if (ctxt == CTXT_HOST) \
+ TEST_ASSERT(exp == val, \
+ "Unexpected %s %s val: 0x%x expected 0x%x\n", \
+ "host", reg, val, exp); \
+ else \
+ __GUEST_ASSERT(exp == val, \
+ "Unexpected %s %s val: 0x%x expected 0x%x\n", \
+ "guest", reg, val, exp); \
+ } while (0)
+
+/*
+ * Test APIC regs values from host/guest context.
+ *
+ * @id : vCPU ID for which values are tested.
+ * @host : Test APIC regs expected values data source.
+ * @ctxt : Context from which this function is called.
+ * @x2apic : X2apic is enabled.
+ * @savic : Secure AVIC is enabled.
+ */
+static void test_apic_vals(int id, bool host, enum context ctxt, bool x2apic,
+ bool savic)
+{
+ uint32_t exp_regval, regval;
+ struct reg_ent *reg_entries;
+ struct reg_ent ent;
+ char *regname;
+ uint32_t reg;
+ int i;
+
+ if (host)
+ reg_entries = host_apic_regs;
+ else
+ reg_entries = guest_apic_regs;
+
+
+ if (ctxt == CTXT_HOST)
+ vcpu_ioctl(vcpus[id], KVM_GET_LAPIC, &apic_state[id]);
+
+ for (i = 0; i < ARRAY_SIZE(host_apic_regs); i++) {
+ ent = reg_entries[i];
+ reg = ent.reg;
+ /* APIC_TASKPRI is accelerated in guest and not synced in host APIC state. */
+ if (savic && reg == APIC_TASKPRI) {
+ if (ctxt == CTXT_GUEST)
+ continue;
+ else
+ ent = host_apic_regs[i];
+ }
+ regname = ent.regname;
+ exp_regval = ent.val;
+ regval = read_apic_reg(id, reg, ctxt, x2apic);
+ CTXT_ASSERT(ctxt, regname, exp_regval, regval);
+ }
+}
+
+#define SAVIC_GUEST_SYNC(sync, func) ({\
+ GUEST_SYNC(sync ## _START); \
+ func(id); \
+ GUEST_SYNC(sync ## _END); \
+})
+
+static void savic_write_apic_regs(struct guest_apic_page *apage)
+{
+ struct reg_ent ent;
+ uint32_t regval;
+ uint32_t reg;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(guest_apic_regs); i++) {
+ ent = guest_apic_regs[i];
+ reg = ent.reg;
+ regval = ent.val;
+ savic_write_reg(apage, reg, regval);
+ if (reg != APIC_TASKPRI)
+ savic_hv_write_reg(reg, regval);
+ }
+}
+
+static void guest_test_and_set_apic_regs(int id, bool x2apic)
+{
+ /*
+ * On guest entry, APIC reg values read from guest context matches
+ * the previous values set by host.
+ */
+ test_apic_vals(id, true, CTXT_GUEST, x2apic, false);
+ set_apic_vals(id, false, CTXT_GUEST, x2apic);
+ test_apic_vals(id, false, CTXT_GUEST, x2apic, false);
+}
+
+static void guest_savic_start(int id)
+{
+ guest_test_and_set_apic_regs(id, false);
+}
+
+static void guest_x2apic_enabled(int id)
+{
+ guest_test_and_set_apic_regs(id, true);
+}
+
+static void guest_savic_enabled(int id)
+{
+ struct guest_apic_page *apage = get_guest_apic_page();
+
+ /* Host values are written to guest backing page */
+ test_apic_vals(id, true, CTXT_GUEST, true, true);
+ /* Update host APIC regs with guest values */
+ savic_write_apic_regs(apage);
+}
+
+static void guest_code(int id)
+{
+ GUEST_ASSERT(rdmsr(MSR_AMD64_SEV) & MSR_AMD64_SNP_SECURE_AVIC);
+
+ SAVIC_GUEST_SYNC(RESET, guest_savic_start);
+
+ x2apic_enable();
+
+ SAVIC_GUEST_SYNC(X2APIC_ENABLE, guest_x2apic_enabled);
+
+ savic_enable();
+
+ SAVIC_GUEST_SYNC(SAVIC_EN, guest_savic_enabled);
+
+ GUEST_DONE();
+}
+
+static void host_reset_s_test_apic_regs(struct kvm_vcpu *vcpu)
+{
+ /* Set and test APIC regs before guest entry. */
+ set_apic_vals(vcpu->id, true, CTXT_HOST, false);
+ test_apic_vals(vcpu->id, true, CTXT_HOST, false, false);
+}
+
+static void host_reset_s(int id)
+{
+ struct kvm_vcpu *vcpu = vcpus[id];
+ uint64_t apic_base;
+
+ apic_base = vcpu_get_msr(vcpu, MSR_IA32_APICBASE);
+ TEST_ASSERT(apic_base & MSR_IA32_APICBASE_ENABLE,
+ "APIC not in ENABLED state at vCPU RESET");
+ TEST_ASSERT(!(apic_base & X2APIC_ENABLE),
+ "APIC not in xAPIC mode at vCPU RESET");
+ host_reset_s_test_apic_regs(vcpu);
+}
+
+static void host_reset_e(int id)
+{
+ /* On guest exit, APIC regs state must match guest values. */
+ test_apic_vals(id, false, CTXT_HOST, false, false);
+ /* Reset the APIC regs to host values. */
+ set_apic_vals(id, true, CTXT_HOST, false);
+}
+
+static void host_x2apic_enabled_s(int id)
+{
+ struct kvm_vcpu *vcpu = vcpus[id];
+ uint64_t apic_base;
+
+ apic_base = vcpu_get_msr(vcpu, MSR_IA32_APICBASE);
+ TEST_ASSERT(apic_base & X2APIC_ENABLE, "APIC not in x2APIC mode");
+
+ /*
+ * On guest x2apic enablement, previous APIC regs values
+ * which were set by host are preserved.
+ */
+ test_apic_vals(id, true, CTXT_HOST, true, false);
+ set_apic_vals(id, true, CTXT_HOST, true);
+ test_apic_vals(id, true, CTXT_HOST, true, false);
+}
+
+static void host_x2apic_enabled_e(int id)
+{
+ /*
+ * In guest x2apic enabled mode, guest written states are propagated
+ * to host APIC state.
+ */
+ test_apic_vals(id, false, CTXT_HOST, true, false);
+ set_apic_vals(id, true, CTXT_HOST, true);
+}
+
+static void host_post_savic_en_start(int id)
+{
+ /* Host APIC regs before SAVIC enablement are not lost. */
+ test_apic_vals(id, true, CTXT_HOST, true, true);
+ set_apic_vals(id, true, CTXT_HOST, true);
+}
+
+static void host_post_savic_en_end(int id)
+{
+ /*
+ * LVTT and other emulated APIC regs are propagated to host APIC
+ * state by SAVIC guest.
+ */
+ test_apic_vals(id, false, CTXT_HOST, true, true);
+}
+
+static void host_test_savic(struct kvm_vm *vm, int id, enum savic_test_state test_state)
+{
+ switch (test_state) {
+ case RESET_START:
+ host_reset_s(id);
+ break;
+ case RESET_END:
+ host_reset_e(id);
+ break;
+ case X2APIC_ENABLE_START:
+ host_x2apic_enabled_s(id);
+ break;
+ case X2APIC_ENABLE_END:
+ host_x2apic_enabled_e(id);
+ break;
+ case SAVIC_EN_START:
+ host_post_savic_en_start(id);
+ break;
+ case SAVIC_EN_END:
+ host_post_savic_en_end(id);
+ break;
+ default:
+ break;
+ }
+}
+
+static void *vcpu_thread(void *arg)
+{
+ struct kvm_vcpu *vcpu = (struct kvm_vcpu *)arg;
+ struct ucall uc;
+
+ fprintf(stderr, "vCPU thread running vCPU %u\n", vcpu->id);
+
+ while (true) {
+ vcpu_run(vcpu);
+ switch (get_ucall(vcpu, &uc)) {
+ case UCALL_SYNC:
+ host_test_savic(vcpu->vm, vcpu->id, uc.args[1]);
+ break;
+ case UCALL_DONE:
+ return NULL;
+ case UCALL_ABORT:
+ REPORT_GUEST_ASSERT(uc);
+ break;
+ case UCALL_NONE:
+ continue;
+ default:
+ TEST_FAIL("Unknown ucall 0x%lx.", uc.cmd);
+ }
+
+ }
+
+ return NULL;
+}
+
+int main(int argc, char *argv[])
+{
+ struct kvm_sev_init args = { .vmsa_features = BIT_ULL(SVM_FEAT_SECURE_AVIC) |
+ BIT_ULL(SVM_FEAT_ALLOWED_SEV_FEATURES_VALID)
+ };
+ struct kvm_vm *vm;
+ int r;
+
+ TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_SNP));
+ TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_SECURE_AVIC));
+
+ vm = _vm_sev_create_with_one_vcpu(KVM_X86_SNP_VM, guest_code, &vcpus[0], &args);
+
+ virt_pg_map(vm, APIC_DEFAULT_GPA, APIC_DEFAULT_GPA);
+
+ vcpu_args_set(vcpus[0], 1, vcpus[0]->id);
+
+ vm_install_exception_handler(vm, 29, sev_es_vc_handler);
+ vm_sev_launch(vm, snp_default_policy(), NULL);
+
+ r = pthread_create(&threads[0], NULL, vcpu_thread, vcpus[0]);
+ TEST_ASSERT(r == 0, "pthread_create failed errno=%d", errno);
+
+ pthread_join(threads[0], NULL);
+
+ kvm_vm_free(vm);
+
+ return 0;
+}
--
2.34.1
Powered by blists - more mailing lists