Kernel KVM virtualization development
 help / color / mirror / Atom feed
* [PATCH v2 0/8] KVM: SVM: Support direct setting of VMSA for SEV-SNP guests
@ 2026-09-08 10:33 Jörg Rödel
  2026-09-08 10:33 ` [PATCH v2 1/8] KVM: SEV: Document SNP direct VMSA userspace ABI Jörg Rödel
                   ` (7 more replies)
  0 siblings, 8 replies; 12+ messages in thread
From: Jörg Rödel @ 2026-09-08 10:33 UTC (permalink / raw)
  To: Paolo Bonzini, Sean Christopherson
  Cc: Michael Roth, Liam Merwick, Vishal Annapurve, Ninad Naik,
	Joerg Roedel, Tom Lendacky, James Bottomley, kvm, linux-doc,
	linux-kernel, linux-kselftest, coconut-svsm

From: Joerg Roedel <joerg.roedel@amd.com>

Hi,

here is the updated version of KVM-AMD changes to allow user-space to
directly set the boot VMSA (direct-vmsa) for an SEV-SNP virtual
machine.

The direct-vmsa support is required for stable launch measurements
when loading the initial SEV-SNP guest image from an IGVM file.
Without this support KVM measures a VMSA at a fixed GPA for every
VCPU, which makes the launch measurement dependent on the number of
VCPUs. Besides that the current KVM VMSA-allocation behavior poses
challenges for live migration of SEV-SNP VMs. The current behavior is
fully preserved when direct-vmsa is not used.

For a longer justification have a look at this email from the v1
thread:
	
	https://lore.kernel.org/kvm/aiqwjn2DVOumfr3T@8bytes.org/

One of the main concerns with the previous version was the user-space
API, which has changed significantly in v2. The API now consists of
three parts:

	- A new KVM_CAP_SNP_DIRECT_VMSA VM-level capability which can
	  be queried and enabled from user-space. When user-space
	  enabled the capability, KVM will no longer allocate VMSA
	  pages when creating VCPUs. This means that the capability
	  can only be enabled before any VCPUs are created.

	- Allow VMSA pages in SNP launch updates. This was already
	  present in the previous version, but has different semantics
	  now. In this version all it does is adding the VMSA pages to
	  the initial image and the launch measurement. 
	
	- A new per-VCPU MEM_ENC operation to get and set the VMSA and
	  GHCB addresses of a VCPU. User-space will use this operation
	  to set the GPA of the launch VMSA of the VCPUs, but can
	  also query and set the addresses during runtime. 

	  This operation is designed to support the requirements of
	  future live-migration support, which needs to be able to set
	  and get VMSA as well as GHCB addresses. Since this
	  functionality is a superset of the operation needed for
	  direct-vmsa support, it goes the extra mile and allows
	  import and export of all SEV-SNP specific VCPU state.

For v2 I heavily used AI (codex with gpt-5.6-sol medium) and countless
iterations with manual review and testing. All changes are reviewed by
me, tested with KVM selftests, and booting an SVSM (as far as it
gets without planes support). Lockdep was enabled during testing as
well.

Please review.

-Joerg

Joerg Roedel (8):
  KVM: SEV: Document SNP direct VMSA user-space ABI
  KVM: SVM: Implement GET_AP_APIC_IDS NAE event
  KVM: SVM: Hold SRCU while reloading guest-owned VMSAs
  KVM: SEV: Add direct VMSA capability
  KVM: SEV: Allow VMSA pages in SNP launch updates
  KVM: SEV: Add SNP vCPU state get and set commands
  KVM: selftests: Test the SNP APIC-ID-list GHCB request
  KVM: selftests: Test SNP vCPU state and direct VMSA launch

 .../virt/kvm/x86/amd-memory-encryption.rst    |  66 ++-
 arch/x86/include/asm/kvm-x86-ops.h            |   1 +
 arch/x86/include/asm/kvm_host.h               |   1 +
 arch/x86/include/asm/sev-common.h             |   1 +
 arch/x86/include/uapi/asm/kvm.h               |  13 +
 arch/x86/include/uapi/asm/svm.h               |   1 +
 arch/x86/kvm/svm/sev.c                        | 287 +++++++++++-
 arch/x86/kvm/svm/svm.c                        |  23 +
 arch/x86/kvm/svm/svm.h                        |   2 +
 arch/x86/kvm/x86.c                            |   6 +
 include/uapi/linux/kvm.h                      |   2 +
 tools/testing/selftests/kvm/Makefile.kvm      |   2 +
 tools/testing/selftests/kvm/include/x86/sev.h |  49 ++
 .../selftests/kvm/x86/sev_snp_apic_id_test.c  | 180 ++++++++
 .../kvm/x86/sev_snp_direct_vmsa_test.c        | 421 ++++++++++++++++++
 15 files changed, 1039 insertions(+), 16 deletions(-)
 create mode 100644 tools/testing/selftests/kvm/x86/sev_snp_apic_id_test.c
 create mode 100644 tools/testing/selftests/kvm/x86/sev_snp_direct_vmsa_test.c


base-commit: df2908090cda368b01ff43709f51890076c56157
-- 
2.53.0


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH v2 1/8] KVM: SEV: Document SNP direct VMSA userspace ABI
  2026-09-08 10:33 [PATCH v2 0/8] KVM: SVM: Support direct setting of VMSA for SEV-SNP guests Jörg Rödel
@ 2026-09-08 10:33 ` Jörg Rödel
  2026-09-08 10:33 ` [PATCH v2 2/8] KVM: SVM: Implement GET_AP_APIC_IDS NAE event Jörg Rödel
                   ` (6 subsequent siblings)
  7 siblings, 0 replies; 12+ messages in thread
From: Jörg Rödel @ 2026-09-08 10:33 UTC (permalink / raw)
  To: Paolo Bonzini, Sean Christopherson
  Cc: Michael Roth, Liam Merwick, Vishal Annapurve, Ninad Naik,
	Joerg Roedel, Tom Lendacky, James Bottomley, kvm, linux-doc,
	linux-kernel, linux-kselftest, coconut-svsm

From: Joerg Roedel <joerg.roedel@amd.com>

Document the VMSA launch-update page type, the vCPU-scoped state get and
set commands, and the VM-scoped direct-VMSA capability in one place.

Describe the ordering and argument requirements for enabling direct-VMSA
mode, and contrast its VMSA allocation and measurement behavior with the
legacy launch flow.

Assisted-by: LLM
---
 .../virt/kvm/x86/amd-memory-encryption.rst    | 66 ++++++++++++++++++-
 1 file changed, 64 insertions(+), 2 deletions(-)

diff --git a/Documentation/virt/kvm/x86/amd-memory-encryption.rst b/Documentation/virt/kvm/x86/amd-memory-encryption.rst
index bd04a908a8db..427b673c3832 100644
--- a/Documentation/virt/kvm/x86/amd-memory-encryption.rst
+++ b/Documentation/virt/kvm/x86/amd-memory-encryption.rst
@@ -536,6 +536,7 @@ Returns: 0 on success, < 0 on error, -EAGAIN if caller should retry
 where the allowed values for page_type are #define'd as::
 
         KVM_SEV_SNP_PAGE_TYPE_NORMAL
+        KVM_SEV_SNP_PAGE_TYPE_VMSA
         KVM_SEV_SNP_PAGE_TYPE_ZERO
         KVM_SEV_SNP_PAGE_TYPE_UNMEASURED
         KVM_SEV_SNP_PAGE_TYPE_SECRETS
@@ -544,7 +545,68 @@ where the allowed values for page_type are #define'd as::
 See the SEV-SNP spec [snp-fw-abi]_ for further details on how each page type is
 used/measured.
 
-20. KVM_SEV_SNP_LAUNCH_FINISH
+``KVM_SEV_SNP_PAGE_TYPE_VMSA`` creates VMSA pages as part of the measured
+initial image.  A request must contain exactly one 4 KiB VMSA page, but the
+command may be used multiple times.  Creating a VMSA page does not associate it
+with a vCPU; use ``KVM_SEV_SNP_SET_VCPU_STATE`` on the intended vCPU file
+descriptor before launch finish to make that association.  KVM treats the
+VMSA contents as guest-owned data, but requires VMPL 0 and a ``sev_features``
+value that matches the VM's configured VMSA features.
+
+20. KVM_SEV_SNP_GET_VCPU_STATE / KVM_SEV_SNP_SET_VCPU_STATE
+------------------------------------------------------------
+
+These commands get or set the VMSA and GHCB addresses for the vCPU on whose
+file descriptor the command is issued.  Unlike the other SEV commands,
+userspace must issue KVM_MEMORY_ENCRYPT_OP on a vCPU file descriptor.  The
+capability is reported as ``KVM_CAP_SNP_VCPU_STATE``.
+
+Parameters (in/out): struct kvm_sev_snp_vcpu_state
+
+Returns: 0 on success, -negative on error
+
+::
+
+        #define KVM_SEV_SNP_VCPU_STATE_VMSA_VALID        _BITULL(0)
+        #define KVM_SEV_SNP_VCPU_STATE_GHCB_VALID        _BITULL(1)
+
+        struct kvm_sev_snp_vcpu_state {
+                __u64 valid_fields;
+                __u64 vmsa_gpa;
+                __u64 ghcb_gpa;
+                __u64 pad[5];           /* Must be zero */
+        };
+
+``KVM_SEV_SNP_GET_VCPU_STATE`` returns the current addresses and sets the
+corresponding bit in ``valid_fields`` for each valid address.
+
+``KVM_SEV_SNP_SET_VCPU_STATE`` sets addresses whose validity bits are present
+and invalidates addresses whose bits are absent.  The command must be issued
+after launch start and before KVM_SEV_SNP_LAUNCH_FINISH, and the VM must have
+enabled ``KVM_CAP_SNP_DIRECT_VMSA``.  A valid VMSA GPA must be backed by
+guest_memfd and populated.  The GPA must be 4-KiB aligned.  A valid GHCB
+address is copied without inspecting its backing page.  Nonzero reserved
+fields or unknown validity bits are rejected.
+
+``KVM_CAP_SNP_DIRECT_VMSA`` is a VM-scoped capability that selects direct-VMSA
+mode.  Userspace enables it with ``KVM_ENABLE_CAP`` on an SNP VM before
+creating any vCPUs.  ``flags`` and all elements of ``args`` must be zero.
+Enabling the capability on a non-SNP VM or after creating a vCPU is rejected.
+
+In direct-VMSA mode, KVM does not allocate a KVM-owned VMSA when a vCPU is
+created and does not generate or measure one at launch finish.  All launch
+VMSAs are owned and supplied by userspace.  Valid VMSAs selected with
+``KVM_SEV_SNP_SET_VCPU_STATE`` are preserved, while vCPUs without a valid VMSA
+have no runnable VMSA until the guest uses SNP AP creation to supply one.  If
+the capability is not enabled, launch finish retains the legacy behavior of
+generating and measuring a KVM-owned VMSA for every vCPU.  VMSAs that were
+measured but not selected remain ordinary valid pages in the initial image.
+
+Direct VMSAs make the launch measurement independent of KVM's selected VMSA
+GPA and of the configured vCPU count.  This gives VMMs a stable launch
+measurement across hypervisors.
+
+21. KVM_SEV_SNP_LAUNCH_FINISH
 -----------------------------
 
 After completion of the SNP guest launch flow, the KVM_SEV_SNP_LAUNCH_FINISH
@@ -572,7 +634,7 @@ Returns: 0 on success, -negative on error
 See SNP_LAUNCH_FINISH in the SEV-SNP specification [snp-fw-abi]_ for further
 details on the input parameters in ``struct kvm_sev_snp_launch_finish``.
 
-21. KVM_SEV_SNP_ENABLE_REQ_CERTS
+22. KVM_SEV_SNP_ENABLE_REQ_CERTS
 --------------------------------
 
 The KVM_SEV_SNP_ENABLE_REQ_CERTS command will configure KVM to exit to
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH v2 2/8] KVM: SVM: Implement GET_AP_APIC_IDS NAE event
  2026-09-08 10:33 [PATCH v2 0/8] KVM: SVM: Support direct setting of VMSA for SEV-SNP guests Jörg Rödel
  2026-09-08 10:33 ` [PATCH v2 1/8] KVM: SEV: Document SNP direct VMSA userspace ABI Jörg Rödel
@ 2026-09-08 10:33 ` Jörg Rödel
  2026-09-08 10:51   ` sashiko-bot
  2026-09-08 10:33 ` [PATCH v2 3/8] KVM: SVM: Hold SRCU while reloading guest-owned VMSAs Jörg Rödel
                   ` (5 subsequent siblings)
  7 siblings, 1 reply; 12+ messages in thread
From: Jörg Rödel @ 2026-09-08 10:33 UTC (permalink / raw)
  To: Paolo Bonzini, Sean Christopherson
  Cc: Michael Roth, Liam Merwick, Vishal Annapurve, Ninad Naik,
	Joerg Roedel, Tom Lendacky, James Bottomley, kvm, linux-doc,
	linux-kernel, linux-kselftest, coconut-svsm

From: Joerg Roedel <joerg.roedel@amd.com>

Advertise and handle the GHCB APIC ID list feature so that SNP guests can
discover the vCPUs for which they need to create VMSAs without relying on
INIT-SIPI.

Return the required page count for undersized buffers.  For sufficiently
large requests, validate the GPA range and use KVM's SRCU-protected guest
write path to copy an exact snapshot of the online vCPU APIC IDs.

Assisted-by: LLM
Signed-off-by: Joerg Roedel <joerg.roedel@amd.com>
---
 arch/x86/include/asm/sev-common.h |  1 +
 arch/x86/include/uapi/asm/svm.h   |  1 +
 arch/x86/kvm/svm/sev.c            | 67 ++++++++++++++++++++++++++++++-
 3 files changed, 67 insertions(+), 2 deletions(-)

diff --git a/arch/x86/include/asm/sev-common.h b/arch/x86/include/asm/sev-common.h
index 01a6e4dbe423..c041f85dfd5c 100644
--- a/arch/x86/include/asm/sev-common.h
+++ b/arch/x86/include/asm/sev-common.h
@@ -136,6 +136,7 @@ enum psc_op {
 
 #define GHCB_HV_FT_SNP			BIT_ULL(0)
 #define GHCB_HV_FT_SNP_AP_CREATION	BIT_ULL(1)
+#define GHCB_HV_FT_APIC_ID_LIST		BIT_ULL(4)
 #define GHCB_HV_FT_SNP_MULTI_VMPL	BIT_ULL(5)
 
 /*
diff --git a/arch/x86/include/uapi/asm/svm.h b/arch/x86/include/uapi/asm/svm.h
index 010a45c9f614..05e5e31e23e1 100644
--- a/arch/x86/include/uapi/asm/svm.h
+++ b/arch/x86/include/uapi/asm/svm.h
@@ -117,6 +117,7 @@
 #define SVM_VMGEXIT_AP_CREATE_ON_INIT		0
 #define SVM_VMGEXIT_AP_CREATE			1
 #define SVM_VMGEXIT_AP_DESTROY			2
+#define SVM_VMGEXIT_GET_APIC_IDS		0x80000017ull
 #define SVM_VMGEXIT_SNP_RUN_VMPL		0x80000018ull
 #define SVM_VMGEXIT_SAVIC			0x8000001aull
 #define SVM_VMGEXIT_SAVIC_REGISTER_GPA		0
diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c
index 5705723f1f41..c16eca9983f3 100644
--- a/arch/x86/kvm/svm/sev.c
+++ b/arch/x86/kvm/svm/sev.c
@@ -40,7 +40,9 @@
 #define GHCB_VERSION_MAX	2ULL
 #define GHCB_VERSION_MIN	1ULL
 
-#define GHCB_HV_FT_SUPPORTED	(GHCB_HV_FT_SNP | GHCB_HV_FT_SNP_AP_CREATION)
+#define GHCB_HV_FT_SUPPORTED	(GHCB_HV_FT_SNP | \
+				 GHCB_HV_FT_SNP_AP_CREATION | \
+				 GHCB_HV_FT_APIC_ID_LIST)
 
 /*
  * The GHCB spec essentially states that all non-zero error codes other than
@@ -3493,7 +3495,8 @@ static bool sev_es_are_required_ghcb_fields_valid(struct vcpu_svm *svm)
 	case SVM_VMGEXIT_AP_CREATION:
 		return kvm_ghcb_rax_is_valid(svm) ||
 		       lower_32_bits(control->exit_info_1) == SVM_VMGEXIT_AP_DESTROY;
-		break;
+	case SVM_VMGEXIT_GET_APIC_IDS:
+		return kvm_ghcb_rax_is_valid(svm);
 	case SVM_VMGEXIT_MMIO_READ:
 	case SVM_VMGEXIT_MMIO_WRITE:
 	case SVM_VMGEXIT_PSC:
@@ -4215,6 +4218,63 @@ static int sev_snp_ap_creation(struct vcpu_svm *svm)
 	return 0;
 }
 
+struct sev_apic_id_desc {
+	u32 num_entries;
+	u32 apic_ids[];
+};
+
+static int sev_snp_get_apic_ids(struct vcpu_svm *svm)
+{
+	struct kvm_vcpu *vcpu = &svm->vcpu;
+	struct kvm *kvm = vcpu->kvm;
+	struct sev_apic_id_desc *desc;
+	unsigned int nr_vcpus, size;
+	unsigned int i;
+	gpa_t gpa, end_gpa;
+	u64 pages;
+
+	nr_vcpus = atomic_read(&kvm->online_vcpus);
+	size = sizeof(*desc) + (nr_vcpus * sizeof(desc->apic_ids[0]));
+
+	pages = vcpu->arch.regs[VCPU_REGS_RAX];
+	if (pages < PFN_UP(size)) {
+		vcpu->arch.regs[VCPU_REGS_RAX] = PFN_UP(size);
+		return 1;
+	}
+
+	gpa = svm->vmcb->control.exit_info_1;
+	if (!PAGE_ALIGNED(gpa) ||
+	    check_add_overflow(gpa, size - 1, &end_gpa) ||
+	    !page_address_valid(vcpu, gpa) ||
+	    !page_address_valid(vcpu, end_gpa & PAGE_MASK))
+		goto invalid_buffer;
+
+	desc = kvzalloc(size, GFP_KERNEL_ACCOUNT);
+	if (!desc)
+		return -ENOMEM;
+
+	desc->num_entries = nr_vcpus;
+	for (i = 0; i < nr_vcpus; i++) {
+		struct kvm_vcpu *listed_vcpu = kvm_get_vcpu(kvm, i);
+
+		if (WARN_ON_ONCE(!listed_vcpu))
+			goto invalid_buffer_free;
+		desc->apic_ids[i] = listed_vcpu->vcpu_id;
+	}
+
+	if (kvm_write_guest(kvm, gpa, desc, size))
+		goto invalid_buffer_free;
+
+	kvfree(desc);
+	return 1;
+
+invalid_buffer_free:
+	kvfree(desc);
+invalid_buffer:
+	svm_vmgexit_bad_input(svm, GHCB_ERR_INVALID_INPUT);
+	return 1;
+}
+
 static int snp_handle_guest_req(struct vcpu_svm *svm, gpa_t req_gpa, gpa_t resp_gpa)
 {
 	struct sev_data_snp_guest_request data = {0};
@@ -4494,6 +4554,7 @@ static bool is_snp_only_vmgexit(u64 exit_code)
 {
 	switch (exit_code) {
 	case SVM_VMGEXIT_AP_CREATION:
+	case SVM_VMGEXIT_GET_APIC_IDS:
 	case SVM_VMGEXIT_GUEST_REQUEST:
 	case SVM_VMGEXIT_EXT_GUEST_REQUEST:
 	case SVM_VMGEXIT_PSC:
@@ -4665,6 +4726,8 @@ int sev_handle_vmgexit(struct kvm_vcpu *vcpu)
 		if (sev_snp_ap_creation(svm))
 			svm_vmgexit_bad_input(svm, GHCB_ERR_INVALID_INPUT);
 		return 1;
+	case SVM_VMGEXIT_GET_APIC_IDS:
+		return sev_snp_get_apic_ids(svm);
 	case SVM_VMGEXIT_GUEST_REQUEST:
 	case SVM_VMGEXIT_EXT_GUEST_REQUEST:
 		if (!PAGE_ALIGNED(control->exit_info_1) ||
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH v2 3/8] KVM: SVM: Hold SRCU while reloading guest-owned VMSAs
  2026-09-08 10:33 [PATCH v2 0/8] KVM: SVM: Support direct setting of VMSA for SEV-SNP guests Jörg Rödel
  2026-09-08 10:33 ` [PATCH v2 1/8] KVM: SEV: Document SNP direct VMSA userspace ABI Jörg Rödel
  2026-09-08 10:33 ` [PATCH v2 2/8] KVM: SVM: Implement GET_AP_APIC_IDS NAE event Jörg Rödel
@ 2026-09-08 10:33 ` Jörg Rödel
  2026-09-08 10:33 ` [PATCH v2 4/8] KVM: SEV: Add direct VMSA capability Jörg Rödel
                   ` (4 subsequent siblings)
  7 siblings, 0 replies; 12+ messages in thread
From: Jörg Rödel @ 2026-09-08 10:33 UTC (permalink / raw)
  To: Paolo Bonzini, Sean Christopherson
  Cc: Michael Roth, Liam Merwick, Vishal Annapurve, Ninad Naik,
	Joerg Roedel, Tom Lendacky, James Bottomley, kvm, linux-doc,
	linux-kernel, linux-kselftest, coconut-svsm

From: Joerg Roedel <joerg.roedel@amd.com>

Take a dedicated KVM SRCU read lock around the guest-owned VMSA memslot
and guest_memfd lookup.  This makes the reload path self-contained and
prevents it from racing a userspace memslot replacement.

Keep the lock through VMCB installation, and release it on all lookup
failure paths.

Assisted-by: LLM
Signed-off-by: Joerg Roedel <joerg.roedel@amd.com>
---
 arch/x86/kvm/svm/sev.c | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c
index c16eca9983f3..ee1bf9b4cc53 100644
--- a/arch/x86/kvm/svm/sev.c
+++ b/arch/x86/kvm/svm/sev.c
@@ -4037,6 +4037,7 @@ static void __sev_snp_reload_vmsa(struct kvm_vcpu *vcpu, gpa_t gpa)
 	unsigned long mmu_seq;
 	struct page *page;
 	kvm_pfn_t pfn;
+	int idx;
 
 	lockdep_assert_held(&svm->sev_es.snp_vmsa_mutex);
 
@@ -4068,9 +4069,10 @@ static void __sev_snp_reload_vmsa(struct kvm_vcpu *vcpu, gpa_t gpa)
 	if (!VALID_PAGE(gpa))
 		return;
 
-	slot = gfn_to_memslot(vcpu->kvm, gfn);
+	idx = srcu_read_lock(&kvm->srcu);
+	slot = gfn_to_memslot(kvm, gfn);
 	if (!slot)
-		return;
+		goto out_unlock;
 
 	mmu_seq = kvm->mmu_invalidate_seq;
 	smp_rmb();
@@ -4079,8 +4081,8 @@ static void __sev_snp_reload_vmsa(struct kvm_vcpu *vcpu, gpa_t gpa)
 	 * The new VMSA will be private memory guest memory, so retrieve the
 	 * PFN from the gmem backend.
 	 */
-	if (kvm_gmem_get_pfn(vcpu->kvm, slot, gfn, &pfn, &page, NULL))
-		return;
+	if (kvm_gmem_get_pfn(kvm, slot, gfn, &pfn, &page, NULL))
+		goto out_unlock;
 
 	read_lock(&kvm->mmu_lock);
 	/*
@@ -4097,6 +4099,8 @@ static void __sev_snp_reload_vmsa(struct kvm_vcpu *vcpu, gpa_t gpa)
 	read_unlock(&kvm->mmu_lock);
 
 	kvm_release_page_clean(page);
+out_unlock:
+	srcu_read_unlock(&kvm->srcu, idx);
 }
 
 /*
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH v2 4/8] KVM: SEV: Add direct VMSA capability
  2026-09-08 10:33 [PATCH v2 0/8] KVM: SVM: Support direct setting of VMSA for SEV-SNP guests Jörg Rödel
                   ` (2 preceding siblings ...)
  2026-09-08 10:33 ` [PATCH v2 3/8] KVM: SVM: Hold SRCU while reloading guest-owned VMSAs Jörg Rödel
@ 2026-09-08 10:33 ` Jörg Rödel
  2026-09-08 10:53   ` sashiko-bot
  2026-09-08 10:33 ` [PATCH v2 5/8] KVM: SEV: Allow VMSA pages in SNP launch updates Jörg Rödel
                   ` (3 subsequent siblings)
  7 siblings, 1 reply; 12+ messages in thread
From: Jörg Rödel @ 2026-09-08 10:33 UTC (permalink / raw)
  To: Paolo Bonzini, Sean Christopherson
  Cc: Michael Roth, Liam Merwick, Vishal Annapurve, Ninad Naik,
	Joerg Roedel, Tom Lendacky, James Bottomley, kvm, linux-doc,
	linux-kernel, linux-kselftest, coconut-svsm

From: Joerg Roedel <joerg.roedel@amd.com>

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


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH v2 5/8] KVM: SEV: Allow VMSA pages in SNP launch updates
  2026-09-08 10:33 [PATCH v2 0/8] KVM: SVM: Support direct setting of VMSA for SEV-SNP guests Jörg Rödel
                   ` (3 preceding siblings ...)
  2026-09-08 10:33 ` [PATCH v2 4/8] KVM: SEV: Add direct VMSA capability Jörg Rödel
@ 2026-09-08 10:33 ` Jörg Rödel
  2026-09-08 10:33 ` [PATCH v2 6/8] KVM: SEV: Add SNP vCPU state get and set commands Jörg Rödel
                   ` (2 subsequent siblings)
  7 siblings, 0 replies; 12+ messages in thread
From: Jörg Rödel @ 2026-09-08 10:33 UTC (permalink / raw)
  To: Paolo Bonzini, Sean Christopherson
  Cc: Michael Roth, Liam Merwick, Vishal Annapurve, Ninad Naik,
	Joerg Roedel, Tom Lendacky, James Bottomley, kvm, linux-doc,
	linux-kernel, linux-kselftest, coconut-svsm

From: Joerg Roedel <joerg.roedel@amd.com>

Pass the firmware VMSA page type through KVM_SEV_SNP_LAUNCH_UPDATE.
Only accept VMSA pages when userspace has enabled
KVM_CAP_SNP_DIRECT_VMSA.
Require each request to describe exactly one 4-KiB VMSA page.  Allow
repeated requests and keep VMSA creation independent of association with a
vCPU.

The VMSA's VMPL and SEV features define its execution context.  They must
agree with KVM's VM-wide configuration.  Before passing a VMSA to firmware,
require VMPL 0.  Require sev_features to exactly match the VM's configured
VMSA features.  Treat the remaining contents as guest-owned data.

Assisted-by: LLM
Signed-off-by: Joerg Roedel <joerg.roedel@amd.com>
---
 arch/x86/include/uapi/asm/kvm.h |  1 +
 arch/x86/kvm/svm/sev.c          | 16 ++++++++++++++++
 2 files changed, 17 insertions(+)

diff --git a/arch/x86/include/uapi/asm/kvm.h b/arch/x86/include/uapi/asm/kvm.h
index 1585ec804066..69dcd044583f 100644
--- a/arch/x86/include/uapi/asm/kvm.h
+++ b/arch/x86/include/uapi/asm/kvm.h
@@ -887,6 +887,7 @@ struct kvm_sev_snp_launch_start {
 /* Kept in sync with firmware values for simplicity. */
 #define KVM_SEV_PAGE_TYPE_INVALID		0x0
 #define KVM_SEV_SNP_PAGE_TYPE_NORMAL		0x1
+#define KVM_SEV_SNP_PAGE_TYPE_VMSA		0x2
 #define KVM_SEV_SNP_PAGE_TYPE_ZERO		0x3
 #define KVM_SEV_SNP_PAGE_TYPE_UNMEASURED	0x4
 #define KVM_SEV_SNP_PAGE_TYPE_SECRETS		0x5
diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c
index 5a282aff04a8..7a9ef1bc54e9 100644
--- a/arch/x86/kvm/svm/sev.c
+++ b/arch/x86/kvm/svm/sev.c
@@ -2346,6 +2346,7 @@ struct sev_gmem_populate_args {
 	__u8 type;
 	int sev_fd;
 	int fw_error;
+	bool vmsa_invalid;
 };
 
 static int sev_gmem_post_populate(struct kvm *kvm, gfn_t gfn, kvm_pfn_t pfn,
@@ -2369,11 +2370,20 @@ static int sev_gmem_post_populate(struct kvm *kvm, gfn_t gfn, kvm_pfn_t pfn,
 	if (src_page) {
 		void *src_vaddr = kmap_local_page(src_page);
 		void *dst_vaddr = kmap_local_pfn(pfn);
+		struct sev_es_save_area *vmsa = dst_vaddr;
 
 		memcpy(dst_vaddr, src_vaddr, PAGE_SIZE);
+		if (sev_populate_args->type == KVM_SEV_SNP_PAGE_TYPE_VMSA &&
+		    (vmsa->vmpl || vmsa->sev_features != sev->vmsa_features)) {
+			sev_populate_args->vmsa_invalid = true;
+			ret = -EINVAL;
+		}
 
 		kunmap_local(dst_vaddr);
 		kunmap_local(src_vaddr);
+
+		if (ret)
+			goto out;
 	}
 
 	ret = rmp_make_private(pfn, gfn << PAGE_SHIFT, PG_LEVEL_4K,
@@ -2439,7 +2449,10 @@ static int snp_launch_update(struct kvm *kvm, struct kvm_sev_cmd *argp)
 		 params.gfn_start, params.len, params.type, params.flags);
 
 	if (!params.len || !PAGE_ALIGNED(params.len) || params.flags ||
+	    (params.type == KVM_SEV_SNP_PAGE_TYPE_VMSA &&
+	     (!sev->snp_direct_vmsa || params.len != PAGE_SIZE)) ||
 	    (params.type != KVM_SEV_SNP_PAGE_TYPE_NORMAL &&
+	     params.type != KVM_SEV_SNP_PAGE_TYPE_VMSA &&
 	     params.type != KVM_SEV_SNP_PAGE_TYPE_ZERO &&
 	     params.type != KVM_SEV_SNP_PAGE_TYPE_UNMEASURED &&
 	     params.type != KVM_SEV_SNP_PAGE_TYPE_SECRETS &&
@@ -2487,6 +2500,9 @@ static int snp_launch_update(struct kvm *kvm, struct kvm_sev_cmd *argp)
 				  params.type == KVM_SEV_SNP_PAGE_TYPE_CPUID,
 				  sev_gmem_post_populate, &sev_populate_args);
 	if (count < 0) {
+		if (sev_populate_args.vmsa_invalid)
+			return -EINVAL;
+
 		argp->error = sev_populate_args.fw_error;
 		pr_debug("%s: kvm_gmem_populate failed, ret %ld (fw_error %d)\n",
 			 __func__, count, argp->error);
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH v2 6/8] KVM: SEV: Add SNP vCPU state get and set commands
  2026-09-08 10:33 [PATCH v2 0/8] KVM: SVM: Support direct setting of VMSA for SEV-SNP guests Jörg Rödel
                   ` (4 preceding siblings ...)
  2026-09-08 10:33 ` [PATCH v2 5/8] KVM: SEV: Allow VMSA pages in SNP launch updates Jörg Rödel
@ 2026-09-08 10:33 ` Jörg Rödel
  2026-09-08 10:49   ` sashiko-bot
  2026-09-08 10:33 ` [PATCH v2 7/8] KVM: selftests: Test the SNP APIC-ID-list GHCB request Jörg Rödel
  2026-09-08 10:33 ` [PATCH v2 8/8] KVM: selftests: Test SNP vCPU state and direct VMSA launch Jörg Rödel
  7 siblings, 1 reply; 12+ messages in thread
From: Jörg Rödel @ 2026-09-08 10:33 UTC (permalink / raw)
  To: Paolo Bonzini, Sean Christopherson
  Cc: Michael Roth, Liam Merwick, Vishal Annapurve, Ninad Naik,
	Joerg Roedel, Tom Lendacky, James Bottomley, kvm, linux-doc,
	linux-kernel, linux-kselftest, coconut-svsm

From: Joerg Roedel <joerg.roedel@amd.com>

Add KVM_SEV_SNP_GET_VCPU_STATE and KVM_SEV_SNP_SET_VCPU_STATE as
vCPU-scoped KVM_MEMORY_ENCRYPT_OP commands and advertise
KVM_CAP_SNP_VCPU_STATE.  The shared, extensible argument reports the VMSA
and GHCB GPAs and has validity bits for both addresses.

For SET, validate the argument, launch ordering, VMSA alignment, and that a
valid VMSA is backed by guest_memfd.  Install or invalidate the VMSA and
GHCB as requested.  GET returns the currently tracked guest-owned VMSA GPA
and the GHCB address.

Require KVM_CAP_SNP_DIRECT_VMSA for SET.  The VM-scoped capability fixes
the launch model before vCPU creation; the per-vCPU command only selects
the guest-owned VMSA and GHCB addresses for that vCPU.

Route the commands through the locked vCPU ioctl hook.  The vCPU mutex
serializes the commands against launch finalization, which locks all vCPUs
before inspecting their state.

Assisted-by: LLM
Signed-off-by: Joerg Roedel <joerg.roedel@amd.com>
---
 arch/x86/include/uapi/asm/kvm.h |  12 +++
 arch/x86/kvm/svm/sev.c          | 164 ++++++++++++++++++++++++++++++++
 arch/x86/kvm/svm/svm.c          |   1 +
 arch/x86/kvm/svm/svm.h          |   1 +
 arch/x86/kvm/x86.c              |   1 +
 include/uapi/linux/kvm.h        |   1 +
 6 files changed, 180 insertions(+)

diff --git a/arch/x86/include/uapi/asm/kvm.h b/arch/x86/include/uapi/asm/kvm.h
index 69dcd044583f..5ae041e617b8 100644
--- a/arch/x86/include/uapi/asm/kvm.h
+++ b/arch/x86/include/uapi/asm/kvm.h
@@ -748,6 +748,8 @@ enum sev_cmd_id {
 	KVM_SEV_SNP_LAUNCH_UPDATE,
 	KVM_SEV_SNP_LAUNCH_FINISH,
 	KVM_SEV_SNP_ENABLE_REQ_CERTS,
+	KVM_SEV_SNP_GET_VCPU_STATE,
+	KVM_SEV_SNP_SET_VCPU_STATE,
 
 	KVM_SEV_NR_MAX,
 };
@@ -904,6 +906,16 @@ struct kvm_sev_snp_launch_update {
 	__u64 pad2[4];
 };
 
+#define KVM_SEV_SNP_VCPU_STATE_VMSA_VALID	_BITULL(0)
+#define KVM_SEV_SNP_VCPU_STATE_GHCB_VALID	_BITULL(1)
+
+struct kvm_sev_snp_vcpu_state {
+	__u64 valid_fields;
+	__u64 vmsa_gpa;
+	__u64 ghcb_gpa;
+	__u64 pad[5];
+};
+
 #define KVM_SEV_SNP_ID_BLOCK_SIZE	96
 #define KVM_SEV_SNP_ID_AUTH_SIZE	4096
 #define KVM_SEV_SNP_FINISH_DATA_SIZE	32
diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c
index 7a9ef1bc54e9..3e0727e251a9 100644
--- a/arch/x86/kvm/svm/sev.c
+++ b/arch/x86/kvm/svm/sev.c
@@ -2520,6 +2520,8 @@ static int snp_launch_update(struct kvm *kvm, struct kvm_sev_cmd *argp)
 	return 0;
 }
 
+static int sev_snp_install_guest_vmsa(struct vcpu_svm *svm, gpa_t gpa);
+
 static int snp_launch_update_vmsa(struct kvm *kvm, struct kvm_sev_cmd *argp)
 {
 	struct kvm_sev_info *sev = to_kvm_sev_info(kvm);
@@ -2585,6 +2587,89 @@ static int snp_launch_update_vmsa(struct kvm *kvm, struct kvm_sev_cmd *argp)
 	return ret;
 }
 
+static int snp_get_vcpu_state(struct kvm_vcpu *vcpu,
+			      struct kvm_sev_cmd *argp)
+{
+	struct vcpu_svm *svm = to_svm(vcpu);
+	struct kvm *kvm = vcpu->kvm;
+	struct kvm_sev_snp_vcpu_state state = {};
+
+	if (!is_sev_snp_guest(vcpu))
+		return -ENOTTY;
+	if (!to_kvm_sev_info(kvm)->snp_context)
+		return -EINVAL;
+
+	guard(mutex)(&svm->sev_es.snp_vmsa_mutex);
+
+	if (VALID_PAGE(svm->sev_es.snp_guest_vmsa_gpa) &&
+	    VALID_PAGE(svm->vmcb->control.vmsa_pa)) {
+		state.vmsa_gpa = svm->sev_es.snp_guest_vmsa_gpa;
+		state.valid_fields |= KVM_SEV_SNP_VCPU_STATE_VMSA_VALID;
+	}
+
+	if (VALID_PAGE(svm->vmcb->control.ghcb_gpa)) {
+		state.ghcb_gpa = svm->vmcb->control.ghcb_gpa;
+		state.valid_fields |= KVM_SEV_SNP_VCPU_STATE_GHCB_VALID;
+	}
+
+	if (copy_to_user(u64_to_user_ptr(argp->data), &state, sizeof(state)))
+		return -EFAULT;
+
+	return 0;
+}
+
+static int snp_set_vcpu_state(struct kvm_vcpu *vcpu,
+			      struct kvm_sev_cmd *argp)
+{
+	struct vcpu_svm *svm = to_svm(vcpu);
+	struct kvm *kvm = vcpu->kvm;
+	struct kvm_sev_info *sev = to_kvm_sev_info(kvm);
+	struct kvm_sev_snp_vcpu_state state;
+	int ret;
+
+	if (!is_sev_snp_guest(vcpu))
+		return -ENOTTY;
+	if (!sev->snp_direct_vmsa)
+		return -EINVAL;
+	if (!sev->snp_context || kvm->arch.pre_fault_allowed)
+		return -EINVAL;
+
+	if (copy_from_user(&state, u64_to_user_ptr(argp->data), sizeof(state)))
+		return -EFAULT;
+
+	if (memchr_inv(state.pad, 0, sizeof(state.pad)) ||
+	    state.valid_fields & ~(KVM_SEV_SNP_VCPU_STATE_VMSA_VALID |
+				   KVM_SEV_SNP_VCPU_STATE_GHCB_VALID))
+		return -EINVAL;
+
+	if (state.valid_fields & KVM_SEV_SNP_VCPU_STATE_VMSA_VALID) {
+		if (!PAGE_ALIGNED(state.vmsa_gpa) ||
+		    !page_address_valid(vcpu, state.vmsa_gpa) ||
+		    IS_ALIGNED(state.vmsa_gpa, PMD_SIZE))
+			return -EINVAL;
+	}
+
+	guard(mutex)(&svm->sev_es.snp_vmsa_mutex);
+
+	if (state.valid_fields & KVM_SEV_SNP_VCPU_STATE_VMSA_VALID) {
+		ret = sev_snp_install_guest_vmsa(svm, state.vmsa_gpa);
+		if (ret)
+			return ret;
+	} else {
+		svm->sev_es.snp_has_guest_vmsa = true;
+		svm->sev_es.snp_guest_vmsa_gpa = INVALID_PAGE;
+		svm->vmcb->control.vmsa_pa = INVALID_PAGE;
+	}
+
+	if (state.valid_fields & KVM_SEV_SNP_VCPU_STATE_GHCB_VALID)
+		svm->vmcb->control.ghcb_gpa = state.ghcb_gpa;
+	else
+		svm->vmcb->control.ghcb_gpa = INVALID_PAGE;
+
+	vmcb_mark_all_dirty(svm->vmcb);
+	return 0;
+}
+
 static int snp_launch_finish(struct kvm *kvm, struct kvm_sev_cmd *argp)
 {
 	struct kvm_sev_info *sev = to_kvm_sev_info(kvm);
@@ -2781,6 +2866,35 @@ int sev_mem_enc_ioctl(struct kvm *kvm, void __user *argp)
 	return r;
 }
 
+int sev_vcpu_mem_enc_ioctl(struct kvm_vcpu *vcpu, void __user *argp)
+{
+	struct kvm_sev_cmd sev_cmd;
+	int ret;
+
+	if (!sev_enabled)
+		return -ENOTTY;
+	if (!argp)
+		return -EINVAL;
+	if (copy_from_user(&sev_cmd, argp, sizeof(sev_cmd)))
+		return -EFAULT;
+
+	switch (sev_cmd.id) {
+	case KVM_SEV_SNP_GET_VCPU_STATE:
+		ret = snp_get_vcpu_state(vcpu, &sev_cmd);
+		break;
+	case KVM_SEV_SNP_SET_VCPU_STATE:
+		ret = snp_set_vcpu_state(vcpu, &sev_cmd);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (copy_to_user(argp, &sev_cmd, sizeof(sev_cmd)))
+		return -EFAULT;
+
+	return ret;
+}
+
 int sev_mem_enc_register_region(struct kvm *kvm,
 				struct kvm_enc_region *range)
 {
@@ -4055,6 +4169,56 @@ static int snp_begin_psc(struct vcpu_svm *svm)
 	return snp_do_psc(svm);
 }
 
+/*
+ * Install a guest-owned VMSA.  The caller must serialize against AP creation
+ * and destruction with snp_vmsa_mutex.
+ */
+static int sev_snp_install_guest_vmsa(struct vcpu_svm *svm, gpa_t gpa)
+{
+	struct kvm *kvm = svm->vcpu.kvm;
+	struct kvm_memory_slot *slot;
+	unsigned long mmu_seq;
+	struct page *page;
+	kvm_pfn_t pfn;
+	gfn_t gfn;
+	int idx;
+	int ret;
+
+	lockdep_assert_held(&svm->sev_es.snp_vmsa_mutex);
+
+	gfn = gpa_to_gfn(gpa);
+	idx = srcu_read_lock(&kvm->srcu);
+	slot = gfn_to_memslot(kvm, gfn);
+	if (!slot) {
+		ret = -EINVAL;
+		goto out_unlock;
+	}
+
+	mmu_seq = kvm->mmu_invalidate_seq;
+	/* Pairs with the smp_wmb() in kvm_mmu_invalidate_end(). */
+	smp_rmb();
+
+	/* Guest-owned VMSAs are backed by guest_memfd private memory. */
+	ret = kvm_gmem_get_pfn(kvm, slot, gfn, &pfn, &page, NULL);
+	if (ret)
+		goto out_unlock;
+
+	read_lock(&kvm->mmu_lock);
+	if (mmu_invalidate_retry_gfn(kvm, mmu_seq, gfn)) {
+		ret = -EAGAIN;
+	} else {
+		svm->sev_es.snp_has_guest_vmsa = true;
+		WRITE_ONCE(svm->sev_es.snp_guest_vmsa_gpa, gpa);
+		svm->vmcb->control.vmsa_pa = pfn_to_hpa(pfn);
+	}
+	read_unlock(&kvm->mmu_lock);
+
+	kvm_release_page_clean(page);
+out_unlock:
+	srcu_read_unlock(&kvm->srcu, idx);
+	return ret;
+}
+
 static void __sev_snp_reload_vmsa(struct kvm_vcpu *vcpu, gpa_t gpa)
 {
 	struct vcpu_svm *svm = to_svm(vcpu);
diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
index 98c5cffe2c22..378d94409134 100644
--- a/arch/x86/kvm/svm/svm.c
+++ b/arch/x86/kvm/svm/svm.c
@@ -5466,6 +5466,7 @@ struct kvm_x86_ops svm_x86_ops __initdata = {
 	.vcpu_needs_initialization = sev_vcpu_needs_initialization,
 	.dev_get_attr = sev_dev_get_attr,
 	.mem_enc_ioctl = sev_mem_enc_ioctl,
+	.vcpu_mem_enc_ioctl = sev_vcpu_mem_enc_ioctl,
 	.mem_enc_register_region = sev_mem_enc_register_region,
 	.mem_enc_unregister_region = sev_mem_enc_unregister_region,
 	.guest_memory_reclaimed = sev_guest_memory_reclaimed,
diff --git a/arch/x86/kvm/svm/svm.h b/arch/x86/kvm/svm/svm.h
index 881d662aaab2..c920c797b7ec 100644
--- a/arch/x86/kvm/svm/svm.h
+++ b/arch/x86/kvm/svm/svm.h
@@ -983,6 +983,7 @@ void sev_es_unmap_ghcb(struct vcpu_svm *svm);
 #ifdef CONFIG_KVM_AMD_SEV
 bool sev_vcpu_needs_initialization(struct kvm_vcpu *vcpu);
 int sev_mem_enc_ioctl(struct kvm *kvm, void __user *argp);
+int sev_vcpu_mem_enc_ioctl(struct kvm_vcpu *vcpu, void __user *argp);
 int sev_mem_enc_register_region(struct kvm *kvm,
 				struct kvm_enc_region *range);
 int sev_mem_enc_unregister_region(struct kvm *kvm,
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index fb4857eca6c6..232507ae504a 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -2404,6 +2404,7 @@ 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_VCPU_STATE:
 	case KVM_CAP_SNP_DIRECT_VMSA:
 		r = !!(kvm_caps.supported_vm_types & BIT(KVM_X86_SNP_VM));
 		break;
diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
index 283e881b2634..8c6765f78c1c 100644
--- a/include/uapi/linux/kvm.h
+++ b/include/uapi/linux/kvm.h
@@ -1000,6 +1000,7 @@ struct kvm_enable_cap {
 #define KVM_CAP_PPC_COMPAT_CAPS 250
 #define KVM_CAP_ARM_PMU_V3_STRICT 251
 #define KVM_CAP_SNP_DIRECT_VMSA 252
+#define KVM_CAP_SNP_VCPU_STATE 253
 
 struct kvm_irq_routing_irqchip {
 	__u32 irqchip;
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH v2 7/8] KVM: selftests: Test the SNP APIC-ID-list GHCB request
  2026-09-08 10:33 [PATCH v2 0/8] KVM: SVM: Support direct setting of VMSA for SEV-SNP guests Jörg Rödel
                   ` (5 preceding siblings ...)
  2026-09-08 10:33 ` [PATCH v2 6/8] KVM: SEV: Add SNP vCPU state get and set commands Jörg Rödel
@ 2026-09-08 10:33 ` Jörg Rödel
  2026-09-08 10:33 ` [PATCH v2 8/8] KVM: selftests: Test SNP vCPU state and direct VMSA launch Jörg Rödel
  7 siblings, 0 replies; 12+ messages in thread
From: Jörg Rödel @ 2026-09-08 10:33 UTC (permalink / raw)
  To: Paolo Bonzini, Sean Christopherson
  Cc: Michael Roth, Liam Merwick, Vishal Annapurve, Ninad Naik,
	Joerg Roedel, Tom Lendacky, James Bottomley, kvm, linux-doc,
	linux-kernel, linux-kselftest, coconut-svsm

From: Joerg Roedel <joerg.roedel@amd.com>

Exercise the GET_AP_APIC_IDS NAE event from an SNP guest using a test-local
GHCB encoder.  Verify the exact count and APIC IDs, missing RAX validity,
zero-sized requests, invalid GPAs, and the 1024-vCPU descriptor boundary
when supported.

Assisted-by: LLM
Signed-off-by: Joerg Roedel <joerg.roedel@amd.com>
---
 tools/testing/selftests/kvm/Makefile.kvm      |   1 +
 .../selftests/kvm/x86/sev_snp_apic_id_test.c  | 180 ++++++++++++++++++
 2 files changed, 181 insertions(+)
 create mode 100644 tools/testing/selftests/kvm/x86/sev_snp_apic_id_test.c

diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 96bab7002d39..92eb3dff13ff 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -149,6 +149,7 @@ TEST_GEN_PROGS_x86 += x86/xen_vmcall_test
 TEST_GEN_PROGS_x86 += x86/sev_dbg_test
 TEST_GEN_PROGS_x86 += x86/sev_init2_tests
 TEST_GEN_PROGS_x86 += x86/sev_migrate_tests
+TEST_GEN_PROGS_x86 += x86/sev_snp_apic_id_test
 TEST_GEN_PROGS_x86 += x86/sev_smoke_test
 TEST_GEN_PROGS_x86 += x86/amx_test
 TEST_GEN_PROGS_x86 += x86/max_vcpuid_cap_test
diff --git a/tools/testing/selftests/kvm/x86/sev_snp_apic_id_test.c b/tools/testing/selftests/kvm/x86/sev_snp_apic_id_test.c
new file mode 100644
index 000000000000..8ad9c15091c2
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86/sev_snp_apic_id_test.c
@@ -0,0 +1,180 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <stddef.h>
+#include <stdint.h>
+
+#include "kvm_util.h"
+#include "processor.h"
+#include "sev.h"
+#include "svm_util.h"
+
+#define GHCB_SAVE_RAX_OFFSET		0x1f8
+#define GHCB_SAVE_SW_EXIT_CODE_OFFSET	0x390
+#define GHCB_SAVE_SW_EXIT_INFO_1_OFFSET	0x398
+#define GHCB_SAVE_SW_EXIT_INFO_2_OFFSET	0x3a0
+#define GHCB_SAVE_VALID_BITMAP_OFFSET	0x3f0
+
+#define GHCB_MSR_REG_GPA_REQ		0x012
+#define GHCB_MSR_REG_GPA_RESP		0x013
+#define GHCB_MSR_INFO_MASK		GENMASK_ULL(11, 0)
+
+#define GHCB_HV_RESP_MALFORMED_INPUT	2
+#define GHCB_ERR_MISSING_INPUT		4
+#define GHCB_ERR_INVALID_INPUT		5
+
+struct apic_id_desc {
+	u32 nr_entries;
+	u32 apic_ids[];
+};
+
+struct apic_id_results {
+	u64 missing_info1;
+	u64 missing_info2;
+	u64 zero_info1;
+	u64 zero_info2;
+	u64 zero_rax;
+	u64 invalid_info1;
+	u64 invalid_info2;
+	u64 valid_info1;
+	u64 valid_info2;
+	u32 nr_entries;
+	u32 first_apic_id;
+	u32 last_apic_id;
+};
+
+static void ghcb_set_field(void *ghcb, size_t offset, u64 value, bool valid)
+{
+	u8 *valid_bitmap = ghcb + GHCB_SAVE_VALID_BITMAP_OFFSET;
+
+	*(u64 *)(ghcb + offset) = value;
+	if (valid)
+		valid_bitmap[(offset / sizeof(u64)) / 8] |=
+			BIT((offset / sizeof(u64)) % 8);
+}
+
+static u64 ghcb_get_field(void *ghcb, size_t offset)
+{
+	return *(u64 *)(ghcb + offset);
+}
+
+static void do_get_apic_ids(void *ghcb, gpa_t buffer_gpa, u64 pages,
+			    bool rax_valid)
+{
+	memset(ghcb, 0, PAGE_SIZE);
+	ghcb_set_field(ghcb, GHCB_SAVE_SW_EXIT_CODE_OFFSET,
+		       SVM_VMGEXIT_GET_APIC_IDS, true);
+	ghcb_set_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_1_OFFSET, buffer_gpa, true);
+	ghcb_set_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_2_OFFSET, 0, true);
+	ghcb_set_field(ghcb, GHCB_SAVE_RAX_OFFSET, pages, rax_valid);
+	vmgexit();
+}
+
+static void guest_code(void *ghcb, gpa_t ghcb_gpa, void *list,
+		       gpa_t list_gpa, struct apic_id_results *results,
+		       u64 expected_vcpus)
+{
+	struct apic_id_desc *desc = list;
+	u64 msr;
+
+	wrmsr(MSR_AMD64_SEV_ES_GHCB,
+	      (ghcb_gpa >> PAGE_SHIFT) << PAGE_SHIFT | GHCB_MSR_REG_GPA_REQ);
+	vmgexit();
+	msr = rdmsr(MSR_AMD64_SEV_ES_GHCB);
+	if ((msr & GHCB_MSR_INFO_MASK) != GHCB_MSR_REG_GPA_RESP)
+		goto terminate;
+
+	wrmsr(MSR_AMD64_SEV_ES_GHCB, ghcb_gpa);
+
+	do_get_apic_ids(ghcb, list_gpa, 1, false);
+	results->missing_info1 = ghcb_get_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_1_OFFSET);
+	results->missing_info2 = ghcb_get_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_2_OFFSET);
+
+	do_get_apic_ids(ghcb, list_gpa, 0, true);
+	results->zero_info1 = ghcb_get_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_1_OFFSET);
+	results->zero_info2 = ghcb_get_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_2_OFFSET);
+	results->zero_rax = ghcb_get_field(ghcb, GHCB_SAVE_RAX_OFFSET);
+
+	do_get_apic_ids(ghcb, BIT_ULL(52), 2, true);
+	results->invalid_info1 = ghcb_get_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_1_OFFSET);
+	results->invalid_info2 = ghcb_get_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_2_OFFSET);
+
+	do_get_apic_ids(ghcb, list_gpa, 2, true);
+	results->valid_info1 = ghcb_get_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_1_OFFSET);
+	results->valid_info2 = ghcb_get_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_2_OFFSET);
+	results->nr_entries = desc->nr_entries;
+	results->first_apic_id = desc->apic_ids[0];
+	results->last_apic_id = desc->apic_ids[expected_vcpus - 1];
+
+terminate:
+	wrmsr(MSR_AMD64_SEV_ES_GHCB, GHCB_MSR_TERM_REQ);
+	vmgexit();
+}
+
+static void run_apic_id_test(unsigned int nr_vcpus)
+{
+	struct apic_id_results *results;
+	struct kvm_vcpu *vcpu;
+	struct kvm_vm *vm;
+	gva_t ghcb_gva, list_gva, results_gva;
+	gpa_t ghcb_gpa, list_gpa;
+	unsigned int i;
+
+	kvm_set_files_rlimit(nr_vcpus);
+	vm = vm_sev_create_with_one_vcpu(KVM_X86_SNP_VM, guest_code, &vcpu);
+	for (i = 1; i < nr_vcpus; i++)
+		__vm_vcpu_add(vm, i);
+
+	ghcb_gva = vm_alloc_shared(vm, PAGE_SIZE, KVM_UTIL_MIN_VADDR,
+				   MEM_REGION_TEST_DATA);
+	list_gva = vm_alloc_shared(vm, 2 * PAGE_SIZE, KVM_UTIL_MIN_VADDR,
+				   MEM_REGION_TEST_DATA);
+	results_gva = vm_alloc_shared(vm, PAGE_SIZE, KVM_UTIL_MIN_VADDR,
+				      MEM_REGION_TEST_DATA);
+	ghcb_gpa = addr_gva2gpa(vm, ghcb_gva);
+	list_gpa = addr_gva2gpa(vm, list_gva);
+	results = addr_gva2hva(vm, results_gva);
+
+	vcpu_args_set(vcpu, 6, ghcb_gva, ghcb_gpa, list_gva, list_gpa,
+		      results_gva, nr_vcpus);
+	memset(addr_gva2hva(vm, ghcb_gva), 0, PAGE_SIZE);
+	memset(addr_gva2hva(vm, list_gva), 0, 2 * PAGE_SIZE);
+	memset(results, 0, PAGE_SIZE);
+	vm_sev_launch(vm, snp_default_policy(), NULL);
+
+	vcpu_run(vcpu);
+	TEST_ASSERT_EQ(vcpu->run->exit_reason, KVM_EXIT_SYSTEM_EVENT);
+	TEST_ASSERT_EQ(vcpu->run->system_event.type, KVM_SYSTEM_EVENT_SEV_TERM);
+
+	TEST_ASSERT_EQ(results->missing_info1, GHCB_HV_RESP_MALFORMED_INPUT);
+	TEST_ASSERT_EQ(results->missing_info2, GHCB_ERR_MISSING_INPUT);
+	TEST_ASSERT_EQ(results->zero_info1, 0);
+	TEST_ASSERT_EQ(results->zero_info2, 0);
+	TEST_ASSERT_EQ(results->zero_rax,
+		       DIV_ROUND_UP(sizeof(struct apic_id_desc) + nr_vcpus * sizeof(u32),
+				    PAGE_SIZE));
+	TEST_ASSERT_EQ(results->invalid_info1, GHCB_HV_RESP_MALFORMED_INPUT);
+	TEST_ASSERT_EQ(results->invalid_info2, GHCB_ERR_INVALID_INPUT);
+	TEST_ASSERT_EQ(results->valid_info1, 0);
+	TEST_ASSERT_EQ(results->valid_info2, 0);
+	TEST_ASSERT_EQ(results->nr_entries, nr_vcpus);
+	TEST_ASSERT_EQ(results->first_apic_id, 0);
+	TEST_ASSERT_EQ(results->last_apic_id, nr_vcpus - 1);
+
+	kvm_vm_free(vm);
+}
+
+int main(int argc, char *argv[])
+{
+	unsigned int max_vcpus;
+
+	TEST_REQUIRE(kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(KVM_X86_SNP_VM));
+	run_apic_id_test(2);
+
+	/* 1024 IDs cross the one-page descriptor boundary. */
+	max_vcpus = kvm_check_cap(KVM_CAP_MAX_VCPUS);
+	if (max_vcpus >= 1024)
+		run_apic_id_test(1024);
+	else
+		pr_info("Skipping vCPU-count boundary test (max vCPUs: %u)\n", max_vcpus);
+
+	return 0;
+}
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH v2 8/8] KVM: selftests: Test SNP vCPU state and direct VMSA launch
  2026-09-08 10:33 [PATCH v2 0/8] KVM: SVM: Support direct setting of VMSA for SEV-SNP guests Jörg Rödel
                   ` (6 preceding siblings ...)
  2026-09-08 10:33 ` [PATCH v2 7/8] KVM: selftests: Test the SNP APIC-ID-list GHCB request Jörg Rödel
@ 2026-09-08 10:33 ` Jörg Rödel
  7 siblings, 0 replies; 12+ messages in thread
From: Jörg Rödel @ 2026-09-08 10:33 UTC (permalink / raw)
  To: Paolo Bonzini, Sean Christopherson
  Cc: Michael Roth, Liam Merwick, Vishal Annapurve, Ninad Naik,
	Joerg Roedel, Tom Lendacky, James Bottomley, kvm, linux-doc,
	linux-kernel, linux-kselftest, coconut-svsm

From: Joerg Roedel <joerg.roedel@amd.com>

Add vCPU-scoped SEV ioctl helpers and exercise the SNP vCPU state get and
set commands.  Enable KVM_CAP_SNP_DIRECT_VMSA before creating vCPUs and
verify that invalid capability arguments and late enable attempts fail.

Boot through a handcrafted BSP VMSA, verify VMSA and GHCB address round
trips and validity bits, rebind to the selected GPA, and verify an extra
unassociated AP remains uninitialized.

Reject multi-page launch updates and VMSAs with nonzero VMPL or mismatched
SEV features.  Also verify the legacy launch path and reject non-SNP use,
bad or reserved arguments, unsafe alignment, absent pages, wrong ordering,
and post-launch SET calls.  Verify that SET accepts both shared and private
guestmemfd-backed VMSA GPAs without consulting memory attributes or the
RMP.

Assisted-by: LLM
Signed-off-by: Joerg Roedel <joerg.roedel@amd.com>
---
 tools/testing/selftests/kvm/Makefile.kvm      |   1 +
 tools/testing/selftests/kvm/include/x86/sev.h |  49 ++
 .../kvm/x86/sev_snp_direct_vmsa_test.c        | 421 ++++++++++++++++++
 3 files changed, 471 insertions(+)
 create mode 100644 tools/testing/selftests/kvm/x86/sev_snp_direct_vmsa_test.c

diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 92eb3dff13ff..4cf0a8297b04 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -150,6 +150,7 @@ TEST_GEN_PROGS_x86 += x86/sev_dbg_test
 TEST_GEN_PROGS_x86 += x86/sev_init2_tests
 TEST_GEN_PROGS_x86 += x86/sev_migrate_tests
 TEST_GEN_PROGS_x86 += x86/sev_snp_apic_id_test
+TEST_GEN_PROGS_x86 += x86/sev_snp_direct_vmsa_test
 TEST_GEN_PROGS_x86 += x86/sev_smoke_test
 TEST_GEN_PROGS_x86 += x86/amx_test
 TEST_GEN_PROGS_x86 += x86/max_vcpuid_cap_test
diff --git a/tools/testing/selftests/kvm/include/x86/sev.h b/tools/testing/selftests/kvm/include/x86/sev.h
index dec383e59a47..0f6c92b802ee 100644
--- a/tools/testing/selftests/kvm/include/x86/sev.h
+++ b/tools/testing/selftests/kvm/include/x86/sev.h
@@ -100,6 +100,30 @@ static inline u64 snp_default_policy(void)
 	__TEST_ASSERT_VM_VCPU_IOCTL(!ret, #cmd,	ret, vm);		\
 })
 
+static inline int __vcpu_sev_ioctl(struct kvm_vcpu *vcpu, u32 cmd, void *arg)
+{
+	union {
+		struct kvm_sev_cmd c;
+		unsigned long raw;
+	} sev_cmd = { .c = {
+		.id = cmd,
+		.data = (u64)arg,
+		.sev_fd = vcpu->vm->arch.sev_fd,
+	} };
+	int ret;
+
+	ret = __vcpu_ioctl(vcpu, KVM_MEMORY_ENCRYPT_OP, &sev_cmd.raw);
+	return ret ?: sev_cmd.c.error;
+}
+
+#define vcpu_sev_ioctl(vcpu, cmd, arg)				\
+({								\
+	struct kvm_vcpu *__vcpu = (vcpu);				\
+	int ret = __vcpu_sev_ioctl(__vcpu, cmd, arg);		\
+								\
+	__TEST_ASSERT_VM_VCPU_IOCTL(!ret, #cmd, ret, __vcpu->vm);	\
+})
+
 void sev_vm_init(struct kvm_vm *vm);
 void sev_es_vm_init(struct kvm_vm *vm);
 void snp_vm_init(struct kvm_vm *vm);
@@ -144,6 +168,31 @@ static inline void snp_launch_update_data(struct kvm_vm *vm, gpa_t gpa,
 	vm_sev_ioctl(vm, KVM_SEV_SNP_LAUNCH_UPDATE, &update_data);
 }
 
+static inline void snp_launch_update_vmsa(struct kvm_vm *vm, gpa_t gpa,
+					  void *vmsa)
+{
+	vm_mem_set_private(vm, gpa, PAGE_SIZE);
+	snp_launch_update_data(vm, gpa, (u64)vmsa, PAGE_SIZE,
+			       KVM_SEV_SNP_PAGE_TYPE_VMSA);
+}
+
+static inline void snp_get_vcpu_state(struct kvm_vcpu *vcpu,
+				      struct kvm_sev_snp_vcpu_state *state)
+{
+	vcpu_sev_ioctl(vcpu, KVM_SEV_SNP_GET_VCPU_STATE, state);
+}
+
+static inline void snp_set_vcpu_state(struct kvm_vcpu *vcpu, gpa_t vmsa_gpa)
+{
+	struct kvm_sev_snp_vcpu_state state = {
+		.vmsa_gpa = vmsa_gpa,
+		.valid_fields = KVM_SEV_SNP_VCPU_STATE_VMSA_VALID |
+				KVM_SEV_SNP_VCPU_STATE_GHCB_VALID,
+	};
+
+	vcpu_sev_ioctl(vcpu, KVM_SEV_SNP_SET_VCPU_STATE, &state);
+}
+
 static inline void sev_dbg_crypt_memory(struct kvm_vm *vm, unsigned int cmd,
 					void *dst, void *src, unsigned int len)
 {
diff --git a/tools/testing/selftests/kvm/x86/sev_snp_direct_vmsa_test.c b/tools/testing/selftests/kvm/x86/sev_snp_direct_vmsa_test.c
new file mode 100644
index 000000000000..72eecf3853a3
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86/sev_snp_direct_vmsa_test.c
@@ -0,0 +1,421 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <errno.h>
+#include <stdlib.h>
+
+#include "kvm_util.h"
+#include "processor.h"
+#include "sev.h"
+#include "svm_util.h"
+
+#define DIRECT_MARKER	0x444952454354564dULL
+#define LEGACY_MARKER	0x4c4547414359564dULL
+#define SNP_ACTIVE_SEV_FEATURE	BIT_ULL(0)
+#define VMSA_PMD_SIZE	BIT_ULL(21)
+#define VMSA_MIN_GPA	(VMSA_PMD_SIZE + PAGE_SIZE)
+
+struct test_vmsa {
+	struct vmcb_seg es, cs, ss, ds, fs, gs;
+	struct vmcb_seg gdtr, ldtr, idtr, tr;
+	u64 pl0_ssp, pl1_ssp, pl2_ssp, pl3_ssp;
+	u64 u_cet;
+	u8 reserved_0xc8[2];
+	u8 vmpl;
+	u8 cpl;
+	u8 reserved_0xcc[4];
+	u64 efer;
+	u8 reserved_0xd8[104];
+	u64 xss;
+	u64 cr4, cr3, cr0, dr7, dr6, rflags, rip;
+	u64 dr0, dr1, dr2, dr3;
+	u64 dr0_addr_mask, dr1_addr_mask, dr2_addr_mask, dr3_addr_mask;
+	u8 reserved_0x1c0[24];
+	u64 rsp, s_cet, ssp, isst_addr, rax;
+	u64 star, lstar, cstar, sfmask, kernel_gs_base;
+	u64 sysenter_cs, sysenter_esp, sysenter_eip, cr2;
+	u8 reserved_0x248[32];
+	u64 g_pat, dbgctl, br_from, br_to, last_excp_from, last_excp_to;
+	u8 reserved_0x298[80];
+	u32 pkru, tsc_aux;
+	u64 tsc_scale, tsc_offset;
+	u8 reserved_0x300[8];
+	u64 rcx, rdx, rbx, reserved_0x320, rbp, rsi, rdi;
+	u64 r8, r9, r10, r11, r12, r13, r14, r15;
+	u8 reserved_0x380[16];
+	u64 guest_exit_info_1, guest_exit_info_2, guest_exit_int_info, guest_nrip;
+	u64 sev_features, vintr_ctrl, guest_exit_code, virtual_tom, tlb_id, pcpu_id;
+	u64 event_inj, xcr0;
+	u8 reserved_0x3f0[16];
+	u64 x87_dp;
+	u32 mxcsr;
+	u16 x87_ftw, x87_fsw, x87_fcw, x87_fop, x87_ds, x87_cs;
+	u64 x87_rip;
+	u8 fpreg_x87[80];
+	u8 fpreg_xmm[256];
+	u8 fpreg_ymm[256];
+} __packed;
+
+static_assert(offsetof(struct test_vmsa, vmpl) == 0xca);
+static_assert(offsetof(struct test_vmsa, rip) == 0x178);
+static_assert(offsetof(struct test_vmsa, sev_features) == 0x3b0);
+static_assert(offsetof(struct test_vmsa, xcr0) == 0x3e8);
+
+static void guest_direct_entry(u64 *marker)
+{
+	*marker = DIRECT_MARKER;
+	wrmsr(MSR_AMD64_SEV_ES_GHCB, GHCB_MSR_TERM_REQ);
+	vmgexit();
+}
+
+static void guest_legacy_entry(u64 *marker)
+{
+	*marker = LEGACY_MARKER;
+	wrmsr(MSR_AMD64_SEV_ES_GHCB, GHCB_MSR_TERM_REQ);
+	vmgexit();
+}
+
+static void copy_segment(struct vmcb_seg *dst, const struct kvm_segment *src)
+{
+	dst->selector = src->selector;
+	dst->base = src->base;
+	dst->limit = src->limit;
+	dst->attrib = src->type |
+		(src->s << SVM_SELECTOR_S_SHIFT) |
+		(src->dpl << SVM_SELECTOR_DPL_SHIFT) |
+		((src->present && !src->unusable) << SVM_SELECTOR_P_SHIFT) |
+		(src->avl << SVM_SELECTOR_AVL_SHIFT) |
+		(src->l << SVM_SELECTOR_L_SHIFT) |
+		(src->db << SVM_SELECTOR_DB_SHIFT) |
+		(src->g << SVM_SELECTOR_G_SHIFT);
+}
+
+static void copy_dtable(struct vmcb_seg *dst, const struct kvm_dtable *src)
+{
+	dst->base = src->base;
+	dst->limit = src->limit;
+}
+
+static void prepare_vmsa(struct kvm_vcpu *vcpu, struct test_vmsa *vmsa,
+			 void *entry)
+{
+	struct kvm_sregs sregs;
+	struct kvm_regs regs;
+
+	memset(vmsa, 0, PAGE_SIZE);
+	vcpu_sregs_get(vcpu, &sregs);
+	vcpu_regs_get(vcpu, &regs);
+
+	copy_segment(&vmsa->es, &sregs.es);
+	copy_segment(&vmsa->cs, &sregs.cs);
+	copy_segment(&vmsa->ss, &sregs.ss);
+	copy_segment(&vmsa->ds, &sregs.ds);
+	copy_segment(&vmsa->fs, &sregs.fs);
+	copy_segment(&vmsa->gs, &sregs.gs);
+	copy_dtable(&vmsa->gdtr, &sregs.gdt);
+	copy_segment(&vmsa->ldtr, &sregs.ldt);
+	copy_dtable(&vmsa->idtr, &sregs.idt);
+	copy_segment(&vmsa->tr, &sregs.tr);
+
+	vmsa->cpl = sregs.cs.dpl;
+	/*
+	 * KVM_GET_SREGS exposes the guest-visible EFER and therefore omits
+	 * SVME, which KVM normally adds to the hardware VMSA itself.
+	 */
+	vmsa->efer = sregs.efer | EFER_SVME;
+	vmsa->cr4 = sregs.cr4;
+	vmsa->cr3 = sregs.cr3;
+	vmsa->cr0 = sregs.cr0;
+	vmsa->dr7 = 0x400;
+	vmsa->dr6 = 0xffff0ff0;
+	vmsa->rflags = regs.rflags;
+	vmsa->rip = (u64)entry;
+	vmsa->rsp = regs.rsp;
+	vmsa->rax = regs.rax;
+	vmsa->rcx = regs.rcx;
+	vmsa->rdx = regs.rdx;
+	vmsa->rbx = regs.rbx;
+	vmsa->rbp = regs.rbp;
+	vmsa->rsi = regs.rsi;
+	vmsa->rdi = regs.rdi;
+	vmsa->r8 = regs.r8;
+	vmsa->r9 = regs.r9;
+	vmsa->r10 = regs.r10;
+	vmsa->r11 = regs.r11;
+	vmsa->r12 = regs.r12;
+	vmsa->r13 = regs.r13;
+	vmsa->r14 = regs.r14;
+	vmsa->r15 = regs.r15;
+	vmsa->g_pat = 0x0007040600070406ULL;
+	vmsa->sev_features = SNP_ACTIVE_SEV_FEATURE;
+	vmsa->xcr0 = 1;
+	vmsa->mxcsr = 0x1f80;
+	vmsa->x87_fcw = 0x37f;
+}
+
+static void expect_launch_update_vmsa_error(struct kvm_vm *vm, gpa_t gpa,
+					    void *vmsa, u64 size)
+{
+	struct kvm_sev_snp_launch_update update = {
+		.gfn_start = gpa >> PAGE_SHIFT,
+		.uaddr = (u64)vmsa,
+		.len = size,
+		.type = KVM_SEV_SNP_PAGE_TYPE_VMSA,
+	};
+
+	errno = 0;
+	TEST_ASSERT_EQ(__vm_sev_ioctl(vm, KVM_SEV_SNP_LAUNCH_UPDATE, &update), -1);
+	TEST_ASSERT_EQ(errno, EINVAL);
+}
+
+static void exclude_from_normal_launch(struct kvm_vm *vm, gpa_t gpa,
+				       unsigned int npages)
+{
+	struct userspace_mem_region *region;
+
+	region = memslot2region(vm, vm->memslots[MEM_REGION_TEST_DATA]);
+	sparsebit_clear_num(region->protected_phy_pages, gpa >> PAGE_SHIFT, npages);
+}
+
+static void assert_vcpu_terminated(struct kvm_vcpu *vcpu)
+{
+	vcpu_run(vcpu);
+	TEST_ASSERT_EQ(vcpu->run->exit_reason, KVM_EXIT_SYSTEM_EVENT);
+	TEST_ASSERT_EQ(vcpu->run->system_event.type, KVM_SYSTEM_EVENT_SEV_TERM);
+}
+
+static struct kvm_vm *create_direct_vmsa_vm(unsigned int nr_vcpus,
+					    void *guest_code,
+					    struct kvm_vcpu **vcpus)
+{
+	struct vm_shape shape = {
+		.mode = VM_MODE_DEFAULT,
+		.type = KVM_X86_SNP_VM,
+	};
+	struct kvm_vm *vm;
+	unsigned int i;
+
+	vm = __vm_create(shape, nr_vcpus, 0);
+	vm_enable_cap(vm, KVM_CAP_SNP_DIRECT_VMSA, 0);
+	for (i = 0; i < nr_vcpus; i++)
+		vcpus[i] = vm_vcpu_add(vm, i, guest_code);
+	kvm_arch_vm_finalize_vcpus(vm);
+
+	return vm;
+}
+
+static void test_direct_vmsa(void)
+{
+	struct test_vmsa *vmsas, *selected_vmsa;
+	struct kvm_sev_snp_vcpu_state state = {};
+	struct kvm_mp_state mp_state = {
+		.mp_state = KVM_MP_STATE_UNINITIALIZED,
+	};
+	struct kvm_vcpu *vcpus[2];
+	struct kvm_vcpu *vcpu, *ap;
+	struct kvm_vm *vm;
+	gva_t marker_gva;
+	gpa_t vmsa_gpa;
+	u64 *marker;
+
+	vm = create_direct_vmsa_vm(ARRAY_SIZE(vcpus), guest_legacy_entry, vcpus);
+	vcpu = vcpus[0];
+	ap = vcpus[1];
+	/* Restore normal AP state after vm_vcpu_add() makes it runnable. */
+	vcpu_mp_state_set(ap, &mp_state);
+	marker_gva = vm_alloc_shared(vm, PAGE_SIZE, KVM_UTIL_MIN_VADDR,
+				     MEM_REGION_TEST_DATA);
+	marker = addr_gva2hva(vm, marker_gva);
+	vcpu_args_set(vcpu, 1, marker_gva);
+
+	vmsa_gpa = vm_phy_pages_alloc(vm, 2, VMSA_MIN_GPA,
+				      vm->memslots[MEM_REGION_TEST_DATA]);
+	TEST_ASSERT(vmsa_gpa & (VMSA_PMD_SIZE - 1), "unsafe VMSA GPA");
+	vmsas = aligned_alloc(PAGE_SIZE, 2 * PAGE_SIZE);
+	TEST_ASSERT(vmsas, "Failed to allocate VMSA source pages");
+	selected_vmsa = (void *)vmsas + PAGE_SIZE;
+	prepare_vmsa(vcpu, &vmsas[0], guest_legacy_entry);
+	prepare_vmsa(vcpu, selected_vmsa, guest_direct_entry);
+
+	snp_vm_launch_start(vm, snp_default_policy());
+	vm_mem_set_private(vm, vmsa_gpa, 2 * PAGE_SIZE);
+	expect_launch_update_vmsa_error(vm, vmsa_gpa, vmsas, 2 * PAGE_SIZE);
+
+	vmsas[0].vmpl = 1;
+	expect_launch_update_vmsa_error(vm, vmsa_gpa, vmsas, PAGE_SIZE);
+	vmsas[0].vmpl = 0;
+	vmsas[0].sev_features = 0;
+	expect_launch_update_vmsa_error(vm, vmsa_gpa, vmsas, PAGE_SIZE);
+	vmsas[0].sev_features = SNP_ACTIVE_SEV_FEATURE;
+
+	snp_launch_update_vmsa(vm, vmsa_gpa, vmsas);
+	snp_launch_update_vmsa(vm, vmsa_gpa + PAGE_SIZE, selected_vmsa);
+	exclude_from_normal_launch(vm, vmsa_gpa, 2);
+	snp_vm_launch_update(vm);
+
+	/* Rebinding is allowed; the second, selected VMSA must win. */
+	snp_set_vcpu_state(vcpu, vmsa_gpa);
+	snp_set_vcpu_state(vcpu, vmsa_gpa + PAGE_SIZE);
+	snp_get_vcpu_state(vcpu, &state);
+	TEST_ASSERT_EQ(state.valid_fields,
+		       KVM_SEV_SNP_VCPU_STATE_VMSA_VALID |
+		       KVM_SEV_SNP_VCPU_STATE_GHCB_VALID);
+	TEST_ASSERT_EQ(state.vmsa_gpa, vmsa_gpa + PAGE_SIZE);
+	TEST_ASSERT_EQ(state.ghcb_gpa, 0);
+	snp_vm_launch_finish(vm);
+
+	vcpu_mp_state_get(ap, &mp_state);
+	TEST_ASSERT_EQ(mp_state.mp_state, KVM_MP_STATE_UNINITIALIZED);
+	*marker = 0;
+	assert_vcpu_terminated(vcpu);
+	TEST_ASSERT_EQ(*marker, DIRECT_MARKER);
+
+	state = (struct kvm_sev_snp_vcpu_state) {
+		.vmsa_gpa = vmsa_gpa,
+		.valid_fields = KVM_SEV_SNP_VCPU_STATE_VMSA_VALID,
+	};
+	errno = 0;
+	TEST_ASSERT_EQ(__vcpu_sev_ioctl(vcpu, KVM_SEV_SNP_SET_VCPU_STATE,
+					&state), -1);
+	TEST_ASSERT_EQ(errno, EINVAL);
+
+	free(vmsas);
+	kvm_vm_free(vm);
+}
+
+static void expect_set_vcpu_state_error(struct kvm_vcpu *vcpu,
+					struct kvm_sev_snp_vcpu_state *state,
+					int expected_errno)
+{
+	errno = 0;
+	TEST_ASSERT_EQ(__vcpu_sev_ioctl(vcpu, KVM_SEV_SNP_SET_VCPU_STATE, state), -1);
+	TEST_ASSERT_EQ(errno, expected_errno);
+}
+
+static void test_invalid_requests(void)
+{
+	struct kvm_sev_snp_vcpu_state state = {
+		.valid_fields = KVM_SEV_SNP_VCPU_STATE_VMSA_VALID,
+	};
+	struct kvm_vcpu *vcpu;
+	struct kvm_vm *vm;
+	gva_t shared_gva;
+	gpa_t private_gpa;
+
+	vm = vm_create_with_one_vcpu(&vcpu, guest_legacy_entry);
+	expect_set_vcpu_state_error(vcpu, &state, ENOTTY);
+	kvm_vm_free(vm);
+
+	vm = vm_sev_create_with_one_vcpu(KVM_X86_SNP_VM, guest_legacy_entry, &vcpu);
+	expect_set_vcpu_state_error(vcpu, &state, EINVAL);
+	TEST_ASSERT_EQ(__vm_enable_cap(vm, KVM_CAP_SNP_DIRECT_VMSA, 0), -1);
+	TEST_ASSERT_EQ(errno, EINVAL);
+	snp_vm_launch_start(vm, snp_default_policy());
+	expect_set_vcpu_state_error(vcpu, &state, EINVAL);
+	kvm_vm_free(vm);
+
+	vm = create_direct_vmsa_vm(1, guest_legacy_entry, &vcpu);
+	expect_set_vcpu_state_error(vcpu, &state, EINVAL);
+	snp_vm_launch_start(vm, snp_default_policy());
+
+	state.pad[4] = 1;
+	expect_set_vcpu_state_error(vcpu, &state, EINVAL);
+	state.pad[4] = 0;
+	state.valid_fields |= BIT_ULL(2);
+	expect_set_vcpu_state_error(vcpu, &state, EINVAL);
+	state.valid_fields &= ~BIT_ULL(2);
+	state.vmsa_gpa = PAGE_SIZE + 1;
+	expect_set_vcpu_state_error(vcpu, &state, EINVAL);
+	state.vmsa_gpa = VMSA_PMD_SIZE;
+	expect_set_vcpu_state_error(vcpu, &state, EINVAL);
+	state.vmsa_gpa = BIT_ULL(40) + PAGE_SIZE;
+	expect_set_vcpu_state_error(vcpu, &state, EINVAL);
+
+	shared_gva = vm_alloc_shared(vm, 2 * PAGE_SIZE, KVM_UTIL_MIN_VADDR,
+				     MEM_REGION_TEST_DATA);
+	state.vmsa_gpa = addr_gva2gpa(vm, shared_gva);
+	if (!(state.vmsa_gpa & (VMSA_PMD_SIZE - 1)))
+		state.vmsa_gpa += PAGE_SIZE;
+	state.ghcb_gpa = BIT_ULL(40);
+	state.valid_fields |= KVM_SEV_SNP_VCPU_STATE_GHCB_VALID;
+	vcpu_sev_ioctl(vcpu, KVM_SEV_SNP_SET_VCPU_STATE, &state);
+
+	private_gpa = vm_phy_page_alloc(vm, VMSA_MIN_GPA,
+					vm->memslots[MEM_REGION_TEST_DATA]);
+	vm_mem_set_private(vm, private_gpa, PAGE_SIZE);
+	state.vmsa_gpa = private_gpa;
+	vcpu_sev_ioctl(vcpu, KVM_SEV_SNP_SET_VCPU_STATE, &state);
+
+	memset(&state, 0, sizeof(state));
+	snp_get_vcpu_state(vcpu, &state);
+	TEST_ASSERT_EQ(state.valid_fields,
+		       KVM_SEV_SNP_VCPU_STATE_VMSA_VALID |
+		       KVM_SEV_SNP_VCPU_STATE_GHCB_VALID);
+	TEST_ASSERT_EQ(state.vmsa_gpa, private_gpa);
+	TEST_ASSERT_EQ(state.ghcb_gpa, BIT_ULL(40));
+
+	kvm_vm_free(vm);
+}
+
+static void test_direct_vmsa_capability(void)
+{
+	struct kvm_enable_cap cap = {
+		.cap = KVM_CAP_SNP_DIRECT_VMSA,
+	};
+	struct vm_shape shape = {
+		.mode = VM_MODE_DEFAULT,
+		.type = KVM_X86_SNP_VM,
+	};
+	struct kvm_vm *vm;
+
+	vm = vm_create_barebones();
+	TEST_ASSERT_EQ(__vm_enable_cap(vm, KVM_CAP_SNP_DIRECT_VMSA, 0), -1);
+	TEST_ASSERT_EQ(errno, EINVAL);
+	kvm_vm_free(vm);
+
+	vm = __vm_create(shape, 1, 0);
+	TEST_ASSERT_EQ(__vm_enable_cap(vm, KVM_CAP_SNP_DIRECT_VMSA, 1), -1);
+	TEST_ASSERT_EQ(errno, EINVAL);
+	cap.args[3] = 1;
+	TEST_ASSERT_EQ(__vm_ioctl(vm, KVM_ENABLE_CAP, &cap), -1);
+	TEST_ASSERT_EQ(errno, EINVAL);
+	cap.args[3] = 0;
+	cap.flags = 1;
+	TEST_ASSERT_EQ(__vm_ioctl(vm, KVM_ENABLE_CAP, &cap), -1);
+	TEST_ASSERT_EQ(errno, EINVAL);
+	vm_enable_cap(vm, KVM_CAP_SNP_DIRECT_VMSA, 0);
+	vm_vcpu_add(vm, 0, guest_legacy_entry);
+	TEST_ASSERT_EQ(__vm_enable_cap(vm, KVM_CAP_SNP_DIRECT_VMSA, 0), -1);
+	TEST_ASSERT_EQ(errno, EINVAL);
+	kvm_vm_free(vm);
+}
+
+static void test_legacy_launch(void)
+{
+	struct kvm_vcpu *vcpu;
+	struct kvm_vm *vm;
+	gva_t marker_gva;
+	u64 *marker;
+
+	vm = vm_sev_create_with_one_vcpu(KVM_X86_SNP_VM, guest_legacy_entry, &vcpu);
+	marker_gva = vm_alloc_shared(vm, PAGE_SIZE, KVM_UTIL_MIN_VADDR,
+				     MEM_REGION_TEST_DATA);
+	marker = addr_gva2hva(vm, marker_gva);
+	vcpu_args_set(vcpu, 1, marker_gva);
+	vm_sev_launch(vm, snp_default_policy(), NULL);
+	*marker = 0;
+	assert_vcpu_terminated(vcpu);
+	TEST_ASSERT_EQ(*marker, LEGACY_MARKER);
+	kvm_vm_free(vm);
+}
+
+int main(int argc, char *argv[])
+{
+	TEST_REQUIRE(kvm_check_cap(KVM_CAP_SNP_VCPU_STATE));
+	TEST_REQUIRE(kvm_check_cap(KVM_CAP_SNP_DIRECT_VMSA));
+	TEST_ASSERT(sizeof(struct test_vmsa) <= PAGE_SIZE, "VMSA structure is too large");
+
+	test_direct_vmsa_capability();
+	test_invalid_requests();
+	test_direct_vmsa();
+	test_legacy_launch();
+	return 0;
+}
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* Re: [PATCH v2 6/8] KVM: SEV: Add SNP vCPU state get and set commands
  2026-09-08 10:33 ` [PATCH v2 6/8] KVM: SEV: Add SNP vCPU state get and set commands Jörg Rödel
@ 2026-09-08 10:49   ` sashiko-bot
  0 siblings, 0 replies; 12+ messages in thread
From: sashiko-bot @ 2026-09-08 10:49 UTC (permalink / raw)
  To: Jörg Rödel; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] KVM_SEV_SNP_GET_VCPU_STATE silently hides the configured VMSA GPA during transient MMU invalidations, causing state loss during live migration.
- [High] KVM_SEV_SNP_SET_VCPU_STATE incorrectly rejects valid 2MB-aligned VMSA GPAs.
--

commit ccc907edf743bac6d650ebf0b64bb61800fedfea
Author: Joerg Roedel <joerg.roedel@amd.com>

KVM: SEV: Add SNP vCPU state get and set commands

Add KVM_SEV_SNP_GET_VCPU_STATE and KVM_SEV_SNP_SET_VCPU_STATE as
vCPU-scoped KVM_MEMORY_ENCRYPT_OP commands and advertise
KVM_CAP_SNP_VCPU_STATE.

> diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c
> index 7a9ef1bc54e9d..3e0727e251a93 100644
> --- a/arch/x86/kvm/svm/sev.c
> +++ b/arch/x86/kvm/svm/sev.c
[ ... ]
> @@ -2585,6 +2587,89 @@ static int snp_launch_update_vmsa(struct kvm *kvm, struct kvm_sev_cmd *argp)
>  	return ret;
>  }
>  
> +static int snp_get_vcpu_state(struct kvm_vcpu *vcpu,
> +			      struct kvm_sev_cmd *argp)
> +{
> +	struct vcpu_svm *svm = to_svm(vcpu);
> +	struct kvm *kvm = vcpu->kvm;
> +	struct kvm_sev_snp_vcpu_state state = {};
> +
> +	if (!is_sev_snp_guest(vcpu))
> +		return -ENOTTY;
> +	if (!to_kvm_sev_info(kvm)->snp_context)
> +		return -EINVAL;
> +
> +	guard(mutex)(&svm->sev_es.snp_vmsa_mutex);
> +
> +	if (VALID_PAGE(svm->sev_es.snp_guest_vmsa_gpa) &&
> +	    VALID_PAGE(svm->vmcb->control.vmsa_pa)) {

[Severity: High]
Does this safely handle transient MMU invalidations?

When a concurrent host MMU invalidation (e.g., from page migration) overlaps
with a VMSA reload, __sev_snp_reload_vmsa() will hit the retry path and set
vmsa_pa to INVALID_PAGE while correctly queuing KVM_REQ_VMSA_PAGE_RELOAD.

If userspace queries the state to migrate the VM during this window, this
check evaluates to false because vmsa_pa is INVALID_PAGE. Consequently, it
returns VMSA_VALID = 0 even though snp_guest_vmsa_gpa is correctly
configured. Can this cause the configured VMSA GPA to be silently
dropped and lead to state loss on the destination VM?

> +		state.vmsa_gpa = svm->sev_es.snp_guest_vmsa_gpa;
> +		state.valid_fields |= KVM_SEV_SNP_VCPU_STATE_VMSA_VALID;
> +	}
[ ... ]
> +static int snp_set_vcpu_state(struct kvm_vcpu *vcpu,
> +			      struct kvm_sev_cmd *argp)
> +{
[ ... ]
> +	if (state.valid_fields & KVM_SEV_SNP_VCPU_STATE_VMSA_VALID) {
> +		if (!PAGE_ALIGNED(state.vmsa_gpa) ||
> +		    !page_address_valid(vcpu, state.vmsa_gpa) ||
> +		    IS_ALIGNED(state.vmsa_gpa, PMD_SIZE))

[Severity: High]
Is it intended to reject perfectly valid 4KB-aligned VMSA GPAs if they happen
to fall on a 2MB boundary?

A guest OS or userspace VMM linearly allocating memory might naturally land on
a 2MB-aligned address (e.g., 0x200000). The IS_ALIGNED(state.vmsa_gpa,
PMD_SIZE) condition evaluates to true for these addresses and rejects them
with -EINVAL, which can cause sporadic VM boot or migration failures.

> +			return -EINVAL;
> +	}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260908103338.427254-1-joro@8bytes.org?part=6

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v2 2/8] KVM: SVM: Implement GET_AP_APIC_IDS NAE event
  2026-09-08 10:33 ` [PATCH v2 2/8] KVM: SVM: Implement GET_AP_APIC_IDS NAE event Jörg Rödel
@ 2026-09-08 10:51   ` sashiko-bot
  0 siblings, 0 replies; 12+ messages in thread
From: sashiko-bot @ 2026-09-08 10:51 UTC (permalink / raw)
  To: Jörg Rödel; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] The new SVM_VMGEXIT_GET_APIC_IDS feature is unconditionally enabled and advertised to the guest, breaking the default-off policy and live migration capability.
--

commit b3798f7802c495f4483b09adbdba116cdb452f71
Author: Joerg Roedel <joerg.roedel@amd.com>

KVM: SVM: Implement GET_AP_APIC_IDS NAE event

This commit implements the GHCB APIC ID list feature to allow SNP guests to
discover vCPUs needing VMSAs without INIT-SIPI. It handles undersized buffers
and copies online vCPU APIC IDs to guest memory.

> diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c
> index 5705723f1f412..c16eca9983f37 100644
> --- a/arch/x86/kvm/svm/sev.c
> +++ b/arch/x86/kvm/svm/sev.c
> @@ -40,7 +40,9 @@
>  #define GHCB_VERSION_MAX	2ULL
>  #define GHCB_VERSION_MIN	1ULL
>  
> -#define GHCB_HV_FT_SUPPORTED	(GHCB_HV_FT_SNP | GHCB_HV_FT_SNP_AP_CREATION)
> +#define GHCB_HV_FT_SUPPORTED	(GHCB_HV_FT_SNP | \
> +				 GHCB_HV_FT_SNP_AP_CREATION | \
> +				 GHCB_HV_FT_APIC_ID_LIST)

[Severity: High]
Does unconditionally adding GHCB_HV_FT_APIC_ID_LIST bypass userspace 
capability negotiation? 

KVM subsystem guidelines mandate that any new behavior the guest can observe
must be off by default and discoverable through the architecture's standard
enumeration interface.

If a VM boots on a kernel with this patch, observes the feature via the
GHCB_MSR_HV_FT_REQ MSR, and is then live-migrated to an older kernel that
lacks support, could it cause the VM to crash when it attempts to use the
NAE event? Should this feature require explicit opt-in from host userspace
(e.g., via a KVM_CAP_ check) before advertising it to the guest?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260908103338.427254-1-joro@8bytes.org?part=2

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v2 4/8] KVM: SEV: Add direct VMSA capability
  2026-09-08 10:33 ` [PATCH v2 4/8] KVM: SEV: Add direct VMSA capability Jörg Rödel
@ 2026-09-08 10:53   ` sashiko-bot
  0 siblings, 0 replies; 12+ messages in thread
From: sashiko-bot @ 2026-09-08 10:53 UTC (permalink / raw)
  To: Jörg Rödel; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] Using `guard(mutex)` inside a `switch` case without enclosing braces causes a compile-time error and bypasses variable initialization.
--

commit 14ba20ca988537a92ef023f074770d517f738803
Author: Joerg Roedel <joerg.roedel@amd.com>

KVM: SEV: Add direct VMSA capability

This commit introduces the KVM_CAP_SNP_DIRECT_VMSA capability, allowing
userspace to manage VMSA pages directly for SNP VMs. When enabled, it skips
KVM-owned VMSA allocation during vCPU creation and measurement at launch.

> diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
> index 7d59d301e1e54..98c5cffe2c227 100644
> --- a/arch/x86/kvm/svm/svm.c
> +++ b/arch/x86/kvm/svm/svm.c
[ ... ]
> +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);

[Severity: Low]
Does this code cause a build failure? Using guard(mutex) inside a switch case
without enclosing braces declares a variable in the wider scope of the switch
block.

> +		if (kvm->created_vcpus)
> +			return -EINVAL;
> +
> +		to_kvm_sev_info(kvm)->snp_direct_vmsa = true;
> +		return 0;
> +#endif
> +	default:
> +		return -EINVAL;

If execution jumps to this default label, it bypasses the initialization of
the cleanup variable declared by the guard in the case above. The cleanup
function will then attempt to run on an uninitialized pointer upon return.

Could the KVM_CAP_SNP_DIRECT_VMSA case be enclosed in curly braces to limit
the scope of the guard?

> +	}
> +}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260908103338.427254-1-joro@8bytes.org?part=4

^ permalink raw reply	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2026-09-08 10:53 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-08 10:33 [PATCH v2 0/8] KVM: SVM: Support direct setting of VMSA for SEV-SNP guests Jörg Rödel
2026-09-08 10:33 ` [PATCH v2 1/8] KVM: SEV: Document SNP direct VMSA userspace ABI Jörg Rödel
2026-09-08 10:33 ` [PATCH v2 2/8] KVM: SVM: Implement GET_AP_APIC_IDS NAE event Jörg Rödel
2026-09-08 10:51   ` sashiko-bot
2026-09-08 10:33 ` [PATCH v2 3/8] KVM: SVM: Hold SRCU while reloading guest-owned VMSAs Jörg Rödel
2026-09-08 10:33 ` [PATCH v2 4/8] KVM: SEV: Add direct VMSA capability Jörg Rödel
2026-09-08 10:53   ` sashiko-bot
2026-09-08 10:33 ` [PATCH v2 5/8] KVM: SEV: Allow VMSA pages in SNP launch updates Jörg Rödel
2026-09-08 10:33 ` [PATCH v2 6/8] KVM: SEV: Add SNP vCPU state get and set commands Jörg Rödel
2026-09-08 10:49   ` sashiko-bot
2026-09-08 10:33 ` [PATCH v2 7/8] KVM: selftests: Test the SNP APIC-ID-list GHCB request Jörg Rödel
2026-09-08 10:33 ` [PATCH v2 8/8] KVM: selftests: Test SNP vCPU state and direct VMSA launch Jörg Rödel

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox