[RFC 02/14] x86/apic: Initialize Secure AVIC APIC backing page

From: Neeraj Upadhyay
Date: Fri Sep 13 2024 - 07:38:23 EST


From: Kishon Vijay Abraham I <kvijayab@xxxxxxx>

With Secure AVIC, the APIC backing page is owned and managed by guest.
Allocate APIC backing page for all guest CPUs. In addition, add a
setup() APIC callback. This callback is used by Secure AVIC driver to
initialize APIC backing page area for each CPU.

Allocate APIC backing page memory area in chunks of 2M, so that
backing page memory is mapped using full huge pages. Without this,
if there are private to shared page state conversions for any
non-backing-page allocation which is part of the same huge page as the
one containing a backing page, hypervisor splits the huge page into 4K
pages. Splitting of APIC backing page area into individual 4K pages can
result in performance impact, due to TLB pressure.

Secure AVIC requires that vCPU's APIC backing page's NPT entry is always
present while that vCPU is running. If APIC backing page's NPT entry is
not present, a VMEXIT_BUSY is returned on VMRUN and the vCPU cannot
be resumed after that point. To handle this, invoke sev_notify_savic_gpa()
in Secure AVIC driver's setup() callback. This triggers SVM_VMGEXIT_SECURE_
AVIC_GPA exit for the hypervisor to note GPA of the vCPU's APIC
backing page. Hypervisor uses this information to ensure that the APIC
backing page is mapped in NPT before invoking VMRUN.

Signed-off-by: Kishon Vijay Abraham I <kvijayab@xxxxxxx>
Co-developed-by: Neeraj Upadhyay <Neeraj.Upadhyay@xxxxxxx>
Signed-off-by: Neeraj Upadhyay <Neeraj.Upadhyay@xxxxxxx>
---

GHCB spec update for SVM_VMGEXIT_SECURE_AVIC_GPA NAE event is
part of the draft spec:

https://lore.kernel.org/linux-coco/3453675d-ca29-4715-9c17-10b56b3af17e@xxxxxxx/T/#u

arch/x86/coco/sev/core.c | 22 +++++++++++++++++
arch/x86/include/asm/apic.h | 1 +
arch/x86/include/asm/sev.h | 2 ++
arch/x86/include/uapi/asm/svm.h | 1 +
arch/x86/kernel/apic/apic.c | 2 ++
arch/x86/kernel/apic/x2apic_savic.c | 38 +++++++++++++++++++++++++++++
6 files changed, 66 insertions(+)

diff --git a/arch/x86/coco/sev/core.c b/arch/x86/coco/sev/core.c
index de1df0cb45da..93470538af5e 100644
--- a/arch/x86/coco/sev/core.c
+++ b/arch/x86/coco/sev/core.c
@@ -1367,6 +1367,28 @@ static enum es_result vc_handle_msr(struct ghcb *ghcb, struct es_em_ctxt *ctxt)
return ret;
}

+enum es_result sev_notify_savic_gpa(u64 gpa)
+{
+ struct ghcb_state state;
+ struct es_em_ctxt ctxt;
+ unsigned long flags;
+ struct ghcb *ghcb;
+ int ret = 0;
+
+ local_irq_save(flags);
+
+ ghcb = __sev_get_ghcb(&state);
+
+ vc_ghcb_invalidate(ghcb);
+
+ ret = sev_es_ghcb_hv_call(ghcb, &ctxt, SVM_VMGEXIT_SECURE_AVIC_GPA, gpa, 0);
+
+ __sev_put_ghcb(&state);
+
+ local_irq_restore(flags);
+ return ret;
+}
+
static void snp_register_per_cpu_ghcb(void)
{
struct sev_es_runtime_data *data;
diff --git a/arch/x86/include/asm/apic.h b/arch/x86/include/asm/apic.h
index 9327eb00e96d..ca682c1e8748 100644
--- a/arch/x86/include/asm/apic.h
+++ b/arch/x86/include/asm/apic.h
@@ -302,6 +302,7 @@ struct apic {

/* Probe, setup and smpboot functions */
int (*probe)(void);
+ void (*setup)(void);
int (*acpi_madt_oem_check)(char *oem_id, char *oem_table_id);

void (*init_apic_ldr)(void);
diff --git a/arch/x86/include/asm/sev.h b/arch/x86/include/asm/sev.h
index 79bbe2be900e..e84fc7fcc32a 100644
--- a/arch/x86/include/asm/sev.h
+++ b/arch/x86/include/asm/sev.h
@@ -399,6 +399,7 @@ u64 snp_get_unsupported_features(u64 status);
u64 sev_get_status(void);
void sev_show_status(void);
void snp_update_svsm_ca(void);
+enum es_result sev_notify_savic_gpa(u64 gpa);

#else /* !CONFIG_AMD_MEM_ENCRYPT */

@@ -435,6 +436,7 @@ static inline u64 snp_get_unsupported_features(u64 status) { return 0; }
static inline u64 sev_get_status(void) { return 0; }
static inline void sev_show_status(void) { }
static inline void snp_update_svsm_ca(void) { }
+static inline enum es_result sev_notify_savic_gpa(u64 gpa) { return ES_UNSUPPORTED; }

#endif /* CONFIG_AMD_MEM_ENCRYPT */

diff --git a/arch/x86/include/uapi/asm/svm.h b/arch/x86/include/uapi/asm/svm.h
index 1814b413fd57..0f21cea6d21c 100644
--- a/arch/x86/include/uapi/asm/svm.h
+++ b/arch/x86/include/uapi/asm/svm.h
@@ -116,6 +116,7 @@
#define SVM_VMGEXIT_AP_CREATE 1
#define SVM_VMGEXIT_AP_DESTROY 2
#define SVM_VMGEXIT_SNP_RUN_VMPL 0x80000018
+#define SVM_VMGEXIT_SECURE_AVIC_GPA 0x8000001a
#define SVM_VMGEXIT_HV_FEATURES 0x8000fffd
#define SVM_VMGEXIT_TERM_REQUEST 0x8000fffe
#define SVM_VMGEXIT_TERM_REASON(reason_set, reason_code) \
diff --git a/arch/x86/kernel/apic/apic.c b/arch/x86/kernel/apic/apic.c
index 373638691cd4..b47d1dc854c3 100644
--- a/arch/x86/kernel/apic/apic.c
+++ b/arch/x86/kernel/apic/apic.c
@@ -1499,6 +1499,8 @@ static void setup_local_APIC(void)
return;
}

+ if (apic->setup)
+ apic->setup();
/*
* If this comes from kexec/kcrash the APIC might be enabled in
* SPIV. Soft disable it before doing further initialization.
diff --git a/arch/x86/kernel/apic/x2apic_savic.c b/arch/x86/kernel/apic/x2apic_savic.c
index 97dac09a7f42..d903c35b8b64 100644
--- a/arch/x86/kernel/apic/x2apic_savic.c
+++ b/arch/x86/kernel/apic/x2apic_savic.c
@@ -9,12 +9,16 @@

#include <linux/cpumask.h>
#include <linux/cc_platform.h>
+#include <linux/percpu-defs.h>

#include <asm/apic.h>
#include <asm/sev.h>

#include "local.h"

+static DEFINE_PER_CPU(void *, apic_backing_page);
+static DEFINE_PER_CPU(bool, savic_setup_done);
+
static int x2apic_savic_acpi_madt_oem_check(char *oem_id, char *oem_table_id)
{
return x2apic_enabled() && cc_platform_has(CC_ATTR_SNP_SECURE_AVIC);
@@ -61,8 +65,30 @@ static void x2apic_savic_send_IPI_mask_allbutself(const struct cpumask *mask, in
__send_IPI_mask(mask, vector, APIC_DEST_ALLBUT);
}

+static void x2apic_savic_setup(void)
+{
+ void *backing_page;
+ enum es_result ret;
+ unsigned long gpa;
+
+ if (this_cpu_read(savic_setup_done))
+ return;
+
+ backing_page = this_cpu_read(apic_backing_page);
+ gpa = __pa(backing_page);
+ ret = sev_notify_savic_gpa(gpa);
+ if (ret != ES_OK)
+ snp_abort();
+ this_cpu_write(savic_setup_done, true);
+}
+
static int x2apic_savic_probe(void)
{
+ void *backing_pages;
+ unsigned int cpu;
+ size_t sz;
+ int i;
+
if (!cc_platform_has(CC_ATTR_SNP_SECURE_AVIC))
return 0;

@@ -71,6 +97,17 @@ static int x2apic_savic_probe(void)
snp_abort();
}

+ sz = ALIGN(num_possible_cpus() * SZ_4K, SZ_2M);
+ backing_pages = kzalloc(sz, GFP_ATOMIC);
+ if (!backing_pages)
+ snp_abort();
+
+ i = 0;
+ for_each_possible_cpu(cpu) {
+ per_cpu(apic_backing_page, cpu) = backing_pages + i * SZ_4K;
+ i++;
+ }
+
pr_info("Secure AVIC Enabled\n");

return 1;
@@ -81,6 +118,7 @@ static struct apic apic_x2apic_savic __ro_after_init = {
.name = "secure avic x2apic",
.probe = x2apic_savic_probe,
.acpi_madt_oem_check = x2apic_savic_acpi_madt_oem_check,
+ .setup = x2apic_savic_setup,

.dest_mode_logical = false,

--
2.34.1