Re: [PATCH v4 2/5] RISC-V: KVM: Split huge pages when dirty logging is enabled

Anup Patel <[email protected]> Fri, 31 Jul 2026 11:10:03 +0530
Newsgroups org.infradead.lists.linux-riscv,org.infradead.lists.kvm-riscv,org.kernel.vger.kvm,org.kernel.vger.linux-kernel
Message-ID <CAAhSdy2AKgcpEokcx2pVWUeB+kS0FAXX1Y2MkJoisqnVZmRO+A@mail.gmail.com>
On Wed, Jul 1, 2026 at 5:41 PM Wang Yechao <[email protected]> wrote:
>
> Split huge pages eagerly when enabling dirty logging. The goal is to
> avoid doing it while faulting on write-protected pages, which
> negatively impacts guest performance.
>
> The benefits of eager page splitting are the same as in x86 and arm64,
> added with commit a3fe5dbda0a4 ("KVM: x86/mmu: Split huge pages mapped
> by the TDP MMU when dirty logging is enabled") and commit e7bf7a490c68
> ("KVM: arm64: Split huge pages when dirty logging is enabled")
>
> Signed-off-by: Wang Yechao <[email protected]>
> ---
>  arch/riscv/include/asm/kvm_gstage.h |  6 +--
>  arch/riscv/kvm/gstage.c             | 23 ++++++---
>  arch/riscv/kvm/mmu.c                | 76 +++++++++++++++++++++++++++++
>  3 files changed, 95 insertions(+), 10 deletions(-)
>
> diff --git a/arch/riscv/include/asm/kvm_gstage.h b/arch/riscv/include/asm/kvm_gstage.h
> index 21e2019df0cf5..f726279780177 100644
> --- a/arch/riscv/include/asm/kvm_gstage.h
> +++ b/arch/riscv/include/asm/kvm_gstage.h
> @@ -64,9 +64,9 @@ int kvm_riscv_gstage_map_page(struct kvm_gstage *gstage,
>                               bool page_rdonly, bool page_exec,
>                               struct kvm_gstage_mapping *out_map);
>
> -int kvm_riscv_gstage_split_huge(struct kvm_gstage *gstage,
> -                               struct kvm_mmu_memory_cache *pcache,
> -                               gpa_t addr, u32 target_level, bool flush);
> +bool kvm_riscv_gstage_split_huge(struct kvm_gstage *gstage,
> +                                struct kvm_mmu_memory_cache *pcache,
> +                                gpa_t addr, u32 target_level, bool flush);
>
>  enum kvm_riscv_gstage_op {
>         GSTAGE_OP_NOP = 0,      /* Nothing */
> diff --git a/arch/riscv/kvm/gstage.c b/arch/riscv/kvm/gstage.c
> index c4c3b79567f10..4815233f9788d 100644
> --- a/arch/riscv/kvm/gstage.c
> +++ b/arch/riscv/kvm/gstage.c
> @@ -303,19 +303,20 @@ static inline unsigned long make_child_pte(unsigned long huge_pte, int index,
>         return child_pte;
>  }
>
> -int kvm_riscv_gstage_split_huge(struct kvm_gstage *gstage,
> -                               struct kvm_mmu_memory_cache *pcache,
> -                               gpa_t addr, u32 target_level, bool flush)
> +bool kvm_riscv_gstage_split_huge(struct kvm_gstage *gstage,
> +                                struct kvm_mmu_memory_cache *pcache,
> +                                gpa_t addr, u32 target_level, bool flush)
>  {
>         u32 current_level = gstage->pgd_levels - 1;
>         pte_t *next_ptep = (pte_t *)gstage->pgd;
>         unsigned long huge_pte, child_pte;
>         unsigned long child_page_size;
> +       bool need_flush = false;
>         pte_t *ptep;
>         int i, ret;
>
>         if (!pcache)
> -               return -ENOMEM;
> +               return false;
>
>         while(current_level > target_level) {
>                 ptep = (pte_t *)&next_ptep[gstage_pte_index(gstage, addr, current_level)];
> @@ -333,27 +334,35 @@ int kvm_riscv_gstage_split_huge(struct kvm_gstage *gstage,
>
>                 ret = gstage_level_to_page_size(gstage, current_level - 1, &child_page_size);
>                 if (ret)
> -                       return ret;
> +                       return need_flush;
>
>                 next_ptep = kvm_mmu_memory_cache_alloc(pcache);
>                 if (!next_ptep)
> -                       return -ENOMEM;
> +                       return need_flush;
>
>                 for (i = 0; i < PTRS_PER_PTE; i++) {
>                         child_pte = make_child_pte(huge_pte, i, child_page_size);
>                         set_pte((pte_t *)&next_ptep[i], __pte(child_pte));
>                 }
>
> +               /*
> +                * Ensure the writes to the child PTEs are visible before
> +                * linking the new page table to the parent PTE.
> +                */
> +               smp_wmb();
> +
>                 set_pte(ptep, pfn_pte(PFN_DOWN(__pa(next_ptep)),
>                                 __pgprot(_PAGE_TABLE)));
>
>                 if (flush)
>                         gstage_tlb_flush(gstage, current_level, addr);
> +               else
> +                       need_flush = true;
>
>                 current_level--;
>         }
>
> -       return 0;
> +       return need_flush;
>  }
>
>  bool kvm_riscv_gstage_op_pte(struct kvm_gstage *gstage, gpa_t addr,
> diff --git a/arch/riscv/kvm/mmu.c b/arch/riscv/kvm/mmu.c
> index 9cf69bc28b9c5..363238efaedb4 100644
> --- a/arch/riscv/kvm/mmu.c
> +++ b/arch/riscv/kvm/mmu.c
> @@ -97,6 +97,62 @@ void kvm_riscv_mmu_iounmap(struct kvm *kvm, gpa_t gpa, unsigned long size)
>                                             size >> PAGE_SHIFT);
>  }
>
> +static bool need_topup_split_caches_or_resched(struct kvm *kvm, int count)
> +{
> +       struct kvm_mmu_memory_cache *cache;
> +
> +       if (need_resched() || rwlock_needbreak(&kvm->mmu_lock))
> +               return true;
> +
> +       cache = &kvm->arch.pgd_split_page_cache;
> +       return kvm_mmu_memory_cache_nr_free_objects(cache) < count;
> +}
> +
> +static bool mmu_split_huge_pages(struct kvm_gstage *gstage,
> +                                phys_addr_t start, phys_addr_t end)
> +{
> +       struct kvm *kvm = gstage->kvm;
> +       struct kvm_mmu_memory_cache *pcache = &kvm->arch.pgd_split_page_cache;
> +       phys_addr_t addr = ALIGN_DOWN(start, PMD_SIZE);
> +       phys_addr_t last_flush_gfn = addr >> PAGE_SHIFT;
> +       int count = gstage->pgd_levels;
> +       bool flush = false;
> +       int ret;
> +
> +       lockdep_assert_held_write(&kvm->mmu_lock);
> +
> +       while (addr < end) {
> +               if (need_topup_split_caches_or_resched(kvm, count)) {
> +                       if (flush) {
> +                               kvm_flush_remote_tlbs_range(kvm, last_flush_gfn,
> +                                         (addr >> PAGE_SHIFT) - last_flush_gfn);
> +                               last_flush_gfn = addr >> PAGE_SHIFT;
> +                               flush = false;
> +                       }
> +
> +                       write_unlock(&kvm->mmu_lock);
> +                       cond_resched();
> +
> +                       ret = kvm_mmu_topup_memory_cache(pcache, count);
> +                       if (ret) {
> +                               kvm_err("Failed to toup split page cache\n");
> +                               write_lock(&kvm->mmu_lock);
> +                               return flush;
> +                       }
> +                       write_lock(&kvm->mmu_lock);
> +               }
> +
> +               if (!kvm->arch.pgd)
> +                       return flush;
> +
> +               flush |= kvm_riscv_gstage_split_huge(gstage, pcache, addr, 0, false);
> +
> +               addr += PMD_SIZE;
> +       }
> +
> +       return flush;
> +}
> +
>  void kvm_arch_mmu_enable_log_dirty_pt_masked(struct kvm *kvm,
>                                              struct kvm_memory_slot *slot,
>                                              gfn_t gfn_offset,
> @@ -151,6 +207,25 @@ void kvm_arch_flush_shadow_memslot(struct kvm *kvm,
>                                             size >> PAGE_SHIFT);
>  }
>
> +static void mmu_split_memory_region(struct kvm *kvm, int slot)
> +{
> +       struct kvm_memslots *slots = kvm_memslots(kvm);
> +       struct kvm_memory_slot *memslot = id_to_memslot(slots, slot);
> +       phys_addr_t start = memslot->base_gfn << PAGE_SHIFT;
> +       phys_addr_t end = (memslot->base_gfn + memslot->npages) << PAGE_SHIFT;
> +       struct kvm_gstage gstage;
> +       bool flush;
> +
> +       kvm_riscv_gstage_init(&gstage, kvm);
> +
> +       write_lock(&kvm->mmu_lock);
> +       flush = mmu_split_huge_pages(&gstage, start, end);
> +       write_unlock(&kvm->mmu_lock);
> +
> +       if (flush)
> +               kvm_flush_remote_tlbs_memslot(kvm, memslot);
> +}
> +
>  void kvm_arch_commit_memory_region(struct kvm *kvm,
>                                 struct kvm_memory_slot *old,
>                                 const struct kvm_memory_slot *new,
> @@ -164,6 +239,7 @@ void kvm_arch_commit_memory_region(struct kvm *kvm,
>                 if (kvm_dirty_log_manual_protect_and_init_set(kvm))
>                         return;
>                 mmu_wp_memory_region(kvm, new->id);
> +               mmu_split_memory_region(kvm, new->id);
>         }
>  }
>
> --
> 2.43.5
>

Make sure this works with 1G hugepages.

Otherwise, it looks good to me.

Reviewed-by: Anup Patel <[email protected]>

Thanks,
Anup

_______________________________________________
linux-riscv mailing list
[email protected]
http://lists.infradead.org/mailman/listinfo/linux-riscv