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.upadh...@amd.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


Reply via email to