Add Secure AVIC lib which provides apis to enable
Secure AVIC for a vCPU. In addition, add guest APIC
backing page initialization support and helper
functions to access APIC regs from guest APIC backing
page and from hv.

Signed-off-by: Neeraj Upadhyay <neeraj.upadh...@amd.com>
---
 tools/testing/selftests/kvm/Makefile.kvm      |   1 +
 .../testing/selftests/kvm/include/x86/apic.h  |   6 +
 .../testing/selftests/kvm/include/x86/savic.h |  19 ++
 tools/testing/selftests/kvm/include/x86/svm.h |   3 +
 .../testing/selftests/kvm/lib/x86/processor.c |   7 +-
 tools/testing/selftests/kvm/lib/x86/savic.c   | 206 ++++++++++++++++++
 tools/testing/selftests/kvm/lib/x86/sev.c     |  15 ++
 7 files changed, 255 insertions(+), 2 deletions(-)
 create mode 100644 tools/testing/selftests/kvm/include/x86/savic.h
 create mode 100644 tools/testing/selftests/kvm/lib/x86/savic.c

diff --git a/tools/testing/selftests/kvm/Makefile.kvm 
b/tools/testing/selftests/kvm/Makefile.kvm
index 5a67e79ae848..50bd78e03d9f 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -25,6 +25,7 @@ LIBKVM_x86 += lib/x86/insn-eval.c
 LIBKVM_x86 += lib/x86/memstress.c
 LIBKVM_x86 += lib/x86/pmu.c
 LIBKVM_x86 += lib/x86/processor.c
+LIBKVM_x86 += lib/x86/savic.c
 LIBKVM_x86 += lib/x86/sev.c
 LIBKVM_x86 += lib/x86/svm.c
 LIBKVM_x86 += lib/x86/ucall.c
diff --git a/tools/testing/selftests/kvm/include/x86/apic.h 
b/tools/testing/selftests/kvm/include/x86/apic.h
index 80fe9f69b38d..6ba5d0545bf8 100644
--- a/tools/testing/selftests/kvm/include/x86/apic.h
+++ b/tools/testing/selftests/kvm/include/x86/apic.h
@@ -29,6 +29,7 @@
 #define        APIC_TASKPRI    0x80
 #define        APIC_PROCPRI    0xA0
 #define        APIC_EOI        0xB0
+#define APIC_LDR       0xD0
 #define        APIC_SPIV       0xF0
 #define                APIC_SPIV_FOCUS_DISABLED        (1 << 9)
 #define                APIC_SPIV_APIC_ENABLED          (1 << 8)
@@ -60,10 +61,15 @@
 #define        APIC_ICR2       0x310
 #define                SET_APIC_DEST_FIELD(x)  ((x) << 24)
 #define APIC_LVTT      0x320
+#define APIC_LVTTHMR    0x330
+#define APIC_LVTPC      0x340
+#define APIC_LVT0       0x350
 #define                APIC_LVT_TIMER_ONESHOT          (0 << 17)
 #define                APIC_LVT_TIMER_PERIODIC         (1 << 17)
 #define                APIC_LVT_TIMER_TSCDEADLINE      (2 << 17)
 #define                APIC_LVT_MASKED                 (1 << 16)
+#define APIC_LVT1       0x360
+#define APIC_LVTERR     0x370
 #define        APIC_TMICT      0x380
 #define        APIC_TMCCT      0x390
 #define        APIC_TDCR       0x3E0
diff --git a/tools/testing/selftests/kvm/include/x86/savic.h 
b/tools/testing/selftests/kvm/include/x86/savic.h
new file mode 100644
index 000000000000..1ab92dad00c1
--- /dev/null
+++ b/tools/testing/selftests/kvm/include/x86/savic.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Helpers used for Secure AVIC guests
+ *
+ */
+#ifndef SELFTEST_KVM_SAVIC_H
+#define SELFTEST_KVM_SAVIC_H
+
+struct guest_apic_page;
+
+void guest_apic_pages_init(struct kvm_vm *vm);
+void set_savic_control_msr(struct guest_apic_page *apic_page, bool enable, 
bool enable_nmi);
+void savic_write_reg(struct guest_apic_page *apic_page, uint32_t reg, uint64_t 
val);
+uint64_t savic_read_reg(struct guest_apic_page *apic_page, uint32_t reg);
+void savic_hv_write_reg(uint32_t reg, uint64_t val);
+uint64_t savic_hv_read_reg(uint32_t reg);
+void savic_enable(void);
+int savic_nr_pages_required(uint64_t page_size);
+#endif
diff --git a/tools/testing/selftests/kvm/include/x86/svm.h 
b/tools/testing/selftests/kvm/include/x86/svm.h
index 66dd4eaf23b9..689fefb72d06 100644
--- a/tools/testing/selftests/kvm/include/x86/svm.h
+++ b/tools/testing/selftests/kvm/include/x86/svm.h
@@ -179,6 +179,9 @@ struct __attribute__ ((__packed__)) vmcb_control_area {
 #define SVM_NESTED_CTL_NP_ENABLE       BIT(0)
 #define SVM_NESTED_CTL_SEV_ENABLE      BIT(1)
 
+#define SVM_FEAT_SECURE_AVIC           16
+#define SVM_FEAT_ALLOWED_SEV_FEATURES_VALID    63
+
 struct __attribute__ ((__packed__)) vmcb_seg {
        u16 selector;
        u16 attrib;
diff --git a/tools/testing/selftests/kvm/lib/x86/processor.c 
b/tools/testing/selftests/kvm/lib/x86/processor.c
index 197110ff1380..2d6105b1f610 100644
--- a/tools/testing/selftests/kvm/lib/x86/processor.c
+++ b/tools/testing/selftests/kvm/lib/x86/processor.c
@@ -659,10 +659,13 @@ void kvm_arch_vm_post_create(struct kvm_vm *vm)
 
 int kvm_arch_vm_additional_pages_required(struct vm_shape shape, uint64_t 
page_size)
 {
-       if (shape.type == KVM_X86_SEV_ES_VM ||
-           shape.type == KVM_X86_SNP_VM)
+       if (shape.type == KVM_X86_SEV_ES_VM)
                return  ghcb_nr_pages_required(page_size);
 
+       if (shape.type == KVM_X86_SNP_VM)
+               return ghcb_nr_pages_required(page_size) +
+                       savic_nr_pages_required(page_size);
+
        return 0;
 }
 
diff --git a/tools/testing/selftests/kvm/lib/x86/savic.c 
b/tools/testing/selftests/kvm/lib/x86/savic.c
new file mode 100644
index 000000000000..f4a765b6040a
--- /dev/null
+++ b/tools/testing/selftests/kvm/lib/x86/savic.c
@@ -0,0 +1,206 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+/*
+ *  Copyright (C) 2024 Advanced Micro Devices, Inc.
+ *
+ */
+
+#include "apic.h"
+#include "kvm_util.h"
+#include "sev.h"
+#include "ex_regs.h"
+
+struct apic_page {
+       u8 apic_regs[PAGE_SIZE];
+} __packed;
+
+struct guest_apic_page {
+       struct apic_page apic_page;
+       uint64_t gpa;
+       uint64_t hva;
+} __attribute__((__aligned__(PAGE_SIZE)));
+
+struct guest_apic_pages {
+       struct guest_apic_page guest_apic_page[KVM_MAX_VCPUS];
+};
+
+static struct guest_apic_pages *apic_page_pool;
+
+enum lapic_lvt_entry {
+       LVT_TIMER,
+       LVT_THERMAL_MONITOR,
+       LVT_PERFORMANCE_COUNTER,
+       LVT_LINT0,
+       LVT_LINT1,
+       LVT_ERROR,
+       APIC_MAX_NR_LVT_ENTRIES,
+};
+
+#define MSR_AMD64_SECURE_AVIC_CONTROL      0xc0010138
+
+#define APIC_LVTx(x) (APIC_LVTT + 0x10 * (x))
+#define MSR_AMD64_SECURE_AVIC_EN_BIT       0
+#define MSR_AMD64_SECURE_AVIC_ALLOWED_NMI_BIT       1
+
+/*
+ * Initial pool of guest apic backing page.
+ */
+void guest_apic_pages_init(struct kvm_vm *vm)
+{
+       struct guest_apic_pages *g_pages;
+       struct guest_apic_page *entry;
+       vm_vaddr_t vaddr;
+       int i;
+       size_t sz = align_up(sizeof(struct guest_apic_pages),
+                            vm_guest_mode_params[vm->mode].page_size);
+
+       vaddr = vm_vaddr_alloc(vm, sz, KVM_UTIL_MIN_VADDR);
+
+       g_pages = (struct guest_apic_pages *)addr_gva2hva(vm, vaddr);
+       memset(g_pages, 0, sz);
+
+       for (i = 0; i < KVM_MAX_VCPUS; ++i) {
+               entry = &g_pages->guest_apic_page[i];
+               entry->hva = (uint64_t)entry;
+               entry->gpa = (uint64_t)addr_hva2gpa(vm, &entry->apic_page);
+       }
+
+       apic_page_pool = (struct guest_apic_pages *)vaddr;
+       sync_global_to_guest(vm, apic_page_pool);
+}
+
+int savic_nr_pages_required(uint64_t page_size)
+{
+       return align_up(sizeof(struct guest_apic_pages), page_size) / page_size;
+}
+
+/*
+ * Enable/disable Secure AVIC in control msr.
+ *
+ * @apic_page  : Guest APIC backing page for the CPU on which
+ *            this function is called.
+ * @enable     : Enable/Disable Secure AVIC.
+ * @enable_nmi : Allow host to send NMI to the guest.
+ */
+void set_savic_control_msr(struct guest_apic_page *apic_page, bool enable, 
bool enable_nmi)
+{
+       uint64_t val = apic_page->gpa | BIT_ULL(MSR_AMD64_SECURE_AVIC_EN_BIT);
+
+       if (!enable) {
+               wrmsr(MSR_AMD64_SECURE_AVIC_CONTROL, 0);
+               return;
+       }
+
+       if (enable_nmi)
+               val |= BIT_ULL(MSR_AMD64_SECURE_AVIC_ALLOWED_NMI_BIT);
+
+       wrmsr(MSR_AMD64_SECURE_AVIC_CONTROL, val);
+}
+
+/*
+ * Write APIC reg offset in the guest APIC backing page.
+ *
+ * @apage : Backing page address.
+ * @reg   : APIC reg offset corresponding to the xapic MMIO
+ *       offset.
+ * @val   : New value to be set for the APIC reg.
+ */
+void savic_write_reg(struct guest_apic_page *apic_page, uint32_t reg, uint64_t 
val)
+{
+       *(volatile uint64_t *)((uint64_t)apic_page + reg) = val;
+}
+
+/*
+ * Read APIC reg offset from the guest APIC backing page.
+ *
+ * @apage : Backing page address.
+ * @reg   : APIC reg offset corresponding to the xapic MMIO
+ *       offset.
+ *
+ * @ret   : APIC register value in the guest APIC backing page.
+ */
+uint64_t savic_read_reg(struct guest_apic_page *apic_page, uint32_t reg)
+{
+       return *(volatile uint64_t *)((uint64_t)apic_page + reg);
+}
+
+/*
+ * Write APIC reg value to hypervisor.
+ *
+ * @reg   : APIC reg offset corresponding to the xapic MMIO
+ *       offset.
+ * @val   : Value to be set for the APIC reg.
+ */
+void savic_hv_write_reg(uint32_t reg, uint64_t val)
+{
+       sev_es_pv_msr_rw(APIC_BASE_MSR + (reg >> 4), &val, true);
+}
+
+/*
+ * Read APIC reg offset from hypervisor.
+ *
+ * @reg   : APIC reg offset corresponding to the xapic MMIO
+ *       offset.
+ *
+ * @ret   : APIC register value in the hypervisor's APIC state.
+ */
+uint64_t savic_hv_read_reg(uint32_t reg)
+{
+       uint64_t val;
+
+       sev_es_pv_msr_rw(APIC_BASE_MSR + (reg >> 4), &val, false);
+
+       return val;
+}
+
+static void savic_init_backing_page(struct guest_apic_page *apic_page, 
uint32_t apic_id)
+{
+       uint64_t regval;
+       enum lapic_lvt_entry i;
+
+       /* Update APIC ID in the backing page */
+       savic_write_reg(apic_page, APIC_ID, apic_id);
+
+       /* Set LVR, LDR, LVT* in backing page from host values */
+       regval = savic_hv_read_reg(APIC_LVR);
+       savic_write_reg(apic_page, APIC_LVR, regval);
+
+       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++) {
+               regval = savic_hv_read_reg(APIC_LVTx(i));
+               savic_write_reg(apic_page, APIC_LVTx(i), regval);
+       }
+
+       regval = savic_hv_read_reg(APIC_LVT0);
+       savic_write_reg(apic_page, APIC_LVT0, regval);
+
+       regval = savic_hv_read_reg(APIC_LVT1);
+       savic_write_reg(apic_page, APIC_LVT1, regval);
+}
+
+/*
+ * Initialize and enable Secure AVIC on a CPU.
+ *
+ * @context: Called from x2apic enabled context and Secure AVIC disabled.
+ */
+void savic_enable(void)
+{
+       uint64_t savic_ctrl_msr_val, exp_msr_val;
+       struct guest_apic_page *apic_page;
+       uint32_t apic_id;
+
+       __GUEST_ASSERT(apic_page_pool, "Guest APIC pages pool is not 
initialized");
+       apic_id = x2apic_read_reg(APIC_ID);
+       apic_page = &apic_page_pool->guest_apic_page[apic_id];
+
+       savic_init_backing_page(apic_page, apic_id);
+       set_savic_control_msr(apic_page, true, true);
+       savic_ctrl_msr_val = rdmsr(MSR_AMD64_SECURE_AVIC_CONTROL);
+       exp_msr_val = apic_page->gpa | BIT_ULL(MSR_AMD64_SECURE_AVIC_EN_BIT) |
+                       BIT_ULL(MSR_AMD64_SECURE_AVIC_ALLOWED_NMI_BIT);
+       __GUEST_ASSERT(savic_ctrl_msr_val == exp_msr_val,
+                       "SAVIC Control msr unexpected val : 0x%lx, expected : 
0x%lx",
+                       savic_ctrl_msr_val, exp_msr_val);
+}
diff --git a/tools/testing/selftests/kvm/lib/x86/sev.c 
b/tools/testing/selftests/kvm/lib/x86/sev.c
index 24aaa75ec450..518e30275960 100644
--- a/tools/testing/selftests/kvm/lib/x86/sev.c
+++ b/tools/testing/selftests/kvm/lib/x86/sev.c
@@ -307,12 +307,27 @@ struct kvm_vm *vm_sev_create_with_one_vcpu(uint32_t type, 
void *guest_code,
        return vm;
 }
 
+static bool is_savic_enabled(void)
+{
+       u64 supported_vmsa_features;
+       int kvm_fd = open_kvm_dev_path_or_exit();
+
+       kvm_device_attr_get(kvm_fd, KVM_X86_GRP_SEV,
+                           KVM_X86_SEV_VMSA_FEATURES,
+                           &supported_vmsa_features);
+
+       return supported_vmsa_features & BIT_ULL(SVM_FEAT_SECURE_AVIC);
+}
+
 void vm_sev_launch(struct kvm_vm *vm, uint64_t policy, uint8_t *measurement)
 {
        if (is_sev_es_vm(vm))
                ghcb_init(vm);
 
        if (is_sev_snp_vm(vm)) {
+               if (is_savic_enabled())
+                       guest_apic_pages_init(vm);
+
                vm_enable_cap(vm, KVM_CAP_EXIT_HYPERCALL, (1 << 
KVM_HC_MAP_GPA_RANGE));
 
                snp_vm_launch_start(vm, policy);
-- 
2.34.1


Reply via email to