KVM: x86: Make cpu_dirty_log_size a per-VM value
authorYan Zhao <yan.y.zhao@intel.com>
Fri, 24 Jan 2025 01:58:26 +0000 (17:58 -0800)
committerPaolo Bonzini <pbonzini@redhat.com>
Fri, 14 Mar 2025 18:20:53 +0000 (14:20 -0400)
Make cpu_dirty_log_size (CPU's dirty log buffer size) a per-VM value and
set the per-VM cpu_dirty_log_size only for normal VMs when PML is enabled.
Do not set it for TDs.

Until now, cpu_dirty_log_size was a system-wide value that is used for
all VMs and is set to the PML buffer size when PML was enabled in VMX.
However, PML is not currently supported for TDs, though PML remains
available for normal VMs as long as the feature is supported by hardware
and enabled in VMX.

Making cpu_dirty_log_size a per-VM value allows it to be ther PML buffer
size for normal VMs and 0 for TDs. This allows functions like
kvm_arch_sync_dirty_log() and kvm_mmu_update_cpu_dirty_logging() to
determine if PML is supported, in order to kick off vCPUs or request them
to update CPU dirty logging status (turn on/off PML in VMCS).

This fixes an issue first reported in [1], where QEMU attaches an
emulated VGA device to a TD; note that KVM_MEM_LOG_DIRTY_PAGES
still works if the corresponding has no flag KVM_MEM_GUEST_MEMFD.
KVM then invokes kvm_mmu_update_cpu_dirty_logging() and from there
vmx_update_cpu_dirty_logging(), which incorrectly accesses a kvm_vmx
struct for a TDX VM.

Reported-by: ANAND NARSHINHA PATIL <Anand.N.Patil@ibm.com>
Reported-by: Pedro Principeza <pedro.principeza@canonical.com>
Reported-by: Farrah Chen <farrah.chen@intel.com>
Closes: https://github.com/canonical/tdx/issues/202
Link: https://github.com/canonical/tdx/issues/202
Suggested-by: Kai Huang <kai.huang@intel.com>
Signed-off-by: Yan Zhao <yan.y.zhao@intel.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
arch/x86/include/asm/kvm_host.h
arch/x86/kvm/mmu/mmu.c
arch/x86/kvm/mmu/mmu_internal.h
arch/x86/kvm/vmx/main.c
arch/x86/kvm/vmx/vmx.c
arch/x86/kvm/x86.c

index 6800d3956ab12c342bf94baf6c2d8413eafbd72a..ddb08581c64da63a90a86d47dfcea569668b81d6 100644 (file)
@@ -1562,6 +1562,13 @@ struct kvm_arch {
        struct kvm_mmu_memory_cache split_desc_cache;
 
        gfn_t gfn_direct_bits;
+
+       /*
+        * Size of the CPU's dirty log buffer, i.e. VMX's PML buffer. A Zero
+        * value indicates CPU dirty logging is unsupported or disabled in
+        * current VM.
+        */
+       int cpu_dirty_log_size;
 };
 
 struct kvm_vm_stat {
@@ -1815,11 +1822,6 @@ struct kvm_x86_ops {
                               struct x86_exception *exception);
        void (*handle_exit_irqoff)(struct kvm_vcpu *vcpu);
 
-       /*
-        * Size of the CPU's dirty log buffer, i.e. VMX's PML buffer.  A zero
-        * value indicates CPU dirty logging is unsupported or disabled.
-        */
-       int cpu_dirty_log_size;
        void (*update_cpu_dirty_logging)(struct kvm_vcpu *vcpu);
 
        const struct kvm_x86_nested_ops *nested_ops;
index a0c736c1109153195f1be8892873a48305ebd88c..0c6a4568af5b945da746a8d0798af23562963e48 100644 (file)
@@ -1305,7 +1305,7 @@ void kvm_arch_mmu_enable_log_dirty_pt_masked(struct kvm *kvm,
         * enabled but it chooses between clearing the Dirty bit and Writeable
         * bit based on the context.
         */
-       if (kvm_x86_ops.cpu_dirty_log_size)
+       if (kvm->arch.cpu_dirty_log_size)
                kvm_mmu_clear_dirty_pt_masked(kvm, slot, gfn_offset, mask);
        else
                kvm_mmu_write_protect_pt_masked(kvm, slot, gfn_offset, mask);
@@ -1313,7 +1313,7 @@ void kvm_arch_mmu_enable_log_dirty_pt_masked(struct kvm *kvm,
 
 int kvm_cpu_dirty_log_size(struct kvm *kvm)
 {
-       return kvm_x86_ops.cpu_dirty_log_size;
+       return kvm->arch.cpu_dirty_log_size;
 }
 
 bool kvm_mmu_slot_gfn_write_protect(struct kvm *kvm,
index 86d6d4f82cf435a26932a5c41985d5f4e2cddb07..db8f33e4de624601b1bc2b28c1e9357da1c15022 100644 (file)
@@ -198,7 +198,7 @@ static inline bool kvm_mmu_page_ad_need_write_protect(struct kvm *kvm,
         * being enabled is mandatory as the bits used to denote WP-only SPTEs
         * are reserved for PAE paging (32-bit KVM).
         */
-       return kvm_x86_ops.cpu_dirty_log_size && sp->role.guest_mode;
+       return kvm->arch.cpu_dirty_log_size && sp->role.guest_mode;
 }
 
 static inline gfn_t gfn_round_for_level(gfn_t gfn, int level)
index abb0fc723a0b95899151c40f33df82286e9ece83..ba3a23747bb1c57c1f087cfa3f94e7b4253e7960 100644 (file)
@@ -322,7 +322,6 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
        .check_intercept = vmx_check_intercept,
        .handle_exit_irqoff = vmx_handle_exit_irqoff,
 
-       .cpu_dirty_log_size = PML_LOG_NR_ENTRIES,
        .update_cpu_dirty_logging = vmx_update_cpu_dirty_logging,
 
        .nested_ops = &vmx_nested_ops,
index cf0a8a040f7ba3e8d17f99ec3af4c1fc3f7d6791..39d55d6388995e91eda4b55347ddde0458596d77 100644 (file)
@@ -7649,6 +7649,9 @@ int vmx_vm_init(struct kvm *kvm)
                        break;
                }
        }
+
+       if (enable_pml)
+               kvm->arch.cpu_dirty_log_size = PML_LOG_NR_ENTRIES;
        return 0;
 }
 
@@ -8502,9 +8505,6 @@ __init int vmx_hardware_setup(void)
        if (!enable_ept || !enable_ept_ad_bits || !cpu_has_vmx_pml())
                enable_pml = 0;
 
-       if (!enable_pml)
-               vt_x86_ops.cpu_dirty_log_size = 0;
-
        if (!cpu_has_vmx_preemption_timer())
                enable_preemption_timer = false;
 
index 9f92170226e5dacc7abbacbb51008aa12f9f2a3b..8f3292ccdca4d415b274053f7104679db27a8e7f 100644 (file)
@@ -6480,7 +6480,7 @@ void kvm_arch_sync_dirty_log(struct kvm *kvm, struct kvm_memory_slot *memslot)
        struct kvm_vcpu *vcpu;
        unsigned long i;
 
-       if (!kvm_x86_ops.cpu_dirty_log_size)
+       if (!kvm->arch.cpu_dirty_log_size)
                return;
 
        kvm_for_each_vcpu(i, vcpu, kvm)
@@ -13078,7 +13078,7 @@ static void kvm_mmu_update_cpu_dirty_logging(struct kvm *kvm, bool enable)
 {
        int nr_slots;
 
-       if (!kvm_x86_ops.cpu_dirty_log_size)
+       if (!kvm->arch.cpu_dirty_log_size)
                return;
 
        nr_slots = atomic_read(&kvm->nr_memslots_dirty_logging);
@@ -13150,7 +13150,7 @@ static void kvm_mmu_slot_apply_flags(struct kvm *kvm,
                if (READ_ONCE(eager_page_split))
                        kvm_mmu_slot_try_split_huge_pages(kvm, new, PG_LEVEL_4K);
 
-               if (kvm_x86_ops.cpu_dirty_log_size) {
+               if (kvm->arch.cpu_dirty_log_size) {
                        kvm_mmu_slot_leaf_clear_dirty(kvm, new);
                        kvm_mmu_slot_remove_write_access(kvm, new, PG_LEVEL_2M);
                } else {