Kernel KVM virtualization development
 help / color / mirror / Atom feed
* [PATCH v8 0/8] KVM: SVM: Add Page Modification Logging (PML) support
@ 2026-09-07  6:38 Nikunj A Dadhania
  2026-09-07  6:38 ` [PATCH v8 1/8] KVM: VMX: Pass @vcpu, not @vmx to init_vmcs() Nikunj A Dadhania
                   ` (7 more replies)
  0 siblings, 8 replies; 14+ messages in thread
From: Nikunj A Dadhania @ 2026-09-07  6:38 UTC (permalink / raw)
  To: kvm, seanjc, pbonzini
  Cc: thomas.lendacky, bp, joao.m.martins, nikunj, kai.huang, yosry

This series implements Page Modification Logging (PML) for guests on select
AMD Zen6 and later processors, bringing hardware-assisted dirty logging
support. PML is designed to track guest modified memory pages. PML enables the
hypervisor to identify which pages in a guest's memory have been modified
since the last checkpoint or during live migration. More details can be
found in the latest AMD APM Volume 2 (System Programming), section
"15.25.15 Page Modification Logging".

The PML feature uses two new VMCB fields (PML_ADDR and PML_INDEX) and
generates a VMEXIT when the 4KB log buffer becomes full.

The feature is enabled by default when hardware support is detected and can
be disabled via the 'pml' module parameter.

Changelog:
v8:
* Rebased on latest kvm/next
* Drop redundant pml_addr=0 in nested_vmcb02_prepare_control() (Yosry)
* Fix misleading comment, pml_index=-1 is hardening not stale-data
  cleanup (Yosry, Sean)
* Route nested SVM_EXIT_PML_FULL to KVM and treat it as unexpected
  exit (Sean)
* Refactor update_cpu_dirty_logging() to common code as a separate
  pre-patch (Sean)

v7: https://lore.kernel.org/kvm/20260518045916.2988667-1-nikunj@amd.com/
* Swap patch order to avoid adding @pml_page parameter only to remove it
  (Sean)
* Add Sean's prep patch to pass @vcpu to init_vmcs() instead of @vmx,
  cleaning up all vmx->vcpu usage (Sean)
* Drop guard(svm_vmcb01) patch; access vmcb01 directly in
  svm_update_cpu_dirty_logging() since VMCB is just memory (Sean, Yosry)
* Only initialize PML index on reset, leave unchanged on INIT to avoid
  discarding unflushed entries. PML_INDEX in the control area remains valid
  after intercepted SHUTDOWN (Sashiko, Sean, Kai)

Nikunj A Dadhania (7):
  KVM: x86: Move PML page to common vcpu arch structure
  KVM: x86: Carve out PML flush routine
  KVM: VMX: Use cpu_dirty_log_size instead of enable_pml for PML checks
  KVM: x86: Carve out common dirty logging update
  x86/cpufeatures: Add Page modification logging
  KVM: SVM: Use BIT_ULL for 64-bit misc_ctl bit definitions
  KVM: SVM: Add Page modification logging support

Sean Christopherson (1):
  KVM: VMX: Pass @vcpu, not @vmx to init_vmcs()

 arch/x86/include/asm/cpufeatures.h |   1 +
 arch/x86/include/asm/kvm_host.h    |   4 +-
 arch/x86/include/asm/svm.h         |  14 ++--
 arch/x86/include/uapi/asm/svm.h    |   2 +
 arch/x86/kernel/cpu/scattered.c    |   1 +
 arch/x86/kvm/svm/nested.c          |  14 ++++
 arch/x86/kvm/svm/sev.c             |   2 +-
 arch/x86/kvm/svm/svm.c             | 106 ++++++++++++++++++++++++++++-
 arch/x86/kvm/svm/svm.h             |   1 +
 arch/x86/kvm/vmx/main.c            |   4 +-
 arch/x86/kvm/vmx/vmx.c             |  82 +++++++---------------
 arch/x86/kvm/vmx/vmx.h             |   9 +--
 arch/x86/kvm/vmx/x86_ops.h         |   2 +-
 arch/x86/kvm/x86.c                 |  48 ++++++++++++-
 arch/x86/kvm/x86.h                 |   7 ++
 15 files changed, 220 insertions(+), 77 deletions(-)


base-commit: d4b7fb647204f0c81dfeae2d1a708e4d858e0c94
-- 
2.48.1


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

* [PATCH v8 1/8] KVM: VMX: Pass @vcpu, not @vmx to init_vmcs()
  2026-09-07  6:38 [PATCH v8 0/8] KVM: SVM: Add Page Modification Logging (PML) support Nikunj A Dadhania
@ 2026-09-07  6:38 ` Nikunj A Dadhania
  2026-09-07  6:39 ` [PATCH v8 2/8] KVM: x86: Move PML page to common vcpu arch structure Nikunj A Dadhania
                   ` (6 subsequent siblings)
  7 siblings, 0 replies; 14+ messages in thread
From: Nikunj A Dadhania @ 2026-09-07  6:38 UTC (permalink / raw)
  To: kvm, seanjc, pbonzini
  Cc: thomas.lendacky, bp, joao.m.martins, nikunj, kai.huang, yosry

From: Sean Christopherson <seanjc@google.com>

Pass @vcpu instead of @vmx to init_vmcs(), and switch all of the vmx->vcpu
usage to a simple vcpu.

No functional change intended.

Signed-off-by: Sean Christopherson <seanjc@google.com>
Signed-off-by: Nikunj A Dadhania <nikunj@amd.com>
Reviewed-by: Kai Huang <kai.huang@intel.com>
---
 arch/x86/kvm/vmx/vmx.c | 25 +++++++++++++------------
 1 file changed, 13 insertions(+), 12 deletions(-)

diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
index 612ab07d4100..a4fb4e77a56f 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -4883,10 +4883,11 @@ int vmx_vcpu_precreate(struct kvm *kvm)
 
 #define VMX_XSS_EXIT_BITMAP 0
 
-static void init_vmcs(struct vcpu_vmx *vmx)
+static void init_vmcs(struct kvm_vcpu *vcpu)
 {
-	struct kvm *kvm = vmx->vcpu.kvm;
+	struct kvm *kvm = vcpu->kvm;
 	struct kvm_vmx *kvm_vmx = to_kvm_vmx(kvm);
+	struct vcpu_vmx *vmx = to_vmx(vcpu);
 
 	if (nested)
 		nested_vmx_set_vmcs_shadowing_bitmap();
@@ -4911,7 +4912,7 @@ static void init_vmcs(struct vcpu_vmx *vmx)
 	if (cpu_has_tertiary_exec_ctrls())
 		tertiary_exec_controls_set(vmx, vmx_tertiary_exec_control(vmx));
 
-	if (enable_apicv && lapic_in_kernel(&vmx->vcpu)) {
+	if (enable_apicv && lapic_in_kernel(vcpu)) {
 		vmcs_write64(EOI_EXIT_BITMAP0, 0);
 		vmcs_write64(EOI_EXIT_BITMAP1, 0);
 		vmcs_write64(EOI_EXIT_BITMAP2, 0);
@@ -4923,7 +4924,7 @@ static void init_vmcs(struct vcpu_vmx *vmx)
 		vmcs_write64(POSTED_INTR_DESC_ADDR, __pa((&vmx->vt.pi_desc)));
 	}
 
-	if (vmx_can_use_ipiv(&vmx->vcpu)) {
+	if (vmx_can_use_ipiv(vcpu)) {
 		vmcs_write64(PID_POINTER_TABLE, __pa(kvm_vmx->pid_table));
 		vmcs_write16(LAST_PID_POINTER_INDEX, kvm->arch.max_vcpu_ids - 1);
 	}
@@ -4958,15 +4959,15 @@ static void init_vmcs(struct vcpu_vmx *vmx)
 	vmcs_write64(VM_ENTRY_MSR_LOAD_ADDR, __pa(vmx->msr_autoload.guest.val));
 
 	if (vmcs_config.vmentry_ctrl & VM_ENTRY_LOAD_IA32_PAT)
-		vmcs_write64(GUEST_IA32_PAT, vmx->vcpu.arch.pat);
+		vmcs_write64(GUEST_IA32_PAT, vcpu->arch.pat);
 
 	vm_exit_controls_set(vmx, vmx_get_initial_vmexit_ctrl());
 
 	/* 22.2.1, 20.8.1 */
 	vm_entry_controls_set(vmx, vmx_get_initial_vmentry_ctrl());
 
-	vmx->vcpu.arch.cr0_guest_owned_bits = vmx_l1_guest_owned_cr0_bits();
-	vmcs_writel(CR0_GUEST_HOST_MASK, ~vmx->vcpu.arch.cr0_guest_owned_bits);
+	vcpu->arch.cr0_guest_owned_bits = vmx_l1_guest_owned_cr0_bits();
+	vmcs_writel(CR0_GUEST_HOST_MASK, ~vcpu->arch.cr0_guest_owned_bits);
 
 	set_cr4_guest_host_mask(vmx);
 
@@ -4981,7 +4982,7 @@ static void init_vmcs(struct vcpu_vmx *vmx)
 		vmcs_write16(GUEST_PML_INDEX, PML_HEAD_INDEX);
 	}
 
-	vmx_write_encls_bitmap(&vmx->vcpu, NULL);
+	vmx_write_encls_bitmap(vcpu, NULL);
 
 	if (vmx_pt_mode_is_host_guest()) {
 		memset(&vmx->pt_desc, 0, sizeof(vmx->pt_desc));
@@ -4994,13 +4995,13 @@ static void init_vmcs(struct vcpu_vmx *vmx)
 	vmcs_writel(GUEST_SYSENTER_ESP, 0);
 	vmcs_writel(GUEST_SYSENTER_EIP, 0);
 
-	vmx_guest_debugctl_write(&vmx->vcpu, 0);
+	vmx_guest_debugctl_write(vcpu, 0);
 
 	if (cpu_has_vmx_tpr_shadow()) {
 		vmcs_write64(VIRTUAL_APIC_PAGE_ADDR, 0);
-		if (cpu_need_tpr_shadow(&vmx->vcpu))
+		if (cpu_need_tpr_shadow(vcpu))
 			vmcs_write64(VIRTUAL_APIC_PAGE_ADDR,
-				     __pa(vmx->vcpu.arch.apic->regs));
+				     __pa(vcpu->arch.apic->regs));
 		vmcs_write32(TPR_THRESHOLD, 0);
 	}
 
@@ -5011,7 +5012,7 @@ static void __vmx_vcpu_reset(struct kvm_vcpu *vcpu)
 {
 	struct vcpu_vmx *vmx = to_vmx(vcpu);
 
-	init_vmcs(vmx);
+	init_vmcs(vcpu);
 
 	if (nested &&
 	    kvm_check_has_quirk(vcpu->kvm, KVM_X86_QUIRK_STUFF_FEATURE_MSRS))
-- 
2.48.1


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

* [PATCH v8 2/8] KVM: x86: Move PML page to common vcpu arch structure
  2026-09-07  6:38 [PATCH v8 0/8] KVM: SVM: Add Page Modification Logging (PML) support Nikunj A Dadhania
  2026-09-07  6:38 ` [PATCH v8 1/8] KVM: VMX: Pass @vcpu, not @vmx to init_vmcs() Nikunj A Dadhania
@ 2026-09-07  6:39 ` Nikunj A Dadhania
  2026-09-07  6:39 ` [PATCH v8 3/8] KVM: x86: Carve out PML flush routine Nikunj A Dadhania
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 14+ messages in thread
From: Nikunj A Dadhania @ 2026-09-07  6:39 UTC (permalink / raw)
  To: kvm, seanjc, pbonzini
  Cc: thomas.lendacky, bp, joao.m.martins, nikunj, kai.huang, yosry

Move the PML page pointer from VMX-specific vcpu_vmx structure to the
common kvm_vcpu_arch structure to enable sharing between VMX and SVM
implementations. Only the page pointer is moved to x86 common code while
keeping allocation logic vendor-specific, since AMD requires
snp_safe_alloc_page() for PML buffer allocation.

Update all VMX references accordingly.

No functional change, restructuring to prepare for SVM PML support.

Suggested-by: Kai Huang <kai.huang@intel.com>
Reviewed-by: Kai Huang <kai.huang@intel.com>
Signed-off-by: Nikunj A Dadhania <nikunj@amd.com>
---
 arch/x86/include/asm/kvm_host.h |  2 ++
 arch/x86/kvm/vmx/vmx.c          | 21 ++++++++++-----------
 arch/x86/kvm/vmx/vmx.h          |  7 -------
 arch/x86/kvm/x86.h              |  5 +++++
 4 files changed, 17 insertions(+), 18 deletions(-)

diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index 3af7395c2430..07c422793702 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -789,6 +789,8 @@ struct kvm_vcpu_arch {
 	 */
 	struct kvm_mmu_memory_cache mmu_external_spt_cache;
 
+	struct page *pml_page;
+
 	/*
 	 * QEMU userspace and the guest each have their own FPU state.
 	 * In vcpu_run, we switch between the user and guest FPU contexts.
diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
index a4fb4e77a56f..caa24dafe989 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -4978,7 +4978,7 @@ static void init_vmcs(struct kvm_vcpu *vcpu)
 		vmcs_write64(XSS_EXIT_BITMAP, VMX_XSS_EXIT_BITMAP);
 
 	if (enable_pml) {
-		vmcs_write64(PML_ADDRESS, page_to_phys(vmx->pml_pg));
+		vmcs_write64(PML_ADDRESS, page_to_phys(vcpu->arch.pml_page));
 		vmcs_write16(GUEST_PML_INDEX, PML_HEAD_INDEX);
 	}
 
@@ -6419,17 +6419,16 @@ void vmx_get_entry_info(struct kvm_vcpu *vcpu, u32 *intr_info, u32 *error_code)
 		*error_code = 0;
 }
 
-static void vmx_destroy_pml_buffer(struct vcpu_vmx *vmx)
+static void vmx_destroy_pml_buffer(struct kvm_vcpu *vcpu)
 {
-	if (vmx->pml_pg) {
-		__free_page(vmx->pml_pg);
-		vmx->pml_pg = NULL;
+	if (vcpu->arch.pml_page) {
+		__free_page(vcpu->arch.pml_page);
+		vcpu->arch.pml_page = NULL;
 	}
 }
 
 static void vmx_flush_pml_buffer(struct kvm_vcpu *vcpu)
 {
-	struct vcpu_vmx *vmx = to_vmx(vcpu);
 	u16 pml_idx, pml_tail_index;
 	u64 *pml_buf;
 	int i;
@@ -6452,7 +6451,7 @@ static void vmx_flush_pml_buffer(struct kvm_vcpu *vcpu)
 	 * Read the entries in the same order they were written, to ensure that
 	 * the dirty ring is filled in the same order the CPU wrote them.
 	 */
-	pml_buf = page_address(vmx->pml_pg);
+	pml_buf = page_address(vcpu->arch.pml_page);
 
 	for (i = PML_HEAD_INDEX; i >= pml_tail_index; i--) {
 		u64 gpa;
@@ -7649,7 +7648,7 @@ void vmx_vcpu_free(struct kvm_vcpu *vcpu)
 	struct vcpu_vmx *vmx = to_vmx(vcpu);
 
 	if (enable_pml)
-		vmx_destroy_pml_buffer(vmx);
+		vmx_destroy_pml_buffer(vcpu);
 	free_vpid(vmx->vpid);
 	nested_vmx_free_vcpu(vcpu);
 	free_loaded_vmcs(vmx->loaded_vmcs);
@@ -7681,8 +7680,8 @@ int vmx_vcpu_create(struct kvm_vcpu *vcpu)
 	 * for the guest), etc.
 	 */
 	if (enable_pml) {
-		vmx->pml_pg = alloc_page(GFP_KERNEL_ACCOUNT | __GFP_ZERO);
-		if (!vmx->pml_pg)
+		vcpu->arch.pml_page = alloc_page(GFP_KERNEL_ACCOUNT | __GFP_ZERO);
+		if (!vcpu->arch.pml_page)
 			goto free_vpid;
 	}
 
@@ -7753,7 +7752,7 @@ int vmx_vcpu_create(struct kvm_vcpu *vcpu)
 free_vmcs:
 	free_loaded_vmcs(vmx->loaded_vmcs);
 free_pml:
-	vmx_destroy_pml_buffer(vmx);
+	vmx_destroy_pml_buffer(vcpu);
 free_vpid:
 	free_vpid(vmx->vpid);
 	return err;
diff --git a/arch/x86/kvm/vmx/vmx.h b/arch/x86/kvm/vmx/vmx.h
index dc8517f15bc4..6bcca3487c59 100644
--- a/arch/x86/kvm/vmx/vmx.h
+++ b/arch/x86/kvm/vmx/vmx.h
@@ -265,13 +265,6 @@ struct vcpu_vmx {
 	unsigned int ple_window;
 	bool ple_window_dirty;
 
-	/* Support for PML */
-#define PML_LOG_NR_ENTRIES	512
-	/* PML is written backwards: this is the first entry written by the CPU */
-#define PML_HEAD_INDEX		(PML_LOG_NR_ENTRIES-1)
-
-	struct page *pml_pg;
-
 	/* apic deadline value in host tsc */
 	u64 hv_deadline_tsc;
 
diff --git a/arch/x86/kvm/x86.h b/arch/x86/kvm/x86.h
index 0f5919b092e4..05c31ee984c9 100644
--- a/arch/x86/kvm/x86.h
+++ b/arch/x86/kvm/x86.h
@@ -900,4 +900,9 @@ int ____kvm_emulate_hypercall(struct kvm_vcpu *vcpu, int cpl,
 
 int kvm_emulate_hypercall(struct kvm_vcpu *vcpu);
 
+/* Support for PML */
+#define PML_LOG_NR_ENTRIES	512
+/* PML is written backwards: this is the first entry written by the CPU */
+#define PML_HEAD_INDEX		(PML_LOG_NR_ENTRIES-1)
+
 #endif
-- 
2.48.1


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

* [PATCH v8 3/8] KVM: x86: Carve out PML flush routine
  2026-09-07  6:38 [PATCH v8 0/8] KVM: SVM: Add Page Modification Logging (PML) support Nikunj A Dadhania
  2026-09-07  6:38 ` [PATCH v8 1/8] KVM: VMX: Pass @vcpu, not @vmx to init_vmcs() Nikunj A Dadhania
  2026-09-07  6:39 ` [PATCH v8 2/8] KVM: x86: Move PML page to common vcpu arch structure Nikunj A Dadhania
@ 2026-09-07  6:39 ` Nikunj A Dadhania
  2026-09-07  6:39 ` [PATCH v8 4/8] KVM: VMX: Use cpu_dirty_log_size instead of enable_pml for PML checks Nikunj A Dadhania
                   ` (4 subsequent siblings)
  7 siblings, 0 replies; 14+ messages in thread
From: Nikunj A Dadhania @ 2026-09-07  6:39 UTC (permalink / raw)
  To: kvm, seanjc, pbonzini
  Cc: thomas.lendacky, bp, joao.m.martins, nikunj, kai.huang, yosry

Move the PML (Page Modification Logging) buffer flushing logic from
VMX-specific code to common x86 KVM code to enable reuse by SVM and avoid
code duplication.

The AMD SVM PML implementations share the same behavior as VMX PML:
 1) The PML buffer is a 4K page with 512 entries
 2) Hardware records dirty GPAs in reverse order (from index 511 to 0)
 3) Hardware clears bits 11:0 when recording GPAs

No functional change intended for VMX, except tone down the WARN_ON() to
WARN_ON_ONCE() for the page alignment check. If hardware exhibits this
behavior once, it's likely to occur repeatedly, so use WARN_ON_ONCE() to
avoid log flooding while still capturing the unexpected condition.

The refactoring prepares for SVM to leverage the same PML flushing
implementation.

Reviewed-by: Kai Huang <kai.huang@intel.com>
Signed-off-by: Nikunj A Dadhania <nikunj@amd.com>
---
 arch/x86/kvm/vmx/vmx.c | 26 ++------------------------
 arch/x86/kvm/x86.c     | 31 +++++++++++++++++++++++++++++++
 arch/x86/kvm/x86.h     |  2 ++
 3 files changed, 35 insertions(+), 24 deletions(-)

diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
index caa24dafe989..8baf570dd456 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -6429,37 +6429,15 @@ static void vmx_destroy_pml_buffer(struct kvm_vcpu *vcpu)
 
 static void vmx_flush_pml_buffer(struct kvm_vcpu *vcpu)
 {
-	u16 pml_idx, pml_tail_index;
-	u64 *pml_buf;
-	int i;
+	u16 pml_idx;
 
 	pml_idx = vmcs_read16(GUEST_PML_INDEX);
 
 	/* Do nothing if PML buffer is empty */
 	if (pml_idx == PML_HEAD_INDEX)
 		return;
-	/*
-	 * PML index always points to the next available PML buffer entity
-	 * unless PML log has just overflowed.
-	 */
-	pml_tail_index = (pml_idx >= PML_LOG_NR_ENTRIES) ? 0 : pml_idx + 1;
 
-	/*
-	 * PML log is written backwards: the CPU first writes the entry 511
-	 * then the entry 510, and so on.
-	 *
-	 * Read the entries in the same order they were written, to ensure that
-	 * the dirty ring is filled in the same order the CPU wrote them.
-	 */
-	pml_buf = page_address(vcpu->arch.pml_page);
-
-	for (i = PML_HEAD_INDEX; i >= pml_tail_index; i--) {
-		u64 gpa;
-
-		gpa = pml_buf[i];
-		WARN_ON(gpa & (PAGE_SIZE - 1));
-		kvm_vcpu_mark_page_dirty(vcpu, gpa >> PAGE_SHIFT);
-	}
+	kvm_flush_pml_buffer(vcpu, pml_idx);
 
 	/* reset PML index */
 	vmcs_write16(GUEST_PML_INDEX, PML_HEAD_INDEX);
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 79468ddfe473..dc5af51c0a90 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -3941,6 +3941,37 @@ void kvm_arch_sync_dirty_log(struct kvm *kvm, struct kvm_memory_slot *memslot)
 		kvm_vcpu_kick(vcpu);
 }
 
+void kvm_flush_pml_buffer(struct kvm_vcpu *vcpu, u16 pml_idx)
+{
+	u16 pml_tail_index;
+	u64 *pml_buf;
+	int i;
+
+	/*
+	 * PML index always points to the next available PML buffer entity
+	 * unless PML log has just overflowed.
+	 */
+	pml_tail_index = (pml_idx >= PML_LOG_NR_ENTRIES) ? 0 : pml_idx + 1;
+
+	/*
+	 * PML log is written backwards: the CPU first writes the entry 511
+	 * then the entry 510, and so on.
+	 *
+	 * Read the entries in the same order they were written, to ensure that
+	 * the dirty ring is filled in the same order the CPU wrote them.
+	 */
+	pml_buf = page_address(vcpu->arch.pml_page);
+
+	for (i = PML_HEAD_INDEX; i >= pml_tail_index; i--) {
+		u64 gpa;
+
+		gpa = pml_buf[i];
+		WARN_ON_ONCE(gpa & (PAGE_SIZE - 1));
+		kvm_vcpu_mark_page_dirty(vcpu, gpa >> PAGE_SHIFT);
+	}
+}
+EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_flush_pml_buffer);
+
 int kvm_vm_ioctl_enable_cap(struct kvm *kvm,
 			    struct kvm_enable_cap *cap)
 {
diff --git a/arch/x86/kvm/x86.h b/arch/x86/kvm/x86.h
index 05c31ee984c9..2d747c20965c 100644
--- a/arch/x86/kvm/x86.h
+++ b/arch/x86/kvm/x86.h
@@ -905,4 +905,6 @@ int kvm_emulate_hypercall(struct kvm_vcpu *vcpu);
 /* PML is written backwards: this is the first entry written by the CPU */
 #define PML_HEAD_INDEX		(PML_LOG_NR_ENTRIES-1)
 
+void kvm_flush_pml_buffer(struct kvm_vcpu *vcpu, u16 pml_idx);
+
 #endif
-- 
2.48.1


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

* [PATCH v8 4/8] KVM: VMX: Use cpu_dirty_log_size instead of enable_pml for PML checks
  2026-09-07  6:38 [PATCH v8 0/8] KVM: SVM: Add Page Modification Logging (PML) support Nikunj A Dadhania
                   ` (2 preceding siblings ...)
  2026-09-07  6:39 ` [PATCH v8 3/8] KVM: x86: Carve out PML flush routine Nikunj A Dadhania
@ 2026-09-07  6:39 ` Nikunj A Dadhania
  2026-09-07  6:39 ` [PATCH v8 5/8] KVM: x86: Carve out common dirty logging update Nikunj A Dadhania
                   ` (3 subsequent siblings)
  7 siblings, 0 replies; 14+ messages in thread
From: Nikunj A Dadhania @ 2026-09-07  6:39 UTC (permalink / raw)
  To: kvm, seanjc, pbonzini
  Cc: thomas.lendacky, bp, joao.m.martins, nikunj, kai.huang, yosry,
	Pankaj Gupta

Replace the enable_pml check with cpu_dirty_log_size in VMX PML code
to determine whether PML is enabled on a per-VM basis. The enable_pml
module parameter is a global setting that doesn't reflect per-VM
capabilities, whereas cpu_dirty_log_size accurately indicates whether
a specific VM has PML enabled.

For example, TDX VMs don't yet support PML. Using cpu_dirty_log_size
ensures the check correctly reflects this, while enable_pml would
incorrectly indicate PML is available.

This also improves consistency with kvm_mmu_update_cpu_dirty_logging(),
which already uses cpu_dirty_log_size to determine PML enablement.

Suggested-by: Kai Huang <kai.huang@intel.com>
Reviewed-by: Kai Huang <kai.huang@intel.com>
Reviewed-by: Pankaj Gupta <pankaj.gupta@amd.com>
Signed-off-by: Nikunj A Dadhania <nikunj@amd.com>
---
 arch/x86/kvm/vmx/vmx.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
index 8baf570dd456..d944ea9ed273 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -8422,7 +8422,7 @@ void vmx_update_cpu_dirty_logging(struct kvm_vcpu *vcpu)
 {
 	struct vcpu_vmx *vmx = to_vmx(vcpu);
 
-	if (WARN_ON_ONCE(!enable_pml))
+	if (WARN_ON_ONCE(!vcpu->kvm->arch.cpu_dirty_log_size))
 		return;
 
 	guard(vmx_vmcs01)(vcpu);
-- 
2.48.1


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

* [PATCH v8 5/8] KVM: x86: Carve out common dirty logging update
  2026-09-07  6:38 [PATCH v8 0/8] KVM: SVM: Add Page Modification Logging (PML) support Nikunj A Dadhania
                   ` (3 preceding siblings ...)
  2026-09-07  6:39 ` [PATCH v8 4/8] KVM: VMX: Use cpu_dirty_log_size instead of enable_pml for PML checks Nikunj A Dadhania
@ 2026-09-07  6:39 ` Nikunj A Dadhania
  2026-09-07  6:39 ` [PATCH v8 6/8] x86/cpufeatures: Add Page modification logging Nikunj A Dadhania
                   ` (2 subsequent siblings)
  7 siblings, 0 replies; 14+ messages in thread
From: Nikunj A Dadhania @ 2026-09-07  6:39 UTC (permalink / raw)
  To: kvm, seanjc, pbonzini
  Cc: thomas.lendacky, bp, joao.m.martins, nikunj, kai.huang, yosry

Move the PML enable/disable decision logic from the VMX into a new
common wrapper kvm_update_cpu_dirty_logging() in x86.c to avoid
code duplication. Update the update_cpu_dirty_logging() signature to
accept a bool enable parameter accordingly.

Note, this subtly changes the ordering for VMX: currently
guard(vmx_vmcs01) is taken before the atomic_read of
nr_memslots_dirty_logging; after this refactoring the atomic_read
happens in common code before guard(vmx_vmcs01) is taken. The ordering
flip is harmless as a concurrent change to nr_memslots_dirty_logging
will queue another update request.

No functional change intended for VMX, restructuring to prepare for SVM
PML support.

Suggested-by: Sean Christopherson <seanjc@google.com>
Signed-off-by: Nikunj A Dadhania <nikunj@amd.com>
---
 arch/x86/include/asm/kvm_host.h |  2 +-
 arch/x86/kvm/vmx/main.c         |  4 ++--
 arch/x86/kvm/vmx/vmx.c          | 12 ++----------
 arch/x86/kvm/vmx/vmx.h          |  2 +-
 arch/x86/kvm/vmx/x86_ops.h      |  2 +-
 arch/x86/kvm/x86.c              | 17 ++++++++++++++++-
 6 files changed, 23 insertions(+), 16 deletions(-)

diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index 07c422793702..2debf6294290 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -1672,7 +1672,7 @@ struct kvm_x86_ops {
 			       struct x86_exception *exception);
 	void (*handle_exit_irqoff)(struct kvm_vcpu *vcpu);
 
-	void (*update_cpu_dirty_logging)(struct kvm_vcpu *vcpu);
+	void (*update_cpu_dirty_logging)(struct kvm_vcpu *vcpu, bool enable);
 
 	void (*vcpu_blocking)(struct kvm_vcpu *vcpu);
 	void (*vcpu_unblocking)(struct kvm_vcpu *vcpu);
diff --git a/arch/x86/kvm/vmx/main.c b/arch/x86/kvm/vmx/main.c
index 4c52ab8d0786..e77b5b731275 100644
--- a/arch/x86/kvm/vmx/main.c
+++ b/arch/x86/kvm/vmx/main.c
@@ -108,7 +108,7 @@ static void vt_vcpu_load(struct kvm_vcpu *vcpu, int cpu)
 	vmx_vcpu_load(vcpu, cpu);
 }
 
-static void vt_update_cpu_dirty_logging(struct kvm_vcpu *vcpu)
+static void vt_update_cpu_dirty_logging(struct kvm_vcpu *vcpu, bool enable)
 {
 	/*
 	 * Basic TDX does not support feature PML. KVM does not enable PML in
@@ -117,7 +117,7 @@ static void vt_update_cpu_dirty_logging(struct kvm_vcpu *vcpu)
 	if (WARN_ON_ONCE(is_td_vcpu(vcpu)))
 		return;
 
-	vmx_update_cpu_dirty_logging(vcpu);
+	vmx_update_cpu_dirty_logging(vcpu, enable);
 }
 
 static void vt_prepare_switch_to_guest(struct kvm_vcpu *vcpu)
diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
index d944ea9ed273..b3768b4b212f 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -8418,21 +8418,13 @@ static void vmx_update_hv_timer(struct kvm_vcpu *vcpu, bool force_immediate_exit
 }
 #endif
 
-void vmx_update_cpu_dirty_logging(struct kvm_vcpu *vcpu)
+void vmx_update_cpu_dirty_logging(struct kvm_vcpu *vcpu, bool enable)
 {
 	struct vcpu_vmx *vmx = to_vmx(vcpu);
 
-	if (WARN_ON_ONCE(!vcpu->kvm->arch.cpu_dirty_log_size))
-		return;
-
 	guard(vmx_vmcs01)(vcpu);
 
-	/*
-	 * Note, nr_memslots_dirty_logging can be changed concurrent with this
-	 * code, but in that case another update request will be made and so
-	 * the guest will never run with a stale PML value.
-	 */
-	if (atomic_read(&vcpu->kvm->nr_memslots_dirty_logging))
+	if (enable)
 		secondary_exec_controls_setbit(vmx, SECONDARY_EXEC_ENABLE_PML);
 	else
 		secondary_exec_controls_clearbit(vmx, SECONDARY_EXEC_ENABLE_PML);
diff --git a/arch/x86/kvm/vmx/vmx.h b/arch/x86/kvm/vmx/vmx.h
index 6bcca3487c59..5f505ad14dde 100644
--- a/arch/x86/kvm/vmx/vmx.h
+++ b/arch/x86/kvm/vmx/vmx.h
@@ -390,7 +390,7 @@ u64 vmx_get_l2_tsc_multiplier(struct kvm_vcpu *vcpu);
 
 gva_t vmx_get_untagged_addr(struct kvm_vcpu *vcpu, gva_t gva, unsigned int flags);
 
-void vmx_update_cpu_dirty_logging(struct kvm_vcpu *vcpu);
+void vmx_update_cpu_dirty_logging(struct kvm_vcpu *vcpu, bool enable);
 
 u64 vmx_get_supported_debugctl(struct kvm_vcpu *vcpu, bool host_initiated);
 bool vmx_is_valid_debugctl(struct kvm_vcpu *vcpu, u64 data, bool host_initiated);
diff --git a/arch/x86/kvm/vmx/x86_ops.h b/arch/x86/kvm/vmx/x86_ops.h
index 054fd14bb2e1..4810f9b1b0fc 100644
--- a/arch/x86/kvm/vmx/x86_ops.h
+++ b/arch/x86/kvm/vmx/x86_ops.h
@@ -118,7 +118,7 @@ u64 vmx_get_l2_tsc_offset(struct kvm_vcpu *vcpu);
 u64 vmx_get_l2_tsc_multiplier(struct kvm_vcpu *vcpu);
 void vmx_write_tsc_offset(struct kvm_vcpu *vcpu);
 void vmx_write_tsc_multiplier(struct kvm_vcpu *vcpu);
-void vmx_update_cpu_dirty_logging(struct kvm_vcpu *vcpu);
+void vmx_update_cpu_dirty_logging(struct kvm_vcpu *vcpu, bool enable);
 #ifdef CONFIG_X86_64
 int vmx_set_hv_timer(struct kvm_vcpu *vcpu, u64 guest_deadline_tsc,
 		     bool *expired);
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index dc5af51c0a90..6d26de8b7c3c 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -8073,6 +8073,21 @@ static void kvm_vcpu_reload_apic_access_page(struct kvm_vcpu *vcpu)
 	kvm_x86_call(set_apic_access_page_addr)(vcpu);
 }
 
+static void kvm_update_cpu_dirty_logging(struct kvm_vcpu *vcpu)
+{
+	/*
+	 * Note, nr_memslots_dirty_logging can be changed concurrent with this
+	 * code, but in that case another update request will be made and so
+	 * the guest will never run with a stale PML value.
+	 */
+	bool enable = atomic_read(&vcpu->kvm->nr_memslots_dirty_logging);
+
+	if (WARN_ON_ONCE(!vcpu->kvm->arch.cpu_dirty_log_size))
+		return;
+
+	kvm_x86_call(update_cpu_dirty_logging)(vcpu, enable);
+}
+
 /*
  * Called within kvm->srcu read side.
  * Returns 1 to let vcpu_run() continue the guest execution loop without
@@ -8239,7 +8254,7 @@ static int vcpu_enter_guest(struct kvm_vcpu *vcpu)
 			kvm_x86_call(recalc_intercepts)(vcpu);
 
 		if (kvm_check_request(KVM_REQ_UPDATE_CPU_DIRTY_LOGGING, vcpu))
-			kvm_x86_call(update_cpu_dirty_logging)(vcpu);
+			kvm_update_cpu_dirty_logging(vcpu);
 
 		if (kvm_check_request(KVM_REQ_UPDATE_PROTECTED_GUEST_STATE, vcpu)) {
 			kvm_vcpu_reset(vcpu, true);
-- 
2.48.1


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

* [PATCH v8 6/8] x86/cpufeatures: Add Page modification logging
  2026-09-07  6:38 [PATCH v8 0/8] KVM: SVM: Add Page Modification Logging (PML) support Nikunj A Dadhania
                   ` (4 preceding siblings ...)
  2026-09-07  6:39 ` [PATCH v8 5/8] KVM: x86: Carve out common dirty logging update Nikunj A Dadhania
@ 2026-09-07  6:39 ` Nikunj A Dadhania
  2026-09-07  6:47   ` sashiko-bot
  2026-09-07  6:39 ` [PATCH v8 7/8] KVM: SVM: Use BIT_ULL for 64-bit misc_ctl bit definitions Nikunj A Dadhania
  2026-09-07  6:39 ` [PATCH v8 8/8] KVM: SVM: Add Page modification logging support Nikunj A Dadhania
  7 siblings, 1 reply; 14+ messages in thread
From: Nikunj A Dadhania @ 2026-09-07  6:39 UTC (permalink / raw)
  To: kvm, seanjc, pbonzini
  Cc: thomas.lendacky, bp, joao.m.martins, nikunj, kai.huang, yosry

Page modification logging(PML) is a hardware feature designed to track
guest modified memory pages. PML enables the hypervisor to identify which
pages in a guest's memory have been changed since the last checkpoint or
during live migration.

The PML feature is advertised via CPUID leaf 0x8000000A ECX[4] bit.

Acked-by: Borislav Petkov (AMD) <bp@alien8.de>
Signed-off-by: Nikunj A Dadhania <nikunj@amd.com>
---
 arch/x86/include/asm/cpufeatures.h | 1 +
 arch/x86/kernel/cpu/scattered.c    | 1 +
 2 files changed, 2 insertions(+)

diff --git a/arch/x86/include/asm/cpufeatures.h b/arch/x86/include/asm/cpufeatures.h
index f70ee74b5f92..cbfcc66a18e9 100644
--- a/arch/x86/include/asm/cpufeatures.h
+++ b/arch/x86/include/asm/cpufeatures.h
@@ -238,6 +238,7 @@
 // free: was #define X86_FEATURE_PVUNLOCK               ( 8*32+20) /* PV unlock function */
 #define X86_FEATURE_VCPUPREEMPT		( 8*32+21) /* PV vcpu_is_preempted function */
 #define X86_FEATURE_TDX_GUEST		( 8*32+22) /* "tdx_guest" Intel Trust Domain Extensions Guest */
+#define X86_FEATURE_PML			( 8*32+23) /* AMD Page Modification logging */
 
 /* Intel-defined CPU features, CPUID level 0x00000007:0 (EBX), word 9 */
 #define X86_FEATURE_FSGSBASE		( 9*32+ 0) /* "fsgsbase" RDFSBASE, WRFSBASE, RDGSBASE, WRGSBASE instructions*/
diff --git a/arch/x86/kernel/cpu/scattered.c b/arch/x86/kernel/cpu/scattered.c
index 8665a6474806..b18cc9c88dd5 100644
--- a/arch/x86/kernel/cpu/scattered.c
+++ b/arch/x86/kernel/cpu/scattered.c
@@ -55,6 +55,7 @@ static const struct cpuid_bit cpuid_bits[] = {
 	{ X86_FEATURE_AMD_FAST_CPPC,		CPUID_EDX, 15, 0x80000007, 0 },
 	{ X86_FEATURE_CPPC_PERF_PRIO,		CPUID_EDX, 16, 0x80000007, 0 },
 	{ X86_FEATURE_MBA,			CPUID_EBX,  6, 0x80000008, 0 },
+	{ X86_FEATURE_PML,			CPUID_ECX,  4, 0x8000000a, 0 },
 	{ X86_FEATURE_X2AVIC_EXT,		CPUID_ECX,  6, 0x8000000a, 0 },
 	{ X86_FEATURE_COHERENCY_SFW_NO,		CPUID_EBX, 31, 0x8000001f, 0 },
 	{ X86_FEATURE_SMBA,			CPUID_EBX,  2, 0x80000020, 0 },
-- 
2.48.1


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

* [PATCH v8 7/8] KVM: SVM: Use BIT_ULL for 64-bit misc_ctl bit definitions
  2026-09-07  6:38 [PATCH v8 0/8] KVM: SVM: Add Page Modification Logging (PML) support Nikunj A Dadhania
                   ` (5 preceding siblings ...)
  2026-09-07  6:39 ` [PATCH v8 6/8] x86/cpufeatures: Add Page modification logging Nikunj A Dadhania
@ 2026-09-07  6:39 ` Nikunj A Dadhania
  2026-09-07  6:39 ` [PATCH v8 8/8] KVM: SVM: Add Page modification logging support Nikunj A Dadhania
  7 siblings, 0 replies; 14+ messages in thread
From: Nikunj A Dadhania @ 2026-09-07  6:39 UTC (permalink / raw)
  To: kvm, seanjc, pbonzini
  Cc: thomas.lendacky, bp, joao.m.martins, nikunj, kai.huang, yosry

Replace BIT() with BIT_ULL() for SVM misc control bit definitions
since misc_ctl is a 64-bit field in the VMCB control area structure.

No functional change intended.

Signed-off-by: Nikunj A Dadhania <nikunj@amd.com>
---
 arch/x86/include/asm/svm.h | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/arch/x86/include/asm/svm.h b/arch/x86/include/asm/svm.h
index aa63431ba92c..f199c52709df 100644
--- a/arch/x86/include/asm/svm.h
+++ b/arch/x86/include/asm/svm.h
@@ -240,10 +240,10 @@ struct __attribute__ ((__packed__)) vmcb_control_area {
 #define SVM_IOIO_SIZE_MASK (7 << SVM_IOIO_SIZE_SHIFT)
 #define SVM_IOIO_ASIZE_MASK (7 << SVM_IOIO_ASIZE_SHIFT)
 
-#define SVM_MISC_ENABLE_NP		BIT(0)
-#define SVM_MISC_ENABLE_SEV		BIT(1)
-#define SVM_MISC_ENABLE_SEV_ES	BIT(2)
-#define SVM_MISC_ENABLE_GMET	BIT(3)
+#define SVM_MISC_ENABLE_NP		BIT_ULL(0)
+#define SVM_MISC_ENABLE_SEV		BIT_ULL(1)
+#define SVM_MISC_ENABLE_SEV_ES		BIT_ULL(2)
+#define SVM_MISC_ENABLE_GMET		BIT_ULL(3)
 
 #define SVM_MISC2_ENABLE_V_LBR	BIT_ULL(0)
 #define SVM_MISC2_ENABLE_V_VMLOAD_VMSAVE	BIT_ULL(1)
-- 
2.48.1


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

* [PATCH v8 8/8] KVM: SVM: Add Page modification logging support
  2026-09-07  6:38 [PATCH v8 0/8] KVM: SVM: Add Page Modification Logging (PML) support Nikunj A Dadhania
                   ` (6 preceding siblings ...)
  2026-09-07  6:39 ` [PATCH v8 7/8] KVM: SVM: Use BIT_ULL for 64-bit misc_ctl bit definitions Nikunj A Dadhania
@ 2026-09-07  6:39 ` Nikunj A Dadhania
  2026-09-07  7:00   ` sashiko-bot
  7 siblings, 1 reply; 14+ messages in thread
From: Nikunj A Dadhania @ 2026-09-07  6:39 UTC (permalink / raw)
  To: kvm, seanjc, pbonzini
  Cc: thomas.lendacky, bp, joao.m.martins, nikunj, kai.huang, yosry

Currently, dirty logging relies on write protecting guest memory and
marking dirty GFNs during subsequent write faults. This method works but
incurs overhead due to additional write faults for each dirty GFN.

Implement support for the Page Modification Logging (PML) feature, a
hardware-assisted method for efficient dirty logging. PML automatically
logs dirty GPA[51:12] to a 4K buffer when the CPU sets NPT D-bits. Two new
VMCB fields are utilized: PML_ADDR and PML_INDEX. The PML_INDEX is
initialized to 511 (8 bytes per GPA entry), and the CPU decreases the
PML_INDEX after logging each GPA. When the PML buffer is full, a
VMEXIT(PML_FULL) with exit code 0x407 is generated.

Since PML_INDEX in the VMCB control area remains valid after an intercepted
SHUTDOWN, only initialize it on reset and leave it unchanged on INIT to
avoid discarding already-logged entries that haven't been flushed.

PML operates on guest physical addresses at the NPT level, tracking D-bit
updates in page tables rather than memory content. This allows it to work
identically for normal and confidential computing guests
(SEV/SEV-ES/SEV-SNP), enabling cpu_dirty_log_size to be set uniformly for
all AMD VMs without special-casing encrypted guests.

Use vmcb01 directly when updating PML controls to ensure L1's state
remains correct, as svm->vmcb points to vmcb02 when L2 is active.

PML is not enabled in hardware for nested guests; treat PML_FULL as
unexpected exits.

Add a new module parameter to enable/disable PML, and enable it by default
when supported.

Acked-by: Kai Huang <kai.huang@intel.com>
Signed-off-by: Nikunj A Dadhania <nikunj@amd.com>
---
 arch/x86/include/asm/svm.h      |   6 +-
 arch/x86/include/uapi/asm/svm.h |   2 +
 arch/x86/kvm/svm/nested.c       |  14 +++++
 arch/x86/kvm/svm/sev.c          |   2 +-
 arch/x86/kvm/svm/svm.c          | 106 +++++++++++++++++++++++++++++++-
 arch/x86/kvm/svm/svm.h          |   1 +
 6 files changed, 127 insertions(+), 4 deletions(-)

diff --git a/arch/x86/include/asm/svm.h b/arch/x86/include/asm/svm.h
index f199c52709df..e0a7549a7c72 100644
--- a/arch/x86/include/asm/svm.h
+++ b/arch/x86/include/asm/svm.h
@@ -165,7 +165,10 @@ struct __attribute__ ((__packed__)) vmcb_control_area {
 	u8 reserved_9[22];
 	u64 allowed_sev_features;	/* Offset 0x138 */
 	u64 guest_sev_features;		/* Offset 0x140 */
-	u8 reserved_10[664];
+	u8 reserved_10[128];
+	u64 pml_addr;			/* Offset 0x1c8 */
+	u16 pml_index;			/* Offset 0x1d0 */
+	u8 reserved_11[526];
 	/*
 	 * Offset 0x3e0, 32 bytes reserved
 	 * for use by hypervisor/software.
@@ -244,6 +247,7 @@ struct __attribute__ ((__packed__)) vmcb_control_area {
 #define SVM_MISC_ENABLE_SEV		BIT_ULL(1)
 #define SVM_MISC_ENABLE_SEV_ES		BIT_ULL(2)
 #define SVM_MISC_ENABLE_GMET		BIT_ULL(3)
+#define SVM_MISC_ENABLE_PML		BIT_ULL(11)
 
 #define SVM_MISC2_ENABLE_V_LBR	BIT_ULL(0)
 #define SVM_MISC2_ENABLE_V_VMLOAD_VMSAVE	BIT_ULL(1)
diff --git a/arch/x86/include/uapi/asm/svm.h b/arch/x86/include/uapi/asm/svm.h
index 010a45c9f614..e80676185092 100644
--- a/arch/x86/include/uapi/asm/svm.h
+++ b/arch/x86/include/uapi/asm/svm.h
@@ -101,6 +101,7 @@
 #define SVM_EXIT_AVIC_INCOMPLETE_IPI		0x401
 #define SVM_EXIT_AVIC_UNACCELERATED_ACCESS	0x402
 #define SVM_EXIT_VMGEXIT       0x403
+#define SVM_EXIT_PML_FULL	0x407
 
 /* SEV-ES software-defined VMGEXIT events */
 #define SVM_VMGEXIT_MMIO_READ			0x80000001ull
@@ -236,6 +237,7 @@
 	{ SVM_EXIT_AVIC_INCOMPLETE_IPI,		"avic_incomplete_ipi" }, \
 	{ SVM_EXIT_AVIC_UNACCELERATED_ACCESS,   "avic_unaccelerated_access" }, \
 	{ SVM_EXIT_VMGEXIT,		"vmgexit" }, \
+	{ SVM_EXIT_PML_FULL,		"pml_full" }, \
 	{ SVM_VMGEXIT_MMIO_READ,	"vmgexit_mmio_read" }, \
 	{ SVM_VMGEXIT_MMIO_WRITE,	"vmgexit_mmio_write" }, \
 	{ SVM_VMGEXIT_NMI_COMPLETE,	"vmgexit_nmi_complete" }, \
diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
index 73f37b050d0a..da417c4c5915 100644
--- a/arch/x86/kvm/svm/nested.c
+++ b/arch/x86/kvm/svm/nested.c
@@ -901,6 +901,13 @@ static void nested_vmcb02_prepare_control(struct vcpu_svm *svm)
 	vmcb02->control.msrpm_base_pa = vmcb01->control.msrpm_base_pa;
 	vmcb_mark_dirty(vmcb02, VMCB_PERM_MAP);
 
+	/*
+	 * PML is never enabled in hardware for L2.  Make sure that an
+	 * unexpected PML write would trigger a PML_FULL VM-Exit.
+	 */
+	if (pml)
+		vmcb02->control.pml_index = -1;
+
 	/*
 	 * Stash vmcb02's counter if the guest hasn't moved past the guilty
 	 * instruction; otherwise, reset the counter to '0'.
@@ -1820,6 +1827,13 @@ int nested_svm_exit_special(struct vcpu_svm *svm)
 		if (nested_svm_is_l2_tlb_flush_hcall(vcpu))
 			return NESTED_EXIT_HOST;
 		break;
+	case SVM_EXIT_PML_FULL:
+		/*
+		 * All PML full exits are handled by KVM.  KVM emulates PML in
+		 * software for L1, but never enables PML in hardware on behalf
+		 * of L1.
+		 */
+		return NESTED_EXIT_HOST;
 	default:
 		break;
 	}
diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c
index 5705723f1f41..cfe990f80504 100644
--- a/arch/x86/kvm/svm/sev.c
+++ b/arch/x86/kvm/svm/sev.c
@@ -4994,7 +4994,7 @@ struct page *snp_safe_alloc_page_node(int node, gfp_t gfp)
 	 * Allocate an SNP-safe page to workaround the SNP erratum where
 	 * the CPU will incorrectly signal an RMP violation #PF if a
 	 * hugepage (2MB or 1GB) collides with the RMP entry of a
-	 * 2MB-aligned VMCB, VMSA, or AVIC backing page.
+	 * 2MB-aligned VMCB, VMSA, PML or AVIC backing page.
 	 *
 	 * Allocate one extra page, choose a page which is not
 	 * 2MB-aligned, and free the other.
diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
index 7d59d301e1e5..950c250a77b5 100644
--- a/arch/x86/kvm/svm/svm.c
+++ b/arch/x86/kvm/svm/svm.c
@@ -179,6 +179,9 @@ module_param(vnmi, bool, 0444);
 
 module_param(enable_mediated_pmu, bool, 0444);
 
+bool __ro_after_init pml = true;
+module_param(pml, bool, 0444);
+
 static bool __ro_after_init svm_gp_erratum_intercept = true;
 
 static u8 rsm_ins_bytes[] = "\x0f\xaa";
@@ -1267,6 +1270,23 @@ static void init_vmcb(struct kvm_vcpu *vcpu, bool init_event)
 	if (vcpu->kvm->arch.bus_lock_detection_enabled)
 		svm_set_intercept(svm, INTERCEPT_BUSLOCK);
 
+	if (pml) {
+		/*
+		 * Populate the page address and index here, PML is enabled
+		 * when dirty logging is enabled on the memslot through
+		 * svm_update_cpu_dirty_logging()
+		 */
+		control->pml_addr = (u64)__sme_set(page_to_phys(vcpu->arch.pml_page));
+
+		/*
+		 * The VMCB control area remains valid after an intercepted SHUTDOWN
+		 * (AMD APM Rev 3.45+), so only initialize PML index on reset to avoid
+		 * discarding already-logged entries that haven't been flushed.
+		 */
+		if (!init_event)
+			control->pml_index = PML_HEAD_INDEX;
+	}
+
 	if (is_sev_guest(vcpu))
 		sev_init_vmcb(svm, init_event);
 
@@ -1326,9 +1346,15 @@ static int svm_vcpu_create(struct kvm_vcpu *vcpu)
 	if (!vmcb01_page)
 		goto out;
 
+	if (pml) {
+		vcpu->arch.pml_page = snp_safe_alloc_page();
+		if (!vcpu->arch.pml_page)
+			goto error_free_vmcb_page;
+	}
+
 	err = sev_vcpu_create(vcpu);
 	if (err)
-		goto error_free_vmcb_page;
+		goto error_free_pml_page;
 
 	err = avic_init_vcpu(svm);
 	if (err)
@@ -1353,6 +1379,9 @@ static int svm_vcpu_create(struct kvm_vcpu *vcpu)
 
 error_free_sev:
 	sev_free_vcpu(vcpu);
+error_free_pml_page:
+	if (vcpu->arch.pml_page)
+		__free_page(vcpu->arch.pml_page);
 error_free_vmcb_page:
 	__free_page(vmcb01_page);
 out:
@@ -1370,6 +1399,9 @@ static void svm_vcpu_free(struct kvm_vcpu *vcpu)
 
 	sev_free_vcpu(vcpu);
 
+	if (pml && vcpu->arch.pml_page)
+		__free_page(vcpu->arch.pml_page);
+
 	__free_page(__sme_pa_to_page(svm->vmcb01.pa));
 	svm_vcpu_free_msrpm(svm->msrpm);
 }
@@ -3344,6 +3376,45 @@ static int vmmcall_interception(struct kvm_vcpu *vcpu)
 	return kvm_emulate_hypercall(vcpu);
 }
 
+static void svm_update_cpu_dirty_logging(struct kvm_vcpu *vcpu, bool enable)
+{
+	struct vcpu_svm *svm = to_svm(vcpu);
+	struct vmcb *vmcb01 = svm->vmcb01.ptr;
+
+	if (enable)
+		vmcb01->control.misc_ctl |= SVM_MISC_ENABLE_PML;
+	else
+		vmcb01->control.misc_ctl &= ~SVM_MISC_ENABLE_PML;
+
+	vmcb_mark_dirty(vmcb01, VMCB_NPT);
+}
+
+static void svm_flush_pml_buffer(struct kvm_vcpu *vcpu)
+{
+	struct vcpu_svm *svm = to_svm(vcpu);
+	struct vmcb_control_area *control = &svm->vmcb->control;
+
+	/* Do nothing if PML buffer is empty */
+	if (control->pml_index == PML_HEAD_INDEX)
+		return;
+
+	kvm_flush_pml_buffer(vcpu, control->pml_index);
+
+	/* Reset the PML index */
+	control->pml_index = PML_HEAD_INDEX;
+}
+
+static int pml_full_interception(struct kvm_vcpu *vcpu)
+{
+	trace_kvm_pml_full(vcpu->vcpu_id);
+
+	/*
+	 * PML buffer is already flushed at the beginning of svm_handle_exit().
+	 * Nothing to do here.
+	 */
+	return 1;
+}
+
 static int (*const svm_exit_handlers[])(struct kvm_vcpu *vcpu) = {
 	[SVM_EXIT_READ_CR0]			= cr_interception,
 	[SVM_EXIT_READ_CR3]			= cr_interception,
@@ -3421,6 +3492,7 @@ static int (*const svm_exit_handlers[])(struct kvm_vcpu *vcpu) = {
 #ifdef CONFIG_KVM_AMD_SEV
 	[SVM_EXIT_VMGEXIT]			= sev_handle_vmgexit,
 #endif
+	[SVM_EXIT_PML_FULL]			= pml_full_interception,
 };
 
 static void dump_vmcb(struct kvm_vcpu *vcpu)
@@ -3470,8 +3542,14 @@ static void dump_vmcb(struct kvm_vcpu *vcpu)
 	pr_err("%-20s%016llx\n", "exit_info2:", control->exit_info_2);
 	pr_err("%-20s%08x\n", "exit_int_info:", control->exit_int_info);
 	pr_err("%-20s%08x\n", "exit_int_info_err:", control->exit_int_info_err);
-	pr_err("%-20s%lld\n", "misc_ctl:", control->misc_ctl);
+	pr_err("%-20s%llx\n", "misc_ctl:", control->misc_ctl);
 	pr_err("%-20s%016llx\n", "nested_cr3:", control->nested_cr3);
+
+	if (pml) {
+		pr_err("%-20s%016llx\n", "pml_addr:", control->pml_addr);
+		pr_err("%-20s%04x\n", "pml_index:", control->pml_index);
+	}
+
 	pr_err("%-20s%016llx\n", "avic_vapic_bar:", control->avic_vapic_bar);
 	pr_err("%-20s%016llx\n", "ghcb:", control->ghcb_gpa);
 	pr_err("%-20s%08x\n", "event_inj:", control->event_inj);
@@ -3649,6 +3727,13 @@ int svm_invoke_exit_handler(struct kvm_vcpu *vcpu, u64 __exit_code)
 	    (u64)exit_code != __exit_code)
 		goto unexpected_vmexit;
 
+	/*
+	 * PML is never enabled when running L2, bail immediately if a PML full
+	 * exit occurs as something is horribly wrong.
+	 */
+	if (unlikely(is_guest_mode(vcpu) && exit_code == SVM_EXIT_PML_FULL))
+		goto unexpected_vmexit;
+
 #ifdef CONFIG_MITIGATION_RETPOLINE
 	if (exit_code == SVM_EXIT_MSR)
 		return msr_interception(vcpu);
@@ -3717,6 +3802,14 @@ static int svm_handle_exit(struct kvm_vcpu *vcpu, fastpath_t exit_fastpath)
 	struct vcpu_svm *svm = to_svm(vcpu);
 	struct kvm_run *kvm_run = vcpu->run;
 
+	/*
+	 * Opportunistically flush the PML buffer on VM exit. This keeps the
+	 * dirty bitmap current by processing logged GPAs rather than waiting for
+	 * PML_FULL exit.
+	 */
+	if (vcpu->kvm->arch.cpu_dirty_log_size && !is_guest_mode(vcpu))
+		svm_flush_pml_buffer(vcpu);
+
 	if (unlikely(exit_fastpath == EXIT_FASTPATH_EXIT_USERSPACE))
 		return 0;
 
@@ -5311,6 +5404,9 @@ static int svm_vm_init(struct kvm *kvm)
 	if (!pause_filter_count || !pause_filter_thresh)
 		kvm_disable_exits(kvm, KVM_X86_DISABLE_EXITS_PAUSE);
 
+	if (pml)
+		kvm->arch.cpu_dirty_log_size = PML_LOG_NR_ENTRIES;
+
 	svm_srso_vm_init();
 	return 0;
 }
@@ -5429,6 +5525,8 @@ struct kvm_x86_ops svm_x86_ops __initdata = {
 	.check_intercept = svm_check_intercept,
 	.handle_exit_irqoff = svm_handle_exit_irqoff,
 
+	.update_cpu_dirty_logging = svm_update_cpu_dirty_logging,
+
 	.deliver_interrupt = svm_deliver_interrupt,
 	.pi_update_irte = avic_pi_update_irte,
 	.setup_mce = svm_setup_mce,
@@ -5689,6 +5787,10 @@ static __init int svm_hardware_setup(void)
 
 	nrips = nrips && boot_cpu_has(X86_FEATURE_NRIPS);
 
+	pml = pml && npt_enabled && cpu_feature_enabled(X86_FEATURE_PML);
+	if (pml)
+		pr_info("Page modification logging supported\n");
+
 	if (lbrv) {
 		if (!boot_cpu_has(X86_FEATURE_LBRV))
 			lbrv = false;
diff --git a/arch/x86/kvm/svm/svm.h b/arch/x86/kvm/svm/svm.h
index e958943b8162..f566dae44bb0 100644
--- a/arch/x86/kvm/svm/svm.h
+++ b/arch/x86/kvm/svm/svm.h
@@ -53,6 +53,7 @@ extern int vgif;
 extern bool intercept_smi;
 extern bool vnmi;
 extern int lbrv;
+extern bool pml;
 
 extern int tsc_aux_uret_slot __ro_after_init;
 
-- 
2.48.1


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

* Re: [PATCH v8 6/8] x86/cpufeatures: Add Page modification logging
  2026-09-07  6:39 ` [PATCH v8 6/8] x86/cpufeatures: Add Page modification logging Nikunj A Dadhania
@ 2026-09-07  6:47   ` sashiko-bot
  2026-09-07  9:49     ` Nikunj A. Dadhania
  0 siblings, 1 reply; 14+ messages in thread
From: sashiko-bot @ 2026-09-07  6:47 UTC (permalink / raw)
  To: Nikunj A Dadhania; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] Missing synchronization of tools/arch/x86/include/asm/cpufeatures.h with arch/x86/include/asm/cpufeatures.h.
--

commit 86e9807958bbcad0cbcf6a0ae079d6e8a75e8935
Author: Nikunj A Dadhania <nikunj@amd.com>

x86/cpufeatures: Add Page modification logging

Page modification logging(PML) is a hardware feature designed to track
guest modified memory pages. The feature is advertised via CPUID leaf
0x8000000A ECX[4] bit.

> diff --git a/arch/x86/include/asm/cpufeatures.h b/arch/x86/include/asm/cpufeatures.h
> index f70ee74b5f921..cbfcc66a18e9b 100644
> --- a/arch/x86/include/asm/cpufeatures.h
> +++ b/arch/x86/include/asm/cpufeatures.h
> @@ -238,6 +238,7 @@
>  // free: was #define X86_FEATURE_PVUNLOCK               ( 8*32+20) /* PV unlock function */
>  #define X86_FEATURE_VCPUPREEMPT		( 8*32+21) /* PV vcpu_is_preempted function */
>  #define X86_FEATURE_TDX_GUEST		( 8*32+22) /* "tdx_guest" Intel Trust Domain Extensions Guest */
> +#define X86_FEATURE_PML			( 8*32+23) /* AMD Page Modification logging */

[Severity: Low]
Does this new feature flag also need to be added to
tools/arch/x86/include/asm/cpufeatures.h to keep the tools header in sync
with the main kernel header?

Without this synchronization, will building kernel tools trigger 'Kernel ABI
header differences' warnings during tools/perf/check-headers.sh, potentially
breaking builds configured with WERROR?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260907063906.1964557-1-nikunj@amd.com?part=6

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

* Re: [PATCH v8 8/8] KVM: SVM: Add Page modification logging support
  2026-09-07  6:39 ` [PATCH v8 8/8] KVM: SVM: Add Page modification logging support Nikunj A Dadhania
@ 2026-09-07  7:00   ` sashiko-bot
  2026-09-08  4:08     ` Nikunj A. Dadhania
  0 siblings, 1 reply; 14+ messages in thread
From: sashiko-bot @ 2026-09-07  7:00 UTC (permalink / raw)
  To: Nikunj A Dadhania; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Critical] Hardware PML is disabled for nested L2 guests, but the KVM MMU is not informed, causing all memory dirtied by L2 to be silently lost.
- [High] The PML index is reset in memory but the VMCB is not marked dirty, causing the processor to use a stale cached index and livelock.
- [Medium] Missing .flush_pml_buffer implementation in svm_x86_ops causes KVM_GET_DIRTY_LOG to return incomplete data.
--

commit d1428c9c095cb0cc4ca5ce0ed8ebb044a3eb17a3
Author: Nikunj A Dadhania <nikunj@amd.com>
Subject: KVM: SVM: Add Page modification logging support

This commit adds support for the Page Modification Logging (PML) feature
on AMD processors. PML automatically logs dirty guest physical addresses to
a buffer when the CPU sets NPT D-bits, offering a hardware-assisted method
for efficient dirty logging.

> diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
> index 73f37b050d0a0..da417c4c59154 100644
> --- a/arch/x86/kvm/svm/nested.c
> +++ b/arch/x86/kvm/svm/nested.c
> @@ -901,6 +901,13 @@ static void nested_vmcb02_prepare_control(struct vcpu_svm *svm)
>  	vmcb02->control.msrpm_base_pa = vmcb01->control.msrpm_base_pa;
>  	vmcb_mark_dirty(vmcb02, VMCB_PERM_MAP);
>  
> +	/*
> +	 * PML is never enabled in hardware for L2.  Make sure that an
> +	 * unexpected PML write would trigger a PML_FULL VM-Exit.
> +	 */
> +	if (pml)
> +		vmcb02->control.pml_index = -1;

[Severity: Critical]
By intentionally leaving hardware PML disabled for nested L2 guests in 
nested_vmcb02_prepare_control without informing the KVM MMU, doesn't this 
cause all memory dirtied by L2 to be silently lost?

When an L2 guest writes to memory and the host KVM has PML enabled, the KVM 
MMU creates L2 shadow NPT SPTEs as writable but clears the D-bit. If hardware 
PML is disabled for L2, the hardware will set the D-bit but will not log the 
GPA, potentially leading to silent memory corruption upon live migration.

[ ... ]

> diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
> index 7d59d301e1e54..950c250a77b51 100644
> --- a/arch/x86/kvm/svm/svm.c
> +++ b/arch/x86/kvm/svm/svm.c
[ ... ]
> @@ -3344,6 +3376,45 @@ static int vmmcall_interception(struct kvm_vcpu *vcpu)
>  	return kvm_emulate_hypercall(vcpu);
>  }
>  
> +static void svm_update_cpu_dirty_logging(struct kvm_vcpu *vcpu, bool enable)
> +{
> +	struct vcpu_svm *svm = to_svm(vcpu);
> +	struct vmcb *vmcb01 = svm->vmcb01.ptr;
> +
> +	if (enable)
> +		vmcb01->control.misc_ctl |= SVM_MISC_ENABLE_PML;
> +	else
> +		vmcb01->control.misc_ctl &= ~SVM_MISC_ENABLE_PML;
> +
> +	vmcb_mark_dirty(vmcb01, VMCB_NPT);
> +}
> +
> +static void svm_flush_pml_buffer(struct kvm_vcpu *vcpu)
> +{
> +	struct vcpu_svm *svm = to_svm(vcpu);
> +	struct vmcb_control_area *control = &svm->vmcb->control;
> +
> +	/* Do nothing if PML buffer is empty */
> +	if (control->pml_index == PML_HEAD_INDEX)
> +		return;
> +
> +	kvm_flush_pml_buffer(vcpu, control->pml_index);
> +
> +	/* Reset the PML index */
> +	control->pml_index = PML_HEAD_INDEX;
> +}

[Severity: High]
Since the PML index is reset here without marking the VMCB_NPT field dirty via
vmcb_mark_dirty(svm->vmcb, VMCB_NPT), will the CPU hardware ignore the reset
and use a stale cached value?

This could lead to a situation where the processor continuously generates a
PML_FULL VMEXIT on every subsequent write because the cached index remains at
zero, causing guest livelock and a performance collapse.

[ ... ]
> @@ -5429,6 +5525,8 @@ struct kvm_x86_ops svm_x86_ops __initdata = {
>  	.check_intercept = svm_check_intercept,
>  	.handle_exit_irqoff = svm_handle_exit_irqoff,
>  
> +	.update_cpu_dirty_logging = svm_update_cpu_dirty_logging,
> +
>  	.deliver_interrupt = svm_deliver_interrupt,
>  	.pi_update_irte = avic_pi_update_irte,
>  	.setup_mce = svm_setup_mce,

[Severity: Medium]
Is there a missing implementation of .flush_pml_buffer in svm_x86_ops?

Without .flush_pml_buffer exported to the generic KVM x86 ops, 
kvm_vcpu_flush_pml() might silently do nothing. This would result in
KVM_GET_DIRTY_LOG returning incomplete data, potentially delaying live
migration convergence or confusing userspace tools expecting an accurate
snapshot.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260907063906.1964557-1-nikunj@amd.com?part=8

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

* Re: [PATCH v8 6/8] x86/cpufeatures: Add Page modification logging
  2026-09-07  6:47   ` sashiko-bot
@ 2026-09-07  9:49     ` Nikunj A. Dadhania
  0 siblings, 0 replies; 14+ messages in thread
From: Nikunj A. Dadhania @ 2026-09-07  9:49 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: kvm



On 9/7/2026 12:17 PM, sashiko-bot@kernel.org wrote:

>> diff --git a/arch/x86/include/asm/cpufeatures.h b/arch/x86/include/asm/cpufeatures.h
>> index f70ee74b5f921..cbfcc66a18e9b 100644
>> --- a/arch/x86/include/asm/cpufeatures.h
>> +++ b/arch/x86/include/asm/cpufeatures.h
>> @@ -238,6 +238,7 @@
>>  // free: was #define X86_FEATURE_PVUNLOCK               ( 8*32+20) /* PV unlock function */
>>  #define X86_FEATURE_VCPUPREEMPT              ( 8*32+21) /* PV vcpu_is_preempted function */
>>  #define X86_FEATURE_TDX_GUEST                ( 8*32+22) /* "tdx_guest" Intel Trust Domain Extensions Guest */
>> +#define X86_FEATURE_PML                      ( 8*32+23) /* AMD Page Modification logging */
> 
> [Severity: Low]
> Does this new feature flag also need to be added to
> tools/arch/x86/include/asm/cpufeatures.h to keep the tools header in sync
> with the main kernel header?

No, as per tools/include/uapi/README, updating tools/ copies of kernel
headers is done by perf/tools maintainers, not the patch submitter.

Regards,
Nikunj

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

* Re: [PATCH v8 8/8] KVM: SVM: Add Page modification logging support
  2026-09-07  7:00   ` sashiko-bot
@ 2026-09-08  4:08     ` Nikunj A. Dadhania
  2026-09-10  0:22       ` Sean Christopherson
  0 siblings, 1 reply; 14+ messages in thread
From: Nikunj A. Dadhania @ 2026-09-08  4:08 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: kvm



On 9/7/2026 12:30 PM, sashiko-bot@kernel.org wrote:

>> diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
>> index 73f37b050d0a0..da417c4c59154 100644
>> --- a/arch/x86/kvm/svm/nested.c
>> +++ b/arch/x86/kvm/svm/nested.c
>> @@ -901,6 +901,13 @@ static void nested_vmcb02_prepare_control(struct vcpu_svm *svm)
>>       vmcb02->control.msrpm_base_pa = vmcb01->control.msrpm_base_pa;
>>       vmcb_mark_dirty(vmcb02, VMCB_PERM_MAP);
>>
>> +     /*
>> +      * PML is never enabled in hardware for L2.  Make sure that an
>> +      * unexpected PML write would trigger a PML_FULL VM-Exit.
>> +      */
>> +     if (pml)
>> +             vmcb02->control.pml_index = -1;
> 
> [Severity: Critical]
> By intentionally leaving hardware PML disabled for nested L2 guests in
> nested_vmcb02_prepare_control without informing the KVM MMU, doesn't this
> cause all memory dirtied by L2 to be silently lost?

No, kvm_mmu_page_ad_need_write_protect() returns true when
cpu_dirty_log_size is set and the shadow page is in guest_mode,
which forces write-protection on L2 SPTEs instead of relying on
hardware PML. L2 dirty pages are tracked via write faults, not PML.
This is the same approach VMX uses for nested guests.

> 
> When an L2 guest writes to memory and the host KVM has PML enabled, the KVM
> MMU creates L2 shadow NPT SPTEs as writable but clears the D-bit. If hardware
> PML is disabled for L2, the hardware will set the D-bit but will not log the
> GPA, potentially leading to silent memory corruption upon live migration.
> 
> [ ... ]
> 
>> diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
>> index 7d59d301e1e54..950c250a77b51 100644
>> --- a/arch/x86/kvm/svm/svm.c
>> +++ b/arch/x86/kvm/svm/svm.c
> [ ... ]
>> @@ -3344,6 +3376,45 @@ static int vmmcall_interception(struct kvm_vcpu *vcpu)
>>       return kvm_emulate_hypercall(vcpu);
>>  }
>>
>> +static void svm_update_cpu_dirty_logging(struct kvm_vcpu *vcpu, bool enable)
>> +{
>> +     struct vcpu_svm *svm = to_svm(vcpu);
>> +     struct vmcb *vmcb01 = svm->vmcb01.ptr;
>> +
>> +     if (enable)
>> +             vmcb01->control.misc_ctl |= SVM_MISC_ENABLE_PML;
>> +     else
>> +             vmcb01->control.misc_ctl &= ~SVM_MISC_ENABLE_PML;
>> +
>> +     vmcb_mark_dirty(vmcb01, VMCB_NPT);
>> +}
>> +
>> +static void svm_flush_pml_buffer(struct kvm_vcpu *vcpu)
>> +{
>> +     struct vcpu_svm *svm = to_svm(vcpu);
>> +     struct vmcb_control_area *control = &svm->vmcb->control;
>> +
>> +     /* Do nothing if PML buffer is empty */
>> +     if (control->pml_index == PML_HEAD_INDEX)
>> +             return;
>> +
>> +     kvm_flush_pml_buffer(vcpu, control->pml_index);
>> +
>> +     /* Reset the PML index */
>> +     control->pml_index = PML_HEAD_INDEX;
>> +}
> 
> [Severity: High]
> Since the PML index is reset here without marking the VMCB_NPT field dirty via
> vmcb_mark_dirty(svm->vmcb, VMCB_NPT), will the CPU hardware ignore the reset
> and use a stale cached value?

No clean bits are defined for the PML_ADDR and PML_INDEX fields, so on a
VMRUN, hardware always loads them from the VMCB if the feature is enabled.

> 
> This could lead to a situation where the processor continuously generates a
> PML_FULL VMEXIT on every subsequent write because the cached index remains at
> zero, causing guest livelock and a performance collapse.
> 
> [ ... ]
>> @@ -5429,6 +5525,8 @@ struct kvm_x86_ops svm_x86_ops __initdata = {
>>       .check_intercept = svm_check_intercept,
>>       .handle_exit_irqoff = svm_handle_exit_irqoff,
>>
>> +     .update_cpu_dirty_logging = svm_update_cpu_dirty_logging,
>> +
>>       .deliver_interrupt = svm_deliver_interrupt,
>>       .pi_update_irte = avic_pi_update_irte,
>>       .setup_mce = svm_setup_mce,
> 
> [Severity: Medium]
> Is there a missing implementation of .flush_pml_buffer in svm_x86_ops?

No. flush_pml_buffer is not a kvm_x86_ops operation. SVM calls
svm_flush_pml_buffer() directly from svm_handle_exit() on every
VM exit, including SVM_EXIT_PML_FULL.
 
> Without .flush_pml_buffer exported to the generic KVM x86 ops,
> kvm_vcpu_flush_pml() might silently do nothing. This would result in
> KVM_GET_DIRTY_LOG returning incomplete data, potentially delaying live
> migration convergence or confusing userspace tools expecting an accurate
> snapshot.

Regards,
Nikunj

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

* Re: [PATCH v8 8/8] KVM: SVM: Add Page modification logging support
  2026-09-08  4:08     ` Nikunj A. Dadhania
@ 2026-09-10  0:22       ` Sean Christopherson
  0 siblings, 0 replies; 14+ messages in thread
From: Sean Christopherson @ 2026-09-10  0:22 UTC (permalink / raw)
  To: Nikunj A. Dadhania; +Cc: sashiko-reviews, kvm

On Tue, Sep 08, 2026, Nikunj A. Dadhania wrote:
> 
> 
> On 9/7/2026 12:30 PM, sashiko-bot@kernel.org wrote:
> 
> >> diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
> >> index 73f37b050d0a0..da417c4c59154 100644
> >> --- a/arch/x86/kvm/svm/nested.c
> >> +++ b/arch/x86/kvm/svm/nested.c
> >> @@ -901,6 +901,13 @@ static void nested_vmcb02_prepare_control(struct vcpu_svm *svm)
> >>       vmcb02->control.msrpm_base_pa = vmcb01->control.msrpm_base_pa;
> >>       vmcb_mark_dirty(vmcb02, VMCB_PERM_MAP);
> >>
> >> +     /*
> >> +      * PML is never enabled in hardware for L2.  Make sure that an
> >> +      * unexpected PML write would trigger a PML_FULL VM-Exit.
> >> +      */
> >> +     if (pml)
> >> +             vmcb02->control.pml_index = -1;
> > 
> > [Severity: Critical]
> > By intentionally leaving hardware PML disabled for nested L2 guests in
> > nested_vmcb02_prepare_control without informing the KVM MMU, doesn't this
> > cause all memory dirtied by L2 to be silently lost?
> 
> No, kvm_mmu_page_ad_need_write_protect() returns true when
> cpu_dirty_log_size is set and the shadow page is in guest_mode,
> which forces write-protection on L2 SPTEs instead of relying on
> hardware PML. L2 dirty pages are tracked via write faults, not PML.
> This is the same approach VMX uses for nested guests.

And FWIW, because Sashiko's hallucinations made me double check, this series
doesn't advertise PML to L1, i.e. doesn't need to implement and wire up
kvm_x86_nested_ops.write_log_dirty() for SVM.

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

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

Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-07  6:38 [PATCH v8 0/8] KVM: SVM: Add Page Modification Logging (PML) support Nikunj A Dadhania
2026-09-07  6:38 ` [PATCH v8 1/8] KVM: VMX: Pass @vcpu, not @vmx to init_vmcs() Nikunj A Dadhania
2026-09-07  6:39 ` [PATCH v8 2/8] KVM: x86: Move PML page to common vcpu arch structure Nikunj A Dadhania
2026-09-07  6:39 ` [PATCH v8 3/8] KVM: x86: Carve out PML flush routine Nikunj A Dadhania
2026-09-07  6:39 ` [PATCH v8 4/8] KVM: VMX: Use cpu_dirty_log_size instead of enable_pml for PML checks Nikunj A Dadhania
2026-09-07  6:39 ` [PATCH v8 5/8] KVM: x86: Carve out common dirty logging update Nikunj A Dadhania
2026-09-07  6:39 ` [PATCH v8 6/8] x86/cpufeatures: Add Page modification logging Nikunj A Dadhania
2026-09-07  6:47   ` sashiko-bot
2026-09-07  9:49     ` Nikunj A. Dadhania
2026-09-07  6:39 ` [PATCH v8 7/8] KVM: SVM: Use BIT_ULL for 64-bit misc_ctl bit definitions Nikunj A Dadhania
2026-09-07  6:39 ` [PATCH v8 8/8] KVM: SVM: Add Page modification logging support Nikunj A Dadhania
2026-09-07  7:00   ` sashiko-bot
2026-09-08  4:08     ` Nikunj A. Dadhania
2026-09-10  0:22       ` Sean Christopherson

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