[PATCH v16 4/5] x86/sev: Perform RMP optimizations asynchronously
From: Ashish Kalra
Date: Wed Sep 16 2026 - 18:16:22 EST
From: Ashish Kalra <ashish.kalra@xxxxxxx>
When SNP is enabled, all writes to memory are checked to ensure memory
integrity. This imposes performance overhead on the whole system.
RMPOPT is a new instruction that minimizes the performance overhead of
RMP checks on the hypervisor and on non-SNP guests by allowing RMP
checks to be skipped for 1GB regions of memory that are known not to
contain any SNP guest memory.
Add support for performing RMP optimizations asynchronously using a
dedicated per-CPU workqueue. The workqueue is allocated from an initcall,
and snp_enable_rmpopt() queues the optimization pass.
Shortly after SNP initialization, run an optimization pass over all
physical memory (up to 2TB of system RAM, starting from the lowest
physical memory address aligned down to a 1GB boundary), skipping RMP
checks for 1GB regions that do not contain SNP guest memory (excluding
preassigned pages such as the RMP table and firmware pages).
As SNP guests are launched, RMPUPDATE assigns their private pages to
guest-owned state; when such a page falls within an optimized 1GB
region, the hardware clears that region's RMPOPT optimization and RMP
checks resume there to protect the guest memory.
Since launching SNP guests clears these optimizations, perform them
again asynchronously using the dedicated workqueue.
Suggested-by: Thomas Lendacky <thomas.lendacky@xxxxxxx>
Suggested-by: Dave Hansen <dave.hansen@xxxxxxxxxxxxxxx>
Signed-off-by: Ashish Kalra <ashish.kalra@xxxxxxx>
---
Changes in v15:
- Move the workqueue allocation and the (fixed) optimization range
computation to an initcall; snp_enable_rmpopt() now only programs the
RMPOPT_BASE MSRs and queues the optimization pass.
- Gate rmpopt_capable() on a static rmpopt_enabled bool set when the
workqueue is allocated, instead of an if (rmpopt_wq) check, and drop
rmpopt_wq_mutex.
- Queue both the initial and the teardown pass with mod_delayed_work().
arch/x86/virt/svm/sev.c | 99 +++++++++++++++++++++++++++++++++++++++--
1 file changed, 95 insertions(+), 4 deletions(-)
diff --git a/arch/x86/virt/svm/sev.c b/arch/x86/virt/svm/sev.c
index 25cb486ff61b..bd97135cacc2 100644
--- a/arch/x86/virt/svm/sev.c
+++ b/arch/x86/virt/svm/sev.c
@@ -19,6 +19,7 @@
#include <linux/iommu.h>
#include <linux/amd-iommu.h>
#include <linux/nospec.h>
+#include <linux/workqueue.h>
#include <asm/sev.h>
#include <asm/processor.h>
@@ -124,7 +125,25 @@ static void *rmp_bookkeeping __ro_after_init;
static u64 probed_rmp_base, probed_rmp_size;
-static phys_addr_t rmpopt_pa_start;
+static u64 rmpopt_pa_start, rmpopt_pa_end;
+
+enum rmpopt_op_type {
+ RMPOPT_OP_VERIFY_AND_REPORT_STATUS,
+ RMPOPT_OP_REPORT_STATUS
+};
+
+static struct workqueue_struct *rmpopt_wq;
+static struct delayed_work rmpopt_delayed_work;
+static bool rmpopt_enabled;
+
+/*
+ * Delay, in milliseconds, before the RMP re-optimization pass runs after an SNP
+ * guest is torn down, passed as the delay to mod_delayed_work(). This coalesces
+ * a burst of teardowns into a single scan and gives each guest's pages time to
+ * be converted back to the shared, hypervisor-owned state. The 10 second value
+ * is a heuristic trading re-optimization latency against scanning too eagerly.
+ */
+#define RMPOPT_WORK_TIMEOUT (10 * MSEC_PER_SEC)
static LIST_HEAD(snp_leaked_pages_list);
static DEFINE_SPINLOCK(snp_leaked_pages_list_lock);
@@ -557,6 +576,12 @@ int snp_prepare(void)
}
EXPORT_SYMBOL_FOR_MODULES(snp_prepare, "ccp");
+static void rmpopt_disable(void)
+{
+ if (rmpopt_wq)
+ cancel_delayed_work_sync(&rmpopt_delayed_work);
+}
+
void snp_shutdown(void)
{
u64 syscfg;
@@ -565,6 +590,8 @@ void snp_shutdown(void)
if (syscfg & MSR_AMD64_SYSCFG_SNP_EN)
return;
+ rmpopt_disable();
+
clear_rmp();
on_each_cpu(mfd_reconfigure, NULL, 1);
@@ -580,8 +607,69 @@ EXPORT_SYMBOL_FOR_MODULES(snp_shutdown, "ccp");
static bool rmpopt_capable(void)
{
return cpu_feature_enabled(X86_FEATURE_RMPOPT) &&
- cc_platform_has(CC_ATTR_HOST_SEV_SNP);
+ cc_platform_has(CC_ATTR_HOST_SEV_SNP) && rmpopt_enabled;
+}
+
+/*
+ * RMPOPT optimizations skip RMP checks at 1GB granularity if this range of
+ * memory does not contain any SNP guest memory.
+ *
+ * @pa is a system physical address; RMPOPT operates on the containing 1GB.
+ */
+static void rmpopt(u64 pa)
+{
+ enum rmpopt_op_type op = RMPOPT_OP_VERIFY_AND_REPORT_STATUS;
+ u64 pa_start = ALIGN_DOWN(pa, SZ_1G);
+
+ /* Supported by binutils 2.48+ */
+ asm volatile(".byte 0xf2, 0x0f, 0x01, 0xfc"
+ :: "a" (pa_start), "c" (op)
+ : "memory", "cc");
+}
+
+static void rmpopt_scan_range(void *arg)
+{
+ u64 pa;
+
+ for (pa = rmpopt_pa_start; pa < rmpopt_pa_end; pa += SZ_1G)
+ rmpopt(pa);
+}
+
+static void do_rmpopt_work(struct work_struct *work)
+{
+ /*
+ * Warm up the RMPOPT cache on this pinned per-CPU worker with interrupts
+ * enabled, so the IRQ-disabled fan-out below only issues cache-hit RMPOPTs.
+ */
+ rmpopt_scan_range(NULL);
+
+ on_each_cpu_mask(cpu_primary_thread_mask, rmpopt_scan_range, NULL, true);
+}
+
+static int __init rmpopt_init(void)
+{
+ if (!cpu_feature_enabled(X86_FEATURE_RMPOPT))
+ return 0;
+
+ rmpopt_wq = alloc_workqueue("rmpopt_wq", WQ_PERCPU, 1);
+ if (!rmpopt_wq) {
+ pr_err("Failed to allocate RMPOPT workqueue\n");
+ return 0;
+ }
+
+ INIT_DELAYED_WORK(&rmpopt_delayed_work, do_rmpopt_work);
+
+ /* The optimization range is fixed at boot; compute it once. */
+ rmpopt_pa_start = ALIGN_DOWN(PFN_PHYS(min_low_pfn), SZ_1G);
+ rmpopt_pa_end = ALIGN(PFN_PHYS(max_pfn), SZ_1G);
+ if ((rmpopt_pa_end - rmpopt_pa_start) > SZ_2T)
+ rmpopt_pa_end = rmpopt_pa_start + SZ_2T;
+
+ rmpopt_enabled = true;
+
+ return 0;
}
+device_initcall(rmpopt_init);
void snp_enable_rmpopt(void)
{
@@ -591,8 +679,6 @@ void snp_enable_rmpopt(void)
if (!rmpopt_capable())
return;
- rmpopt_pa_start = ALIGN_DOWN(PFN_PHYS(min_low_pfn), SZ_1G);
-
/*
* Per-CPU RMPOPT tables cover at most 2 TB. Program each core's
* RMPOPT_BASE with the start of RAM to optimize up to 2 TB. The MSR
@@ -610,6 +696,11 @@ void snp_enable_rmpopt(void)
for_each_cpu(cpu, cpu_primary_thread_mask)
wrmsrq_on_cpu(cpu, MSR_AMD64_RMPOPT_BASE,
rmpopt_pa_start | MSR_AMD64_RMPOPT_ENABLE);
+
+ mod_delayed_work(rmpopt_wq, &rmpopt_delayed_work,
+ msecs_to_jiffies(RMPOPT_WORK_TIMEOUT));
+
+ pr_info_once("RMPOPT optimizations enabled\n");
}
EXPORT_SYMBOL_FOR_MODULES(snp_enable_rmpopt, "ccp");
--
2.43.0