[PATCH v2 6/6] x86/percpu: Share decrypted storage before guest CPU setup

From: Zack Rusin

Date: Tue Sep 29 2026 - 00:04:01 EST


Convert every possible CPU's decrypted section after per-CPU setup and
before the boot CPU registers its buffers. This replaces KVM's object
loop and shares VMware steal-time storage without a driver conversion
path or readiness state.

UP KVM registers inside setup_arch(), so convert before guest_late_init()
there and move VMware's UP registration to that hook. Stop boot on a
conversion failure: inconsistent page state must not be published to the
hypervisor. Host SME keeps its existing mappings. Skip Hyper-V vTOM
per-CPU storage: its visibility callbacks require later Hyper-V
initialization.

Suggested-by: Kiryl Shutsemau <kas@xxxxxxxxxx>
Link: https://lore.kernel.org/r/aqqGUAX65s4LdJkr@thinkstation
Link: https://lore.kernel.org/r/aqvxQoIoYhJTZpAC@thinkstation
Signed-off-by: Zack Rusin <zack.rusin@xxxxxxxxxxxx>
---
arch/x86/hyperv/ivm.c | 4 ++++
arch/x86/include/asm/mem_encrypt.h | 2 ++
arch/x86/include/asm/x86_init.h | 2 ++
arch/x86/kernel/cpu/vmware.c | 2 +-
arch/x86/kernel/kvm.c | 35 -----------------------------------
arch/x86/kernel/setup.c | 2 ++
arch/x86/kernel/setup_percpu.c | 2 ++
arch/x86/mm/mem_encrypt.c | 22 ++++++++++++++++++++++
8 files changed, 35 insertions(+), 36 deletions(-)

diff --git a/arch/x86/hyperv/ivm.c b/arch/x86/hyperv/ivm.c
index 2ce4dfe53472..4a5c735c9c52 100644
--- a/arch/x86/hyperv/ivm.c
+++ b/arch/x86/hyperv/ivm.c
@@ -887,6 +887,10 @@ void __init hv_vtom_init(void)
cc_set_mask(ms_hyperv.shared_gpa_boundary);
physical_mask &= ms_hyperv.shared_gpa_boundary - 1;

+ /* vTOM has no early per-CPU consumers and needs the Hyper-V setup. */
+ x86_init.paging.skip_percpu_decryption = true;
+ x86_init.paging.early_decrypt_page = NULL;
+
x86_platform.hyper.is_private_mmio = hv_is_private_mmio;
x86_platform.guest.enc_cache_flush_required = hv_vtom_cache_flush_required;
x86_platform.guest.enc_tlb_flush_required = hv_vtom_tlb_flush_required;
diff --git a/arch/x86/include/asm/mem_encrypt.h b/arch/x86/include/asm/mem_encrypt.h
index 4d81f693b1d3..05cf395407ef 100644
--- a/arch/x86/include/asm/mem_encrypt.h
+++ b/arch/x86/include/asm/mem_encrypt.h
@@ -21,11 +21,13 @@ struct boot_params;
#ifdef CONFIG_X86_MEM_ENCRYPT
void __init mem_encrypt_init(void);
void __init mem_encrypt_setup_arch(void);
+void __init mem_encrypt_init_percpu(void);
int __init early_set_memory_decrypted(unsigned long vaddr, unsigned long size);
void __init early_set_page_decrypted(unsigned long addr, unsigned long alias);
#else
static inline void mem_encrypt_init(void) { }
static inline void __init mem_encrypt_setup_arch(void) { }
+static inline void __init mem_encrypt_init_percpu(void) { }
static inline int __init
early_set_memory_decrypted(unsigned long vaddr, unsigned long size) { return 0; }
#endif
diff --git a/arch/x86/include/asm/x86_init.h b/arch/x86/include/asm/x86_init.h
index e4131402c783..8d1597372eb6 100644
--- a/arch/x86/include/asm/x86_init.h
+++ b/arch/x86/include/asm/x86_init.h
@@ -76,10 +76,12 @@ struct x86_init_oem {
* Callback must call paging_init(). Called once after the
* direct mapping for phys memory is available.
* @early_decrypt_page: Share a direct-mapped page and its optional image alias
+ * @skip_percpu_decryption: Platform does not use early shared per-CPU data
*/
struct x86_init_paging {
void (*pagetable_init)(void);
int (*early_decrypt_page)(unsigned long addr, unsigned long alias);
+ bool skip_percpu_decryption;
};

/**
diff --git a/arch/x86/kernel/cpu/vmware.c b/arch/x86/kernel/cpu/vmware.c
index 34b73573b108..b477cc027b18 100644
--- a/arch/x86/kernel/cpu/vmware.c
+++ b/arch/x86/kernel/cpu/vmware.c
@@ -366,7 +366,7 @@ static void __init vmware_paravirt_ops_setup(void)
vmware_cpu_down_prepare) < 0)
pr_err("vmware_guest: Failed to install cpu hotplug callbacks\n");
#else
- vmware_guest_cpu_init();
+ x86_init.hyper.guest_late_init = vmware_guest_cpu_init;
#endif
}
}
diff --git a/arch/x86/kernel/kvm.c b/arch/x86/kernel/kvm.c
index 6b0a5861ccb8..acb3b7b18ebe 100644
--- a/arch/x86/kernel/kvm.c
+++ b/arch/x86/kernel/kvm.c
@@ -429,34 +429,6 @@ static u64 kvm_steal_clock(int cpu)
return steal;
}

-static inline __init void __set_percpu_decrypted(void *ptr, unsigned long size)
-{
- early_set_memory_decrypted((unsigned long) ptr, size);
-}
-
-/*
- * Iterate through all possible CPUs and map the memory region pointed
- * by apf_reason, steal_time and kvm_apic_eoi as decrypted at once.
- *
- * Note: we iterate through all possible CPUs to ensure that CPUs
- * hotplugged will have their per-cpu variable already mapped as
- * decrypted.
- */
-static void __init sev_map_percpu_data(void)
-{
- int cpu;
-
- if (cc_vendor != CC_VENDOR_AMD ||
- !cc_platform_has(CC_ATTR_GUEST_MEM_ENCRYPT))
- return;
-
- for_each_possible_cpu(cpu) {
- __set_percpu_decrypted(&per_cpu(apf_reason, cpu), sizeof(apf_reason));
- __set_percpu_decrypted(&per_cpu(steal_time, cpu), sizeof(steal_time));
- __set_percpu_decrypted(&per_cpu(kvm_apic_eoi, cpu), sizeof(kvm_apic_eoi));
- }
-}
-
static void kvm_guest_cpu_offline(bool shutdown)
{
kvm_disable_steal_time();
@@ -709,12 +681,6 @@ arch_initcall(kvm_alloc_cpumask);

static void __init kvm_smp_prepare_boot_cpu(void)
{
- /*
- * Map the per-cpu variables as decrypted before kvm_guest_cpu_init()
- * shares the guest physical address with the hypervisor.
- */
- sev_map_percpu_data();
-
kvm_guest_cpu_init();
native_smp_prepare_boot_cpu();
kvm_spinlock_init();
@@ -868,7 +834,6 @@ static void __init kvm_guest_init(void)
kvm_cpu_online, kvm_cpu_down_prepare) < 0)
pr_err("failed to install cpu hotplug callbacks\n");
#else
- sev_map_percpu_data();
kvm_guest_cpu_init();
#endif

diff --git a/arch/x86/kernel/setup.c b/arch/x86/kernel/setup.c
index cda6adb9f69c..8eebd85e59af 100644
--- a/arch/x86/kernel/setup.c
+++ b/arch/x86/kernel/setup.c
@@ -1251,6 +1251,8 @@ void __init setup_arch(char **cmdline_p)

io_apic_init_mappings();

+ if (!IS_ENABLED(CONFIG_SMP))
+ mem_encrypt_init_percpu();
x86_init.hyper.guest_late_init();

e820__reserve_resources();
diff --git a/arch/x86/kernel/setup_percpu.c b/arch/x86/kernel/setup_percpu.c
index c83c61e0b20a..8526ad37a81b 100644
--- a/arch/x86/kernel/setup_percpu.c
+++ b/arch/x86/kernel/setup_percpu.c
@@ -5,6 +5,7 @@
#include <linux/export.h>
#include <linux/init.h>
#include <linux/memblock.h>
+#include <linux/mem_encrypt.h>
#include <linux/percpu.h>
#include <linux/kexec.h>
#include <linux/crash_dump.h>
@@ -234,4 +235,5 @@ void __init setup_per_cpu_areas(void)
* this call?
*/
sync_initial_page_table();
+ mem_encrypt_init_percpu();
}
diff --git a/arch/x86/mm/mem_encrypt.c b/arch/x86/mm/mem_encrypt.c
index c3e239a47b66..d3224607170d 100644
--- a/arch/x86/mm/mem_encrypt.c
+++ b/arch/x86/mm/mem_encrypt.c
@@ -13,6 +13,7 @@
#include <linux/cc_platform.h>
#include <linux/mem_encrypt.h>
#include <linux/pgalloc.h>
+#include <linux/percpu.h>
#include <linux/virtio_anchor.h>
#include <linux/iommu-dma.h>

@@ -24,6 +25,8 @@

#include "mm_internal.h"

+extern char __percpu __start_percpu_decrypted[], __end_percpu_decrypted[];
+
static pte_t * __init early_lookup_pte(unsigned long addr)
{
unsigned long pfn, step;
@@ -134,6 +137,25 @@ int __init early_set_memory_decrypted(unsigned long vaddr, unsigned long size)
return 0;
}

+void __init mem_encrypt_init_percpu(void)
+{
+ unsigned long size = __end_percpu_decrypted - __start_percpu_decrypted;
+ int cpu, ret;
+
+ if (!cc_platform_has(CC_ATTR_GUEST_MEM_ENCRYPT) ||
+ x86_init.paging.skip_percpu_decryption)
+ return;
+
+ for_each_possible_cpu(cpu) {
+ unsigned long addr = (unsigned long)
+ per_cpu_ptr(__start_percpu_decrypted, cpu);
+
+ ret = early_set_memory_decrypted(addr, size);
+ if (ret)
+ panic("Cannot share CPU %d per-CPU data (err=%d)", cpu, ret);
+ }
+}
+
/* Override for DMA direct allocation check - ARCH_HAS_FORCE_DMA_UNENCRYPTED */
bool force_dma_unencrypted(struct device *dev)
{

--
2.53.0