[PATCH v2 4/8] KVM: SEV: Add direct VMSA capability
From: Jörg Rödel
Date: Tue Sep 08 2026 - 06:55:49 EST
From: Joerg Roedel <joerg.roedel@xxxxxxx>
Direct VMSA mode is currently selected implicitly when userspace installs
a guest-owned VMSA on any vCPU. That makes the launch model a side effect
of per-vCPU state and applies it retroactively to every vCPU.
Add KVM_CAP_SNP_DIRECT_VMSA as a VM-scoped settable capability. Require
userspace to enable it on an SNP VM before creating vCPUs, and reject
nonzero arguments.
When enabled, skip allocating KVM-owned VMSAs during vCPU creation and
skip generating and measuring KVM-owned VMSAs at launch finish. Keep
legacy allocation and measurement behavior when it is not enabled.
Assisted-by: LLM
---
arch/x86/include/asm/kvm-x86-ops.h | 1 +
arch/x86/include/asm/kvm_host.h | 1 +
arch/x86/kvm/svm/sev.c | 28 ++++++++++++++++++++--------
arch/x86/kvm/svm/svm.c | 22 ++++++++++++++++++++++
arch/x86/kvm/svm/svm.h | 1 +
arch/x86/kvm/x86.c | 5 +++++
include/uapi/linux/kvm.h | 1 +
7 files changed, 51 insertions(+), 8 deletions(-)
diff --git a/arch/x86/include/asm/kvm-x86-ops.h b/arch/x86/include/asm/kvm-x86-ops.h
index e213c9ae3e30..a48259696ca6 100644
--- a/arch/x86/include/asm/kvm-x86-ops.h
+++ b/arch/x86/include/asm/kvm-x86-ops.h
@@ -23,6 +23,7 @@ KVM_X86_OP(vcpu_after_set_cpuid)
KVM_X86_OP(vm_init)
KVM_X86_OP_OPTIONAL(vm_destroy)
KVM_X86_OP_OPTIONAL(vm_pre_destroy)
+KVM_X86_OP_OPTIONAL(enable_vm_cap)
KVM_X86_OP_OPTIONAL_RET0(vcpu_precreate)
KVM_X86_OP(vcpu_create)
KVM_X86_OP(vcpu_free)
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index 683bb8bf43a9..3bfc02bb7998 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -1519,6 +1519,7 @@ struct kvm_x86_ops {
int (*vm_init)(struct kvm *kvm);
void (*vm_destroy)(struct kvm *kvm);
void (*vm_pre_destroy)(struct kvm *kvm);
+ int (*enable_vm_cap)(struct kvm *kvm, struct kvm_enable_cap *cap);
/* Create, but do not attach this VCPU */
int (*vcpu_precreate)(struct kvm *kvm);
diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c
index ee1bf9b4cc53..5a282aff04a8 100644
--- a/arch/x86/kvm/svm/sev.c
+++ b/arch/x86/kvm/svm/sev.c
@@ -2524,8 +2524,15 @@ static int snp_launch_update_vmsa(struct kvm *kvm, struct kvm_sev_cmd *argp)
kvm_for_each_vcpu(i, vcpu, kvm) {
struct vcpu_svm *svm = to_svm(vcpu);
- u64 pfn = __pa(svm->sev_es.vmsa) >> PAGE_SHIFT;
+ u64 pfn;
+
+ if (sev->snp_direct_vmsa) {
+ if (!svm->sev_es.snp_has_guest_vmsa)
+ svm->vmcb->control.vmsa_pa = INVALID_PAGE;
+ goto protect_vcpu;
+ }
+ pfn = __pa(svm->sev_es.vmsa) >> PAGE_SHIFT;
ret = sev_es_sync_vmsa(svm);
if (ret)
goto out;
@@ -2545,6 +2552,7 @@ static int snp_launch_update_vmsa(struct kvm *kvm, struct kvm_sev_cmd *argp)
goto out;
}
+protect_vcpu:
svm->vcpu.arch.guest_state_protected = true;
/*
* SEV-ES (and thus SNP) guest mandates LBR Virtualization to
@@ -3559,6 +3567,9 @@ void sev_free_vcpu(struct kvm_vcpu *vcpu)
* a guest-owned page. Transition the page to hypervisor state before
* releasing it back to the system.
*/
+ if (!svm->sev_es.vmsa)
+ goto skip_vmsa_free;
+
if (is_sev_snp_guest(vcpu)) {
u64 pfn = __pa(svm->sev_es.vmsa) >> PAGE_SHIFT;
@@ -4056,13 +4067,9 @@ static void __sev_snp_reload_vmsa(struct kvm_vcpu *vcpu, gpa_t gpa)
vmcb_mark_all_dirty(svm->vmcb);
/*
- * From this point forward, the VMSA will always be a guest-mapped page
- * rather than the initial one allocated by KVM in svm->sev_es.vmsa. In
- * theory, svm->sev_es.vmsa could be free'd and cleaned up here, but
- * that involves cleanups like flushing caches, which would ideally be
- * handled during teardown rather than guest boot. Deferring that also
- * allows the existing logic for SEV-ES VMSAs to be re-used with
- * minimal SNP-specific changes.
+ * From this point forward, the VMSA will always be a guest-mapped page.
+ * If KVM allocated an initial VMSA, keep it until teardown to defer
+ * cache flushing and other cleanup out of the guest boot path.
*/
svm->sev_es.snp_has_guest_vmsa = true;
@@ -4929,12 +4936,15 @@ void sev_init_vmcb(struct vcpu_svm *svm, bool init_event)
int sev_vcpu_create(struct kvm_vcpu *vcpu)
{
struct vcpu_svm *svm = to_svm(vcpu);
+ struct kvm_sev_info *sev = to_kvm_sev_info(vcpu->kvm);
struct page *vmsa_page;
mutex_init(&svm->sev_es.snp_vmsa_mutex);
if (!is_sev_es_guest(vcpu))
return 0;
+ if (is_sev_snp_guest(vcpu) && sev->snp_direct_vmsa)
+ goto init_vmsa_state;
/*
* SEV-ES guests require a separate (from the VMCB) VMSA page used to
@@ -4945,6 +4955,8 @@ int sev_vcpu_create(struct kvm_vcpu *vcpu)
return -ENOMEM;
svm->sev_es.vmsa = page_address(vmsa_page);
+
+init_vmsa_state:
svm->sev_es.snp_pending_vmsa_gpa = INVALID_PAGE;
svm->sev_es.snp_guest_vmsa_gpa = INVALID_PAGE;
diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
index 7d59d301e1e5..98c5cffe2c22 100644
--- a/arch/x86/kvm/svm/svm.c
+++ b/arch/x86/kvm/svm/svm.c
@@ -5325,6 +5325,27 @@ static void *svm_alloc_apic_backing_page(struct kvm_vcpu *vcpu)
return page_address(page);
}
+static int svm_enable_vm_cap(struct kvm *kvm, struct kvm_enable_cap *cap)
+{
+ switch (cap->cap) {
+#ifdef CONFIG_KVM_AMD_SEV
+ case KVM_CAP_SNP_DIRECT_VMSA:
+ if (memchr_inv(cap->args, 0, sizeof(cap->args)) ||
+ kvm->arch.vm_type != KVM_X86_SNP_VM)
+ return -EINVAL;
+
+ guard(mutex)(&kvm->lock);
+ if (kvm->created_vcpus)
+ return -EINVAL;
+
+ to_kvm_sev_info(kvm)->snp_direct_vmsa = true;
+ return 0;
+#endif
+ default:
+ return -EINVAL;
+ }
+}
+
struct kvm_x86_ops svm_x86_ops __initdata = {
.name = KBUILD_MODNAME,
@@ -5345,6 +5366,7 @@ struct kvm_x86_ops svm_x86_ops __initdata = {
.vm_init = svm_vm_init,
.vm_pre_destroy = avic_vm_pre_destroy,
.vm_destroy = svm_vm_destroy,
+ .enable_vm_cap = svm_enable_vm_cap,
.prepare_switch_to_guest = svm_prepare_switch_to_guest,
.vcpu_load = svm_vcpu_load,
diff --git a/arch/x86/kvm/svm/svm.h b/arch/x86/kvm/svm/svm.h
index e958943b8162..881d662aaab2 100644
--- a/arch/x86/kvm/svm/svm.h
+++ b/arch/x86/kvm/svm/svm.h
@@ -121,6 +121,7 @@ struct kvm_sev_info {
struct mutex guest_req_mutex; /* Must acquire before using bounce buffers */
cpumask_var_t have_run_cpus; /* CPUs that have done VMRUN for this VM. */
bool snp_certs_enabled; /* SNP certificate-fetching support. */
+ bool snp_direct_vmsa; /* Userspace provides and measures VMSA pages. */
};
#endif
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 79468ddfe473..fb4857eca6c6 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -2404,6 +2404,9 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
case KVM_CAP_VM_TYPES:
r = kvm_caps.supported_vm_types;
break;
+ case KVM_CAP_SNP_DIRECT_VMSA:
+ r = !!(kvm_caps.supported_vm_types & BIT(KVM_X86_SNP_VM));
+ break;
case KVM_CAP_READONLY_MEM:
r = kvm ? kvm_arch_has_readonly_mem(kvm) : 1;
break;
@@ -4212,6 +4215,8 @@ int kvm_vm_ioctl_enable_cap(struct kvm *kvm,
}
default:
r = -EINVAL;
+ if (kvm_x86_ops.enable_vm_cap)
+ r = kvm_x86_call(enable_vm_cap)(kvm, cap);
break;
}
return r;
diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
index ac2d77d14963..283e881b2634 100644
--- a/include/uapi/linux/kvm.h
+++ b/include/uapi/linux/kvm.h
@@ -999,6 +999,7 @@ struct kvm_enable_cap {
#define KVM_CAP_S390_HPAGE_2G 249
#define KVM_CAP_PPC_COMPAT_CAPS 250
#define KVM_CAP_ARM_PMU_V3_STRICT 251
+#define KVM_CAP_SNP_DIRECT_VMSA 252
struct kvm_irq_routing_irqchip {
__u32 irqchip;
--
2.53.0